Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 // | 4 // |
| 5 // Note: aside from using windows headers to obtain the definitions of minidump | 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 | 6 // structures, nothing here is windows specific. This seems like the best |
| 7 // approach given this code is for temporary experimentation on Windows. | 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 | 8 // Longer term, Crashpad will take over the minidump writing in this case as |
| 9 // well. | 9 // well. |
| 10 | 10 |
| 11 #include "components/browser_watcher/postmortem_minidump_writer.h" | 11 #include "components/browser_watcher/postmortem_minidump_writer.h" |
| 12 | 12 |
| 13 #include <windows.h> // NOLINT | 13 #include <windows.h> // NOLINT |
| 14 #include <dbghelp.h> | 14 #include <dbghelp.h> |
| 15 | 15 |
| 16 #include <map> | 16 #include <map> |
| 17 #include <type_traits> | 17 #include <type_traits> |
| 18 #include <vector> | 18 #include <vector> |
| 19 | 19 |
| 20 #include "base/files/file_util.h" | 20 #include "base/files/file_util.h" |
| 21 #include "base/macros.h" | 21 #include "base/macros.h" |
| 22 #include "base/metrics/histogram_macros.h" | |
| 22 #include "base/numerics/safe_math.h" | 23 #include "base/numerics/safe_math.h" |
| 23 #include "base/strings/string_piece.h" | 24 #include "base/strings/string_piece.h" |
| 25 #include "components/browser_watcher/stability_data_names.h" | |
| 24 #include "third_party/crashpad/crashpad/minidump/minidump_extensions.h" | 26 #include "third_party/crashpad/crashpad/minidump/minidump_extensions.h" |
| 25 | 27 |
| 26 namespace browser_watcher { | 28 namespace browser_watcher { |
| 27 | 29 |
| 28 namespace { | 30 namespace { |
| 29 | 31 |
| 30 // The stream type assigned to the minidump stream that holds the serialized | 32 // The stream type assigned to the minidump stream that holds the serialized |
| 31 // stability report. | 33 // stability report. |
| 32 // Note: the value was obtained by adding 1 to the stream type used for holding | 34 // Note: the value was obtained by adding 1 to the stream type used for holding |
| 33 // the SyzyAsan proto. | 35 // the SyzyAsan proto. |
| 34 // TODO(manzagop): centralize the stream type definitions to avoid issues. | 36 // TODO(manzagop): centralize the stream type definitions to avoid issues. |
| 35 const uint32_t kStabilityReportStreamType = 0x4B6B0002; | 37 const uint32_t kStabilityReportStreamType = 0x4B6B0002; |
| 36 | 38 |
| 39 struct ProductDetails { | |
| 40 std::string product; | |
| 41 std::string channel; | |
| 42 std::string platform; | |
| 43 std::string version; | |
| 44 }; | |
| 45 | |
| 46 bool GetStringFromTypedValueMap( | |
| 47 const google::protobuf::Map<std::string, TypedValue>& map, | |
| 48 const std::string& key, | |
| 49 std::string* out) { | |
| 50 DCHECK(out); | |
| 51 | |
| 52 auto it = map.find(key); | |
| 53 if (it == map.end()) | |
| 54 return false; | |
| 55 | |
| 56 const TypedValue& value = it->second; | |
| 57 if (value.value_case() != TypedValue::kStringValue) | |
| 58 return false; | |
| 59 | |
| 60 *out = value.string_value(); | |
| 61 return true; | |
| 62 } | |
| 63 | |
| 64 bool GetProductDetails( | |
| 65 const google::protobuf::Map<std::string, TypedValue>& global_data, | |
| 66 ProductDetails* product_details) { | |
| 67 DCHECK(product_details); | |
| 68 | |
| 69 if (!GetStringFromTypedValueMap(global_data, kStabilityProduct, | |
| 70 &(product_details->product))) | |
| 71 return false; | |
| 72 if (!GetStringFromTypedValueMap(global_data, kStabilityChannel, | |
| 73 &(product_details->channel))) | |
| 74 return false; | |
| 75 if (!GetStringFromTypedValueMap(global_data, kStabilityPlatform, | |
| 76 &(product_details->platform))) | |
| 77 return false; | |
| 78 return GetStringFromTypedValueMap(global_data, kStabilityVersion, | |
| 79 &(product_details->version)); | |
| 80 } | |
| 81 | |
| 37 int64_t GetFileOffset(base::File* file) { | 82 int64_t GetFileOffset(base::File* file) { |
| 38 DCHECK(file); | 83 DCHECK(file); |
| 39 return file->Seek(base::File::FROM_CURRENT, 0LL); | 84 return file->Seek(base::File::FROM_CURRENT, 0LL); |
| 40 } | 85 } |
| 41 | 86 |
| 42 // Returns true if the file is empty, and false if the file is not empty or if | 87 // Returns true if the file is empty, and false if the file is not empty or if |
| 43 // there is an error. | 88 // there is an error. |
| 44 bool IsFileEmpty(base::File* file) { | 89 bool IsFileEmpty(base::File* file) { |
| 45 DCHECK(file); | 90 DCHECK(file); |
| 46 int64_t end = file->Seek(base::File::FROM_END, 0LL); | 91 int64_t end = file->Seek(base::File::FROM_END, 0LL); |
| 47 return end == 0LL; | 92 return end == 0LL; |
| 48 } | 93 } |
| 49 | 94 |
| 50 // A class with functionality for writing minimal minidump containers to wrap | 95 // A class with functionality for writing minimal minidump containers to wrap |
| 51 // postmortem stability reports. | 96 // postmortem stability reports. |
| 52 // TODO(manzagop): remove this class once Crashpad takes over writing postmortem | 97 // TODO(manzagop): remove this class once Crashpad takes over writing postmortem |
| 53 // minidumps. | 98 // minidumps. |
| 54 // TODO(manzagop): revisit where the module information should be transported, | 99 // TODO(manzagop): revisit where the module information should be transported, |
| 55 // in the protocol buffer or in a module stream. | 100 // in the protocol buffer or in a module stream. |
| 56 class PostmortemMinidumpWriter { | 101 class PostmortemMinidumpWriter { |
| 57 public: | 102 public: |
| 103 // DO NOT CHANGE VALUES. This is logged persistently in a histogram. | |
| 104 enum WriteStatus { | |
| 105 NONE = 0, | |
|
bcwhite
2017/02/13 18:35:07
Omit NONE if you never send it.
manzagop (departed)
2017/02/13 19:22:36
Done.
| |
| 106 SUCCESS = 1, | |
| 107 FAILED = 2, | |
| 108 FAILED_MISSING_PRODUCT_DETAILS = 3, | |
| 109 WRITE_STATUS_MAX = 4 | |
| 110 }; | |
| 111 | |
| 58 PostmortemMinidumpWriter(); | 112 PostmortemMinidumpWriter(); |
| 59 ~PostmortemMinidumpWriter(); | 113 ~PostmortemMinidumpWriter(); |
| 60 | 114 |
| 61 // Write to |minidump_file| a minimal minidump that wraps |report|. Returns | 115 // Write to |minidump_file| a minimal minidump that wraps |report|. Returns |
| 62 // true on success, false otherwise. | 116 // true on success, false otherwise. |
| 63 // Note: the caller owns |minidump_file| and is responsible for keeping it | 117 // Note: the caller owns |minidump_file| and is responsible for keeping it |
| 64 // valid for this object's lifetime. |minidump_file| is expected to be empty | 118 // valid for this object's lifetime. |minidump_file| is expected to be empty |
| 65 // and a binary stream. | 119 // and a binary stream. |
| 66 bool WriteDump(base::PlatformFile minidump_file, | 120 bool WriteDump(base::PlatformFile minidump_file, |
| 67 const StabilityReport& report, | 121 const crashpad::UUID& client_id, |
| 68 const MinidumpInfo& minidump_info); | 122 const crashpad::UUID& report_id, |
| 123 StabilityReport* report); | |
| 69 | 124 |
| 70 private: | 125 private: |
| 71 // An offset within a minidump file. Note: using this type to avoid including | 126 // An offset within a minidump file. Note: using this type to avoid including |
| 72 // windows.h and relying on the RVA type. | 127 // windows.h and relying on the RVA type. |
| 73 using FilePosition = uint32_t; | 128 using FilePosition = uint32_t; |
| 74 | 129 |
| 75 // The minidump header is always located at the head. | 130 // The minidump header is always located at the head. |
| 76 static const FilePosition kHeaderPos = 0U; | 131 static const FilePosition kHeaderPos = 0U; |
| 77 | 132 |
| 78 bool WriteDumpImpl(const StabilityReport& report, | 133 bool WriteDumpImpl(const StabilityReport& report, |
| 79 const MinidumpInfo& minidump_info); | 134 const crashpad::UUID& client_id, |
| 135 const crashpad::UUID& report_id, | |
| 136 const ProductDetails& product_details); | |
| 80 | 137 |
| 81 bool AppendCrashpadInfo(const crashpad::UUID& client_id, | 138 bool AppendCrashpadInfo(const crashpad::UUID& client_id, |
| 82 const crashpad::UUID& report_id, | 139 const crashpad::UUID& report_id, |
| 83 const std::map<std::string, std::string>& crash_keys); | 140 const std::map<std::string, std::string>& crash_keys); |
| 84 | 141 |
| 85 bool AppendCrashpadDictionaryEntry( | 142 bool AppendCrashpadDictionaryEntry( |
| 86 const std::string& key, | 143 const std::string& key, |
| 87 const std::string& value, | 144 const std::string& value, |
| 88 std::vector<crashpad::MinidumpSimpleStringDictionaryEntry>* entries); | 145 std::vector<crashpad::MinidumpSimpleStringDictionaryEntry>* entries); |
| 89 | 146 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 130 | 187 |
| 131 PostmortemMinidumpWriter::PostmortemMinidumpWriter() | 188 PostmortemMinidumpWriter::PostmortemMinidumpWriter() |
| 132 : next_available_byte_(0U), minidump_file_(nullptr) {} | 189 : next_available_byte_(0U), minidump_file_(nullptr) {} |
| 133 | 190 |
| 134 PostmortemMinidumpWriter::~PostmortemMinidumpWriter() { | 191 PostmortemMinidumpWriter::~PostmortemMinidumpWriter() { |
| 135 DCHECK_EQ(nullptr, minidump_file_); | 192 DCHECK_EQ(nullptr, minidump_file_); |
| 136 } | 193 } |
| 137 | 194 |
| 138 bool PostmortemMinidumpWriter::WriteDump( | 195 bool PostmortemMinidumpWriter::WriteDump( |
| 139 base::PlatformFile minidump_platform_file, | 196 base::PlatformFile minidump_platform_file, |
| 140 const StabilityReport& report, | 197 const crashpad::UUID& client_id, |
| 141 const MinidumpInfo& minidump_info) { | 198 const crashpad::UUID& report_id, |
| 199 StabilityReport* report) { | |
| 142 DCHECK_NE(base::kInvalidPlatformFile, minidump_platform_file); | 200 DCHECK_NE(base::kInvalidPlatformFile, minidump_platform_file); |
| 201 DCHECK(report); | |
| 143 | 202 |
| 144 DCHECK_EQ(0U, next_available_byte_); | 203 DCHECK_EQ(0U, next_available_byte_); |
| 145 DCHECK(directory_.empty()); | 204 DCHECK(directory_.empty()); |
| 146 DCHECK_EQ(nullptr, minidump_file_); | 205 DCHECK_EQ(nullptr, minidump_file_); |
| 147 | 206 |
| 207 // Ensure the report contains the crasher's product details. | |
| 208 ProductDetails product_details = {}; | |
| 209 if (!GetProductDetails(report->global_data(), &product_details)) { | |
| 210 // The report is missing the basic information to determine the affected | |
| 211 // version. Ignore the report. | |
| 212 UMA_HISTOGRAM_ENUMERATION("ActivityTracker.Collect.WriteDumpStatus", | |
| 213 FAILED_MISSING_PRODUCT_DETAILS, WRITE_STATUS_MAX); | |
| 214 return false; | |
| 215 } | |
| 216 | |
| 217 // No need to keep the version details inside the report. | |
| 218 report->mutable_global_data()->erase(kStabilityProduct); | |
| 219 report->mutable_global_data()->erase(kStabilityChannel); | |
| 220 report->mutable_global_data()->erase(kStabilityPlatform); | |
| 221 report->mutable_global_data()->erase(kStabilityVersion); | |
| 222 | |
| 148 // We do not own |minidump_platform_file|, but we want to rely on base::File's | 223 // We do not own |minidump_platform_file|, but we want to rely on base::File's |
| 149 // API, and so we need to duplicate it. | 224 // API, and so we need to duplicate it. |
| 150 HANDLE duplicated_handle; | 225 HANDLE duplicated_handle; |
| 151 BOOL duplicate_success = ::DuplicateHandle( | 226 BOOL duplicate_success = ::DuplicateHandle( |
| 152 ::GetCurrentProcess(), minidump_platform_file, ::GetCurrentProcess(), | 227 ::GetCurrentProcess(), minidump_platform_file, ::GetCurrentProcess(), |
| 153 &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS); | 228 &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS); |
| 154 if (!duplicate_success) | 229 if (!duplicate_success) { |
| 230 UMA_HISTOGRAM_ENUMERATION("ActivityTracker.Collect.WriteDumpStatus", FAILED, | |
|
bcwhite
2017/02/13 18:35:07
To avoid duplicate histogram instantiation code, r
manzagop (departed)
2017/02/13 19:22:36
Ah right! Went with the latter, to limit indentati
| |
| 231 WRITE_STATUS_MAX); | |
| 155 return false; | 232 return false; |
| 233 } | |
| 156 base::File minidump_file(duplicated_handle); | 234 base::File minidump_file(duplicated_handle); |
| 157 DCHECK(minidump_file.IsValid()); | 235 DCHECK(minidump_file.IsValid()); |
| 158 minidump_file_ = &minidump_file; | 236 minidump_file_ = &minidump_file; |
| 159 DCHECK_EQ(0LL, GetFileOffset(minidump_file_)); | 237 DCHECK_EQ(0LL, GetFileOffset(minidump_file_)); |
| 160 DCHECK(IsFileEmpty(minidump_file_)); | 238 DCHECK(IsFileEmpty(minidump_file_)); |
| 161 | 239 |
| 162 // Write the minidump, then reset members. | 240 // Write the minidump, then reset members. |
| 163 bool success = WriteDumpImpl(report, minidump_info); | 241 bool success = WriteDumpImpl(*report, client_id, report_id, product_details); |
| 164 next_available_byte_ = 0U; | 242 next_available_byte_ = 0U; |
| 165 directory_.clear(); | 243 directory_.clear(); |
| 166 minidump_file_ = nullptr; | 244 minidump_file_ = nullptr; |
| 167 | 245 |
| 246 WriteStatus status = success ? SUCCESS : FAILED; | |
|
bcwhite
2017/02/13 18:35:07
Move this inline below.
manzagop (departed)
2017/02/13 19:22:36
Done.
| |
| 247 UMA_HISTOGRAM_ENUMERATION("ActivityTracker.Collect.WriteDumpStatus", status, | |
| 248 WRITE_STATUS_MAX); | |
| 168 return success; | 249 return success; |
| 169 } | 250 } |
| 170 | 251 |
| 171 bool PostmortemMinidumpWriter::WriteDumpImpl( | 252 bool PostmortemMinidumpWriter::WriteDumpImpl( |
| 172 const StabilityReport& report, | 253 const StabilityReport& report, |
| 173 const MinidumpInfo& minidump_info) { | 254 const crashpad::UUID& client_id, |
| 255 const crashpad::UUID& report_id, | |
| 256 const ProductDetails& product_details) { | |
| 174 // Allocate space for the header and seek the cursor. | 257 // Allocate space for the header and seek the cursor. |
| 175 FilePosition pos = 0U; | 258 FilePosition pos = 0U; |
| 176 if (!Allocate(sizeof(MINIDUMP_HEADER), &pos)) | 259 if (!Allocate(sizeof(MINIDUMP_HEADER), &pos)) |
| 177 return false; | 260 return false; |
| 178 if (!SeekCursor(sizeof(MINIDUMP_HEADER))) | 261 if (!SeekCursor(sizeof(MINIDUMP_HEADER))) |
| 179 return false; | 262 return false; |
| 180 DCHECK_EQ(kHeaderPos, pos); | 263 DCHECK_EQ(kHeaderPos, pos); |
| 181 | 264 |
| 182 // Write the proto to the file. | 265 // Write the proto to the file. |
| 183 std::string serialized_report; | 266 std::string serialized_report; |
| 184 if (!report.SerializeToString(&serialized_report)) | 267 if (!report.SerializeToString(&serialized_report)) |
| 185 return false; | 268 return false; |
| 186 FilePosition report_pos = 0U; | 269 FilePosition report_pos = 0U; |
| 187 if (!AppendBytes(serialized_report, &report_pos)) | 270 if (!AppendBytes(serialized_report, &report_pos)) |
| 188 return false; | 271 return false; |
| 189 | 272 |
| 190 // The directory entry for the stability report's stream. | 273 // The directory entry for the stability report's stream. |
| 191 RegisterDirectoryEntry(kStabilityReportStreamType, report_pos, | 274 RegisterDirectoryEntry(kStabilityReportStreamType, report_pos, |
| 192 serialized_report.length()); | 275 serialized_report.length()); |
| 193 | 276 |
| 194 // Write mandatory crash keys. These will be read by crashpad and used as | 277 // Write mandatory crash keys. These will be read by crashpad and used as |
| 195 // http request parameters for the upload. Keys and values should match | 278 // http request parameters for the upload. Keys and values should match |
| 196 // server side configuration. | 279 // server side configuration. |
| 197 // TODO(manzagop): use product and version from the stability report. The | 280 // TODO(manzagop): use product and version from the stability report. The |
| 198 // current executable's values are an (imperfect) proxy. | 281 // current executable's values are an (imperfect) proxy. |
| 199 std::map<std::string, std::string> crash_keys = { | 282 std::map<std::string, std::string> crash_keys = { |
| 200 {"prod", minidump_info.product_name + "_Postmortem"}, | 283 {"prod", product_details.product + "_Postmortem"}, |
| 201 {"ver", minidump_info.version_number}, | 284 {"ver", product_details.version}, |
| 202 {"channel", minidump_info.channel_name}, | 285 {"channel", product_details.channel}, |
| 203 {"plat", minidump_info.platform}}; | 286 {"plat", product_details.platform}}; |
| 204 if (!AppendCrashpadInfo(minidump_info.client_id, minidump_info.report_id, | 287 if (!AppendCrashpadInfo(client_id, report_id, crash_keys)) |
| 205 crash_keys)) | |
| 206 return false; | 288 return false; |
| 207 | 289 |
| 208 // Write the directory. | 290 // Write the directory. |
| 209 FilePosition directory_pos = 0U; | 291 FilePosition directory_pos = 0U; |
| 210 if (!AppendVec(directory_, &directory_pos)) | 292 if (!AppendVec(directory_, &directory_pos)) |
| 211 return false; | 293 return false; |
| 212 | 294 |
| 213 // Write the header. | 295 // Write the header. |
| 214 MINIDUMP_HEADER header; | 296 MINIDUMP_HEADER header; |
| 215 header.Signature = MINIDUMP_SIGNATURE; | 297 header.Signature = MINIDUMP_SIGNATURE; |
| (...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 398 uint32_t size) { | 480 uint32_t size) { |
| 399 MINIDUMP_DIRECTORY entry = {0}; | 481 MINIDUMP_DIRECTORY entry = {0}; |
| 400 entry.StreamType = stream_type; | 482 entry.StreamType = stream_type; |
| 401 entry.Location.Rva = pos; | 483 entry.Location.Rva = pos; |
| 402 entry.Location.DataSize = size; | 484 entry.Location.DataSize = size; |
| 403 directory_.push_back(entry); | 485 directory_.push_back(entry); |
| 404 } | 486 } |
| 405 | 487 |
| 406 } // namespace | 488 } // namespace |
| 407 | 489 |
| 408 MinidumpInfo::MinidumpInfo() {} | 490 bool WritePostmortemDump(base::PlatformFile minidump_file, |
| 491 const crashpad::UUID& client_id, | |
| 492 const crashpad::UUID& report_id, | |
| 493 StabilityReport* report) { | |
| 494 DCHECK(report); | |
| 409 | 495 |
| 410 MinidumpInfo::~MinidumpInfo() {} | |
| 411 | |
| 412 bool WritePostmortemDump(base::PlatformFile minidump_file, | |
| 413 const StabilityReport& report, | |
| 414 const MinidumpInfo& minidump_info) { | |
| 415 PostmortemMinidumpWriter writer; | 496 PostmortemMinidumpWriter writer; |
| 416 return writer.WriteDump(minidump_file, report, minidump_info); | 497 return writer.WriteDump(minidump_file, client_id, report_id, report); |
| 417 } | 498 } |
| 418 | 499 |
| 419 } // namespace browser_watcher | 500 } // namespace browser_watcher |
| OLD | NEW |