| 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 SUCCESS = 0, |
| 106 FAILED = 1, |
| 107 FAILED_MISSING_PRODUCT_DETAILS = 2, |
| 108 WRITE_STATUS_MAX = 3 |
| 109 }; |
| 110 |
| 58 PostmortemMinidumpWriter(); | 111 PostmortemMinidumpWriter(); |
| 59 ~PostmortemMinidumpWriter(); | 112 ~PostmortemMinidumpWriter(); |
| 60 | 113 |
| 61 // Write to |minidump_file| a minimal minidump that wraps |report|. Returns | 114 // Write to |minidump_file| a minimal minidump that wraps |report|. Returns |
| 62 // true on success, false otherwise. | 115 // true on success, false otherwise. |
| 63 // Note: the caller owns |minidump_file| and is responsible for keeping it | 116 // 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 | 117 // valid for this object's lifetime. |minidump_file| is expected to be empty |
| 65 // and a binary stream. | 118 // and a binary stream. |
| 66 bool WriteDump(base::PlatformFile minidump_file, | 119 bool WriteDump(base::PlatformFile minidump_file, |
| 67 const StabilityReport& report, | 120 const crashpad::UUID& client_id, |
| 68 const MinidumpInfo& minidump_info); | 121 const crashpad::UUID& report_id, |
| 122 StabilityReport* report); |
| 69 | 123 |
| 70 private: | 124 private: |
| 71 // An offset within a minidump file. Note: using this type to avoid including | 125 // An offset within a minidump file. Note: using this type to avoid including |
| 72 // windows.h and relying on the RVA type. | 126 // windows.h and relying on the RVA type. |
| 73 using FilePosition = uint32_t; | 127 using FilePosition = uint32_t; |
| 74 | 128 |
| 75 // The minidump header is always located at the head. | 129 // The minidump header is always located at the head. |
| 76 static const FilePosition kHeaderPos = 0U; | 130 static const FilePosition kHeaderPos = 0U; |
| 77 | 131 |
| 78 bool WriteDumpImpl(const StabilityReport& report, | 132 bool WriteDumpImpl(const StabilityReport& report, |
| 79 const MinidumpInfo& minidump_info); | 133 const crashpad::UUID& client_id, |
| 134 const crashpad::UUID& report_id, |
| 135 const ProductDetails& product_details); |
| 80 | 136 |
| 81 bool AppendCrashpadInfo(const crashpad::UUID& client_id, | 137 bool AppendCrashpadInfo(const crashpad::UUID& client_id, |
| 82 const crashpad::UUID& report_id, | 138 const crashpad::UUID& report_id, |
| 83 const std::map<std::string, std::string>& crash_keys); | 139 const std::map<std::string, std::string>& crash_keys); |
| 84 | 140 |
| 85 bool AppendCrashpadDictionaryEntry( | 141 bool AppendCrashpadDictionaryEntry( |
| 86 const std::string& key, | 142 const std::string& key, |
| 87 const std::string& value, | 143 const std::string& value, |
| 88 std::vector<crashpad::MinidumpSimpleStringDictionaryEntry>* entries); | 144 std::vector<crashpad::MinidumpSimpleStringDictionaryEntry>* entries); |
| 89 | 145 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 | 177 |
| 122 // Storage for the directory during writes. | 178 // Storage for the directory during writes. |
| 123 std::vector<MINIDUMP_DIRECTORY> directory_; | 179 std::vector<MINIDUMP_DIRECTORY> directory_; |
| 124 | 180 |
| 125 // The file to write to. Only valid within the scope of a call to WriteDump. | 181 // The file to write to. Only valid within the scope of a call to WriteDump. |
| 126 base::File* minidump_file_; | 182 base::File* minidump_file_; |
| 127 | 183 |
| 128 DISALLOW_COPY_AND_ASSIGN(PostmortemMinidumpWriter); | 184 DISALLOW_COPY_AND_ASSIGN(PostmortemMinidumpWriter); |
| 129 }; | 185 }; |
| 130 | 186 |
| 187 // This function's purpose is to limit code / data size caused by uma macros. |
| 188 void RecordWriteDumpStatus(PostmortemMinidumpWriter::WriteStatus status) { |
| 189 UMA_HISTOGRAM_ENUMERATION("ActivityTracker.Collect.WriteDumpStatus", status, |
| 190 PostmortemMinidumpWriter::WRITE_STATUS_MAX); |
| 191 } |
| 192 |
| 131 PostmortemMinidumpWriter::PostmortemMinidumpWriter() | 193 PostmortemMinidumpWriter::PostmortemMinidumpWriter() |
| 132 : next_available_byte_(0U), minidump_file_(nullptr) {} | 194 : next_available_byte_(0U), minidump_file_(nullptr) {} |
| 133 | 195 |
| 134 PostmortemMinidumpWriter::~PostmortemMinidumpWriter() { | 196 PostmortemMinidumpWriter::~PostmortemMinidumpWriter() { |
| 135 DCHECK_EQ(nullptr, minidump_file_); | 197 DCHECK_EQ(nullptr, minidump_file_); |
| 136 } | 198 } |
| 137 | 199 |
| 138 bool PostmortemMinidumpWriter::WriteDump( | 200 bool PostmortemMinidumpWriter::WriteDump( |
| 139 base::PlatformFile minidump_platform_file, | 201 base::PlatformFile minidump_platform_file, |
| 140 const StabilityReport& report, | 202 const crashpad::UUID& client_id, |
| 141 const MinidumpInfo& minidump_info) { | 203 const crashpad::UUID& report_id, |
| 204 StabilityReport* report) { |
| 142 DCHECK_NE(base::kInvalidPlatformFile, minidump_platform_file); | 205 DCHECK_NE(base::kInvalidPlatformFile, minidump_platform_file); |
| 206 DCHECK(report); |
| 143 | 207 |
| 144 DCHECK_EQ(0U, next_available_byte_); | 208 DCHECK_EQ(0U, next_available_byte_); |
| 145 DCHECK(directory_.empty()); | 209 DCHECK(directory_.empty()); |
| 146 DCHECK_EQ(nullptr, minidump_file_); | 210 DCHECK_EQ(nullptr, minidump_file_); |
| 147 | 211 |
| 212 // Ensure the report contains the crasher's product details. |
| 213 ProductDetails product_details = {}; |
| 214 if (!GetProductDetails(report->global_data(), &product_details)) { |
| 215 // The report is missing the basic information to determine the affected |
| 216 // version. Ignore the report. |
| 217 RecordWriteDumpStatus(FAILED_MISSING_PRODUCT_DETAILS); |
| 218 return false; |
| 219 } |
| 220 |
| 221 // No need to keep the version details inside the report. |
| 222 report->mutable_global_data()->erase(kStabilityProduct); |
| 223 report->mutable_global_data()->erase(kStabilityChannel); |
| 224 report->mutable_global_data()->erase(kStabilityPlatform); |
| 225 report->mutable_global_data()->erase(kStabilityVersion); |
| 226 |
| 148 // We do not own |minidump_platform_file|, but we want to rely on base::File's | 227 // 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. | 228 // API, and so we need to duplicate it. |
| 150 HANDLE duplicated_handle; | 229 HANDLE duplicated_handle; |
| 151 BOOL duplicate_success = ::DuplicateHandle( | 230 BOOL duplicate_success = ::DuplicateHandle( |
| 152 ::GetCurrentProcess(), minidump_platform_file, ::GetCurrentProcess(), | 231 ::GetCurrentProcess(), minidump_platform_file, ::GetCurrentProcess(), |
| 153 &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS); | 232 &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS); |
| 154 if (!duplicate_success) | 233 if (!duplicate_success) { |
| 234 RecordWriteDumpStatus(FAILED); |
| 155 return false; | 235 return false; |
| 236 } |
| 156 base::File minidump_file(duplicated_handle); | 237 base::File minidump_file(duplicated_handle); |
| 157 DCHECK(minidump_file.IsValid()); | 238 DCHECK(minidump_file.IsValid()); |
| 158 minidump_file_ = &minidump_file; | 239 minidump_file_ = &minidump_file; |
| 159 DCHECK_EQ(0LL, GetFileOffset(minidump_file_)); | 240 DCHECK_EQ(0LL, GetFileOffset(minidump_file_)); |
| 160 DCHECK(IsFileEmpty(minidump_file_)); | 241 DCHECK(IsFileEmpty(minidump_file_)); |
| 161 | 242 |
| 162 // Write the minidump, then reset members. | 243 // Write the minidump, then reset members. |
| 163 bool success = WriteDumpImpl(report, minidump_info); | 244 bool success = WriteDumpImpl(*report, client_id, report_id, product_details); |
| 164 next_available_byte_ = 0U; | 245 next_available_byte_ = 0U; |
| 165 directory_.clear(); | 246 directory_.clear(); |
| 166 minidump_file_ = nullptr; | 247 minidump_file_ = nullptr; |
| 167 | 248 |
| 249 RecordWriteDumpStatus(success ? SUCCESS : FAILED); |
| 168 return success; | 250 return success; |
| 169 } | 251 } |
| 170 | 252 |
| 171 bool PostmortemMinidumpWriter::WriteDumpImpl( | 253 bool PostmortemMinidumpWriter::WriteDumpImpl( |
| 172 const StabilityReport& report, | 254 const StabilityReport& report, |
| 173 const MinidumpInfo& minidump_info) { | 255 const crashpad::UUID& client_id, |
| 256 const crashpad::UUID& report_id, |
| 257 const ProductDetails& product_details) { |
| 174 // Allocate space for the header and seek the cursor. | 258 // Allocate space for the header and seek the cursor. |
| 175 FilePosition pos = 0U; | 259 FilePosition pos = 0U; |
| 176 if (!Allocate(sizeof(MINIDUMP_HEADER), &pos)) | 260 if (!Allocate(sizeof(MINIDUMP_HEADER), &pos)) |
| 177 return false; | 261 return false; |
| 178 if (!SeekCursor(sizeof(MINIDUMP_HEADER))) | 262 if (!SeekCursor(sizeof(MINIDUMP_HEADER))) |
| 179 return false; | 263 return false; |
| 180 DCHECK_EQ(kHeaderPos, pos); | 264 DCHECK_EQ(kHeaderPos, pos); |
| 181 | 265 |
| 182 // Write the proto to the file. | 266 // Write the proto to the file. |
| 183 std::string serialized_report; | 267 std::string serialized_report; |
| 184 if (!report.SerializeToString(&serialized_report)) | 268 if (!report.SerializeToString(&serialized_report)) |
| 185 return false; | 269 return false; |
| 186 FilePosition report_pos = 0U; | 270 FilePosition report_pos = 0U; |
| 187 if (!AppendBytes(serialized_report, &report_pos)) | 271 if (!AppendBytes(serialized_report, &report_pos)) |
| 188 return false; | 272 return false; |
| 189 | 273 |
| 190 // The directory entry for the stability report's stream. | 274 // The directory entry for the stability report's stream. |
| 191 RegisterDirectoryEntry(kStabilityReportStreamType, report_pos, | 275 RegisterDirectoryEntry(kStabilityReportStreamType, report_pos, |
| 192 serialized_report.length()); | 276 serialized_report.length()); |
| 193 | 277 |
| 194 // Write mandatory crash keys. These will be read by crashpad and used as | 278 // 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 | 279 // http request parameters for the upload. Keys and values should match |
| 196 // server side configuration. | 280 // server side configuration. |
| 197 // TODO(manzagop): use product and version from the stability report. The | 281 // TODO(manzagop): use product and version from the stability report. The |
| 198 // current executable's values are an (imperfect) proxy. | 282 // current executable's values are an (imperfect) proxy. |
| 199 std::map<std::string, std::string> crash_keys = { | 283 std::map<std::string, std::string> crash_keys = { |
| 200 {"prod", minidump_info.product_name + "_Postmortem"}, | 284 {"prod", product_details.product + "_Postmortem"}, |
| 201 {"ver", minidump_info.version_number}, | 285 {"ver", product_details.version}, |
| 202 {"channel", minidump_info.channel_name}, | 286 {"channel", product_details.channel}, |
| 203 {"plat", minidump_info.platform}}; | 287 {"plat", product_details.platform}}; |
| 204 if (!AppendCrashpadInfo(minidump_info.client_id, minidump_info.report_id, | 288 if (!AppendCrashpadInfo(client_id, report_id, crash_keys)) |
| 205 crash_keys)) | |
| 206 return false; | 289 return false; |
| 207 | 290 |
| 208 // Write the directory. | 291 // Write the directory. |
| 209 FilePosition directory_pos = 0U; | 292 FilePosition directory_pos = 0U; |
| 210 if (!AppendVec(directory_, &directory_pos)) | 293 if (!AppendVec(directory_, &directory_pos)) |
| 211 return false; | 294 return false; |
| 212 | 295 |
| 213 // Write the header. | 296 // Write the header. |
| 214 MINIDUMP_HEADER header; | 297 MINIDUMP_HEADER header; |
| 215 header.Signature = MINIDUMP_SIGNATURE; | 298 header.Signature = MINIDUMP_SIGNATURE; |
| (...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 398 uint32_t size) { | 481 uint32_t size) { |
| 399 MINIDUMP_DIRECTORY entry = {0}; | 482 MINIDUMP_DIRECTORY entry = {0}; |
| 400 entry.StreamType = stream_type; | 483 entry.StreamType = stream_type; |
| 401 entry.Location.Rva = pos; | 484 entry.Location.Rva = pos; |
| 402 entry.Location.DataSize = size; | 485 entry.Location.DataSize = size; |
| 403 directory_.push_back(entry); | 486 directory_.push_back(entry); |
| 404 } | 487 } |
| 405 | 488 |
| 406 } // namespace | 489 } // namespace |
| 407 | 490 |
| 408 MinidumpInfo::MinidumpInfo() {} | 491 bool WritePostmortemDump(base::PlatformFile minidump_file, |
| 492 const crashpad::UUID& client_id, |
| 493 const crashpad::UUID& report_id, |
| 494 StabilityReport* report) { |
| 495 DCHECK(report); |
| 409 | 496 |
| 410 MinidumpInfo::~MinidumpInfo() {} | |
| 411 | |
| 412 bool WritePostmortemDump(base::PlatformFile minidump_file, | |
| 413 const StabilityReport& report, | |
| 414 const MinidumpInfo& minidump_info) { | |
| 415 PostmortemMinidumpWriter writer; | 497 PostmortemMinidumpWriter writer; |
| 416 return writer.WriteDump(minidump_file, report, minidump_info); | 498 return writer.WriteDump(minidump_file, client_id, report_id, report); |
| 417 } | 499 } |
| 418 | 500 |
| 419 } // namespace browser_watcher | 501 } // namespace browser_watcher |
| OLD | NEW |