OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 // |
| 5 // Note: aside from using windows headers to obtain the definitions of minidump |
| 6 // structures, nothing here is windows specific. This seems like the best |
| 7 // approach given this code is for temporary experimentation on Windows. |
| 8 // Longer term, CrashPad will take over the minidump writing in this case as |
| 9 // well. |
| 10 |
| 11 #include "components/browser_watcher/postmortem_minidump_writer.h" |
| 12 |
| 13 #include <windows.h> // NOLINT |
| 14 #include <dbghelp.h> |
| 15 |
| 16 #include <string> |
| 17 |
| 18 #include "base/files/file_util.h" |
| 19 #include "base/numerics/safe_math.h" |
| 20 #include "third_party/crashpad/crashpad/minidump/minidump_extensions.h" |
| 21 |
| 22 namespace browser_watcher { |
| 23 |
| 24 // The stream type assigned to the minidump stream that holds the serialized |
| 25 // stability report. |
| 26 // Note: the value was obtained by adding 1 to the stream type used for holding |
| 27 // the SyzyAsan proto. |
| 28 // TODO(manzagop): centralize the stream type definitions to avoid issues. |
| 29 const uint32_t kStabilityReportStreamType = 0x4B6B0002; |
| 30 |
| 31 PostmortemMinidumpWriter::PostmortemMinidumpWriter( |
| 32 base::PlatformFile minidump_file) |
| 33 : next_available_byte_(0U), cursor_(0U), minidump_file_(minidump_file) { |
| 34 CHECK(minidump_file); |
| 35 } |
| 36 |
| 37 bool PostmortemMinidumpWriter::WriteDump(const StabilityReport& report, |
| 38 const crashpad::UUID& client_id, |
| 39 const crashpad::UUID& report_id, |
| 40 const std::string& product_name, |
| 41 const std::string& version_number) { |
| 42 DCHECK_NE(base::kInvalidPlatformFile, minidump_file_); |
| 43 DCHECK_EQ(0U, next_available_byte_); |
| 44 DCHECK(directory_.empty()); |
| 45 |
| 46 // Allocate space for the header and seek the cursor. |
| 47 Position pos = 0U; |
| 48 if (!Allocate(sizeof(MINIDUMP_HEADER), &pos)) |
| 49 return false; |
| 50 if (!SeekCursor(sizeof(MINIDUMP_HEADER))) |
| 51 return false; |
| 52 DCHECK_EQ(kHeaderPos, pos); |
| 53 |
| 54 // Write the proto to the file. |
| 55 std::string serialized_report; |
| 56 if (!report.SerializeToString(&serialized_report)) |
| 57 return false; |
| 58 Position report_pos = 0U; |
| 59 if (!AppendBytes(serialized_report, &report_pos)) |
| 60 return false; |
| 61 |
| 62 // The directory entry for the stability report's stream. |
| 63 RegisterDirectoryEntry(kStabilityReportStreamType, report_pos, |
| 64 serialized_report.length()); |
| 65 |
| 66 // Write mandatory crash keys. These will be read by crashpad and used as |
| 67 // http request parameters for the upload. Keys and values should match |
| 68 // server side configuration. |
| 69 // TODO(manzagop): use product and version from the stability report. The |
| 70 // current executable's values are an (imperfect) proxy. |
| 71 std::map<std::string, std::string> crash_keys = { |
| 72 {"product", product_name + "_Postmortem"}, {"version", version_number}}; |
| 73 if (!AppendCrashpadInfo(client_id, report_id, crash_keys)) |
| 74 return false; |
| 75 |
| 76 // Write the directory. |
| 77 Position directory_pos = 0U; |
| 78 if (!AppendVec(directory_, &directory_pos)) |
| 79 return false; |
| 80 |
| 81 // Write the header. |
| 82 MINIDUMP_HEADER header; |
| 83 header.Signature = MINIDUMP_SIGNATURE; |
| 84 header.Version = MINIDUMP_VERSION; |
| 85 header.NumberOfStreams = directory_.size(); |
| 86 header.StreamDirectoryRva = directory_pos; |
| 87 if (!SeekCursor(0U)) |
| 88 return false; |
| 89 return Write(kHeaderPos, header); |
| 90 } |
| 91 |
| 92 bool PostmortemMinidumpWriter::AppendCrashpadInfo( |
| 93 const crashpad::UUID& client_id, |
| 94 const crashpad::UUID& report_id, |
| 95 const std::map<std::string, std::string>& crash_keys) { |
| 96 // Write the crash keys as the contents of a crashpad dictionary. |
| 97 std::vector<crashpad::MinidumpSimpleStringDictionaryEntry> entries; |
| 98 for (const auto& crash_key : crash_keys) { |
| 99 if (!AppendCrashpadDictionaryEntry(crash_key.first, crash_key.second, |
| 100 &entries)) { |
| 101 return false; |
| 102 } |
| 103 } |
| 104 |
| 105 // Write the dictionary's index. |
| 106 Position dict_pos = 0U; |
| 107 uint32_t entry_count = entries.size(); |
| 108 if (entry_count > 0) { |
| 109 if (!Append(entry_count, &dict_pos)) |
| 110 return false; |
| 111 Position unused_pos = 0U; |
| 112 if (!AppendVec(entries, &unused_pos)) |
| 113 return false; |
| 114 } |
| 115 |
| 116 MINIDUMP_LOCATION_DESCRIPTOR simple_annotations; |
| 117 simple_annotations.DataSize = 0U; |
| 118 if (entry_count > 0) |
| 119 simple_annotations.DataSize = next_available_byte_ - dict_pos; |
| 120 // Note: an RVA of 0 indicates the absence of a dictionary. |
| 121 simple_annotations.Rva = dict_pos; |
| 122 |
| 123 // Write the crashpad info. |
| 124 crashpad::MinidumpCrashpadInfo crashpad_info; |
| 125 crashpad_info.version = crashpad::MinidumpCrashpadInfo::kVersion; |
| 126 crashpad_info.report_id = report_id; |
| 127 crashpad_info.client_id = client_id; |
| 128 crashpad_info.simple_annotations = simple_annotations; |
| 129 // Note: module_list is left at 0, which means none. |
| 130 |
| 131 Position crashpad_pos = 0U; |
| 132 if (!Append(crashpad_info, &crashpad_pos)) |
| 133 return false; |
| 134 |
| 135 // Append a directory entry for the crashpad info stream. |
| 136 RegisterDirectoryEntry(crashpad::kMinidumpStreamTypeCrashpadInfo, |
| 137 crashpad_pos, sizeof(crashpad::MinidumpCrashpadInfo)); |
| 138 |
| 139 return true; |
| 140 } |
| 141 |
| 142 bool PostmortemMinidumpWriter::AppendCrashpadDictionaryEntry( |
| 143 const std::string& key, |
| 144 const std::string& value, |
| 145 std::vector<crashpad::MinidumpSimpleStringDictionaryEntry>* entries) { |
| 146 DCHECK_NE(nullptr, entries); |
| 147 |
| 148 Position key_pos = 0U; |
| 149 if (!AppendUtf8String(key, &key_pos)) |
| 150 return false; |
| 151 Position value_pos = 0U; |
| 152 if (!AppendUtf8String(value, &value_pos)) |
| 153 return false; |
| 154 |
| 155 crashpad::MinidumpSimpleStringDictionaryEntry entry = {0}; |
| 156 entry.key = key_pos; |
| 157 entry.value = value_pos; |
| 158 entries->push_back(entry); |
| 159 |
| 160 return true; |
| 161 } |
| 162 |
| 163 bool PostmortemMinidumpWriter::Allocate(size_t size_bytes, Position* pos) { |
| 164 DCHECK(pos); |
| 165 *pos = next_available_byte_; |
| 166 |
| 167 base::CheckedNumeric<Position> next = next_available_byte_; |
| 168 next += size_bytes; |
| 169 if (!next.IsValid()) |
| 170 return false; |
| 171 |
| 172 next_available_byte_ += size_bytes; |
| 173 return true; |
| 174 } |
| 175 |
| 176 bool PostmortemMinidumpWriter::SeekCursor(Position destination) { |
| 177 // Validate the write does not extend past the allocated space. |
| 178 if (destination > next_available_byte_) |
| 179 return false; |
| 180 |
| 181 LARGE_INTEGER offset = {0}; |
| 182 offset.LowPart = destination; |
| 183 if (!::SetFilePointerEx(minidump_file_, offset, nullptr, FILE_BEGIN)) |
| 184 return false; |
| 185 |
| 186 cursor_ = destination; |
| 187 return true; |
| 188 } |
| 189 |
| 190 bool PostmortemMinidumpWriter::WriteBytes(Position pos, |
| 191 size_t size_bytes, |
| 192 const void* data) { |
| 193 DCHECK(data); |
| 194 DCHECK_NE(base::kInvalidPlatformFile, minidump_file_); |
| 195 DCHECK_EQ(cursor_, pos); |
| 196 |
| 197 // Validate the write does not extend past the next available byte. |
| 198 base::CheckedNumeric<Position> pos_end = pos; |
| 199 pos_end += size_bytes; |
| 200 if (!pos_end.IsValid() || pos_end.ValueOrDie() > next_available_byte_) |
| 201 return false; |
| 202 |
| 203 // Write. |
| 204 DWORD written_bytes = 0U; |
| 205 BOOL success = |
| 206 ::WriteFile(minidump_file_, data, size_bytes, &written_bytes, nullptr); |
| 207 if (!success || size_bytes != written_bytes) |
| 208 return false; |
| 209 |
| 210 // Advance cursor. |
| 211 cursor_ += size_bytes; |
| 212 return true; |
| 213 } |
| 214 |
| 215 bool PostmortemMinidumpWriter::AppendUtf8String(base::StringPiece data, |
| 216 Position* pos) { |
| 217 DCHECK(pos); |
| 218 uint32_t string_size = data.size(); |
| 219 if (!Append(string_size, pos)) |
| 220 return false; |
| 221 |
| 222 Position unused_pos = 0U; |
| 223 return AppendBytes(data, &unused_pos); |
| 224 } |
| 225 |
| 226 bool PostmortemMinidumpWriter::AppendBytes(base::StringPiece data, |
| 227 Position* pos) { |
| 228 DCHECK(pos); |
| 229 if (!Allocate(data.length(), pos)) |
| 230 return false; |
| 231 return WriteBytes(*pos, data.length(), data.data()); |
| 232 } |
| 233 |
| 234 void PostmortemMinidumpWriter::RegisterDirectoryEntry(uint32_t stream_type, |
| 235 Position pos, |
| 236 uint32_t size) { |
| 237 MINIDUMP_DIRECTORY entry = {0}; |
| 238 entry.StreamType = stream_type; |
| 239 entry.Location.Rva = pos; |
| 240 entry.Location.DataSize = size; |
| 241 directory_.push_back(entry); |
| 242 } |
| 243 |
| 244 } // namespace browser_watcher |
OLD | NEW |