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