Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(308)

Side by Side Diff: components/crash/content/app/fallback_crash_handler_win.cc

Issue 2611393002: Part two of fallback crash handler for Crashpad handler process. (Closed)
Patch Set: Now remove ALL unused directory entries, instead of ONE. Fix non-const casts to constness. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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 #include "components/crash/content/app/fallback_crash_handler_win.h"
6
7 #include <dbghelp.h>
8
9 #include <algorithm>
10 #include <map>
11 #include <vector>
12
13 #include "base/command_line.h"
14 #include "base/files/file.h"
15 #include "base/files/file_util.h"
16 #include "base/numerics/safe_conversions.h"
17 #include "base/process/process_handle.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/win/scoped_handle.h"
20 #include "base/win/win_util.h"
21 #include "third_party/crashpad/crashpad/client/crash_report_database.h"
22 #include "third_party/crashpad/crashpad/client/settings.h"
23 #include "third_party/crashpad/crashpad/minidump/minidump_extensions.h"
24
25 namespace crash_reporter {
26
27 namespace {
28
29 using FilePosition = uint32_t;
30 const FilePosition kInvalidFilePos = static_cast<FilePosition>(-1);
31
32 using StringStringMap = std::map<std::string, std::string>;
33
34 // This class is a helper to edit minidump files written by MiniDumpWriteDump.
35 // It assumes the minidump file it operates on has a directory entry pointing to
36 // a CrashpadInfo entry, which it updates to point to the SimpleDictionary data
37 // it appends to the file contents.
38 class MinidumpUpdater {
39 public:
40 MinidumpUpdater();
41
42 // Reads the existing directory from |file|.
43 bool Initialize(base::File* file);
44
45 // Appends the simple dictionary with |crash_keys| to the file, and updates
46 // the CrashpadInfo with its location.
47 bool AppendSimpleDictionary(const StringStringMap& crash_keys);
48
49 private:
50 // Writes |data_len| bytes from |data| to the file at the current location.
51 bool WriteData(const void* data, size_t data_len);
52 bool WriteAndAdvance(const void* data,
53 size_t data_len,
54 FilePosition* position);
55
56 base::File* file_;
57 std::vector<MINIDUMP_DIRECTORY> directory_;
58 };
59
60 MinidumpUpdater::MinidumpUpdater() : file_(nullptr) {}
61
62 bool MinidumpUpdater::Initialize(base::File* file) {
63 DCHECK(file && file->IsValid());
64 DCHECK(!file_);
65
66 // Read the file header.
67 MINIDUMP_HEADER header = {};
68 int bytes_read =
69 file->Read(0, reinterpret_cast<char*>(&header), sizeof(header));
70 if (bytes_read != sizeof(header))
71 return false;
72 if (header.Signature != MINIDUMP_SIGNATURE || header.NumberOfStreams == 0)
73 return false;
74
75 // Read the stream directory.
76 directory_.resize(header.NumberOfStreams);
77 int bytes_to_read = header.NumberOfStreams * sizeof(directory_[0]);
78 bytes_read =
79 file->Read(header.StreamDirectoryRva,
80 reinterpret_cast<char*>(&directory_[0]), bytes_to_read);
81 if (bytes_read != bytes_to_read)
82 return false;
83
84 // Crashpad has some fairly unreasonable checking on the minidump header and
85 // directory. Match with those checks for now to allow Crashpad to read the
86 // CrashpadInfo and upload these dumps.
87
88 // Start by removing any unused directory entries.
89 // TODO(siggi): Fix Crashpad to ignore unused streams.
90 directory_.erase(std::remove_if(directory_.begin(), directory_.end(),
91 [](const MINIDUMP_DIRECTORY& entry) {
92 return entry.StreamType == UnusedStream;
93 }),
94 directory_.end());
Sigurður Ásgeirsson 2017/01/12 21:36:41 Ugh :(.
95
96 // Update the header.
97 // TODO(siggi): Fix Crashpad's version checking.
98 header.Version = MINIDUMP_VERSION;
99 header.NumberOfStreams = base::saturated_cast<ULONG32>(directory_.size());
100
101 // Write back the potentially shortened and packed dictionary.
102 int bytes_to_write = header.NumberOfStreams * sizeof(directory_[0]);
103 int bytes_written = file->Write(header.StreamDirectoryRva,
104 reinterpret_cast<const char*>(&directory_[0]),
105 bytes_to_write);
106 if (bytes_written != bytes_to_write)
107 return false;
108
109 // Write back the header.
110 bytes_written =
111 file->Write(0, reinterpret_cast<const char*>(&header), sizeof(header));
112 if (bytes_written != sizeof(header))
113 return false;
114
115 // Success, stash the file.
116 file_ = file;
117
118 return true;
119 }
120
121 bool MinidumpUpdater::AppendSimpleDictionary(
122 const StringStringMap& crash_keys) {
123 DCHECK(file_);
124
125 // Start by finding the Crashpad directory entry and reading the CrashpadInfo.
126 FilePosition crashpad_info_pos = 0;
127 crashpad::MinidumpCrashpadInfo crashpad_info;
128 for (const auto& entry : directory_) {
129 if (entry.StreamType == crashpad::kMinidumpStreamTypeCrashpadInfo) {
130 // This file is freshly written, so it must contain the same version
131 // CrashpadInfo structure this code compiled against.
132 if (entry.Location.DataSize != sizeof(crashpad_info))
133 return false;
134
135 crashpad_info_pos = entry.Location.Rva;
136 break;
137 }
138 }
139
140 // No CrashpadInfo directory entry found.
141 if (crashpad_info_pos == 0)
142 return false;
143
144 int bytes_read =
145 file_->Read(crashpad_info_pos, reinterpret_cast<char*>(&crashpad_info),
146 sizeof(crashpad_info));
147 if (bytes_read != sizeof(crashpad_info))
148 return false;
149
150 if (crashpad_info.version != crashpad::MinidumpCrashpadInfo::kVersion)
151 return false;
152
153 // Seek to the tail of the file, where we're going to extend it.
154 FilePosition next_available_byte = file_->Seek(base::File::FROM_END, 0);
155 if (next_available_byte == kInvalidFilePos)
156 return false;
157
158 // Write the key/value pairs and collect their locations.
159 std::vector<crashpad::MinidumpSimpleStringDictionaryEntry> entries;
160 for (const auto& kv : crash_keys) {
161 crashpad::MinidumpSimpleStringDictionaryEntry entry = {0};
162
163 entry.key = next_available_byte;
164 uint32_t key_len = base::saturated_cast<uint32_t>(kv.first.size());
165 if (!WriteAndAdvance(&key_len, sizeof(key_len), &next_available_byte) ||
166 !WriteAndAdvance(&kv.first[0], key_len, &next_available_byte)) {
167 return false;
168 }
169
170 entry.value = next_available_byte;
171 uint32_t value_len = base::saturated_cast<uint32_t>(kv.second.size());
172 if (!WriteAndAdvance(&value_len, sizeof(value_len), &next_available_byte) ||
173 !WriteAndAdvance(&kv.second[0], value_len, &next_available_byte)) {
174 return false;
175 }
176
177 entries.push_back(entry);
178 }
179
180 // Write the dictionary array itself - note the array is count-prefixed.
181 FilePosition dict_pos = next_available_byte;
182 uint32_t entry_count = base::saturated_cast<uint32_t>(entries.size());
183 if (!WriteAndAdvance(&entry_count, sizeof(entry_count),
184 &next_available_byte) ||
185 !WriteAndAdvance(&entries[0], entry_count * sizeof(entries[0]),
186 &next_available_byte)) {
187 return false;
188 }
189
190 // Touch up the CrashpadInfo and write it back to the file.
191 crashpad_info.simple_annotations.DataSize = next_available_byte - dict_pos;
192 crashpad_info.simple_annotations.Rva = dict_pos;
193
194 int bytes_written = file_->Write(
195 crashpad_info_pos, reinterpret_cast<const char*>(&crashpad_info),
196 sizeof(crashpad_info));
197 if (bytes_written != sizeof(crashpad_info))
198 return false;
199
200 return true;
201 }
202
203 bool MinidumpUpdater::WriteData(const void* data, size_t data_len) {
204 DCHECK(file_);
205 DCHECK(data);
206 DCHECK_NE(0U, data_len);
207
208 if (data_len > INT_MAX)
209 return false;
210
211 int bytes_to_write = static_cast<int>(data_len);
212 int written_bytes = file_->WriteAtCurrentPos(
213 reinterpret_cast<const char*>(data), bytes_to_write);
214 if (written_bytes == -1)
215 return false;
216
217 return true;
218 }
219
220 bool MinidumpUpdater::WriteAndAdvance(const void* data,
221 size_t data_len,
222 FilePosition* position) {
223 DCHECK(position);
224 DCHECK_EQ(file_->Seek(base::File::FROM_CURRENT, 0), *position);
225
226 if (!WriteData(data, data_len))
227 return false;
228
229 *position += base::saturated_cast<FilePosition>(data_len);
230 return true;
231 }
232
233 // Writes a minidump file for |process| to |dump_file| with embedded
234 // CrashpadInfo, containing |crash_keys|, |client_id| and |report_id|.
235 // The |dump_file| must be open for read as well as write.
236 bool MiniDumpWriteDumpWithCrashpadInfo(const base::Process& process,
237 uint32_t minidump_type,
238 MINIDUMP_EXCEPTION_INFORMATION* exc_info,
239 const StringStringMap& crash_keys,
240 const crashpad::UUID& client_id,
241 const crashpad::UUID& report_id,
242 base::File* dump_file) {
243 DCHECK(process.IsValid());
244 DCHECK(exc_info);
245 DCHECK(dump_file && dump_file->IsValid());
246
247 // The CrashpadInfo structure and its associated directory entry are injected
248 // into the minidump, to minimize the work to patching up the dump.
249 crashpad::MinidumpCrashpadInfo crashpad_info;
250 crashpad_info.version = crashpad::MinidumpCrashpadInfo::kVersion;
251 crashpad_info.client_id = client_id;
252 crashpad_info.report_id = report_id;
253
254 MINIDUMP_USER_STREAM crashpad_info_stream = {
255 crashpad::kMinidumpStreamTypeCrashpadInfo, // Type
256 sizeof(crashpad_info), // BufferSize
257 &crashpad_info // Buffer
258 };
259 MINIDUMP_USER_STREAM_INFORMATION user_stream_info = {
260 1, // UserStreamCount
261 &crashpad_info_stream // UserStreamArray
262 };
263
264 // Write the minidump to the provided dump file.
265 if (!MiniDumpWriteDump(
266 process.Handle(), // Process handle.
267 process.Pid(), // Process Id.
268 dump_file->GetPlatformFile(), // File handle.
269 static_cast<MINIDUMP_TYPE>(minidump_type), // Minidump type.
270 exc_info, // Exception Param
271 &user_stream_info, // UserStreamParam,
272 nullptr)) { // CallbackParam
273 return false;
274 }
275
276 // Retouch the minidump to make it Crashpad compatible.
277 MinidumpUpdater updater;
278 if (!updater.Initialize(dump_file))
279 return false;
280 if (!updater.AppendSimpleDictionary(crash_keys))
281 return false;
282
283 return true;
284 }
285
286 // Appends the full contents of |source| to |dest| from the current position
287 // of |dest|.
288 bool AppendFileContents(base::File* source, base::PlatformFile dest) {
289 DCHECK(source && source->IsValid());
290 DCHECK_NE(base::kInvalidPlatformFile, dest);
291
292 // Rewind the source.
293 if (source->Seek(base::File::FROM_BEGIN, 0) == kInvalidFilePos)
294 return false;
295
296 std::vector<char> buf;
297 buf.resize(1024);
298 while (true) {
299 int bytes_read =
300 source->ReadAtCurrentPos(&buf[0], static_cast<int>(buf.size()));
301 if (bytes_read == -1)
302 return false;
303 if (bytes_read == 0)
304 break;
305
306 DWORD bytes_written = 0;
307 // Due to handle instrumentation, the destination can't be wrapped in
308 // a base::File, so we go basic Win32 API here.
309 if (!WriteFile(dest, &buf[0], bytes_read, &bytes_written, nullptr) ||
310 static_cast<int>(bytes_written) != bytes_read) {
311 return false;
312 }
313 }
314
315 return true;
316 }
317
318 } // namespace
319
320 FallbackCrashHandler::FallbackCrashHandler()
321 : thread_id_(base::kInvalidThreadId), exception_ptrs_(0UL) {}
322
323 FallbackCrashHandler::~FallbackCrashHandler() {}
324
325 bool FallbackCrashHandler::ParseCommandLine(const base::CommandLine& cmd_line) {
326 // Retrieve the handle to the process to dump.
327 unsigned int uint_process;
328 if (!base::StringToUint(cmd_line.GetSwitchValueASCII("process"),
329 &uint_process)) {
330 return false;
331 }
332
333 // Before taking ownership of the supposed handle, see whether it's really
334 // a process handle.
335 base::ProcessHandle process_handle = base::win::Uint32ToHandle(uint_process);
336 if (base::GetProcId(process_handle) == base::kNullProcessId)
337 return false;
338
339 // Retrieve the thread id argument.
340 unsigned thread_id = 0;
341 if (!base::StringToUint(cmd_line.GetSwitchValueASCII("thread"), &thread_id)) {
342 return false;
343 }
344
345 // Retrieve the "exception-pointers" argument.
346 uint64_t uint_exc_ptrs = 0;
347 if (!base::StringToUint64(cmd_line.GetSwitchValueASCII("exception-pointers"),
348 &uint_exc_ptrs)) {
349 return false;
350 }
351 exception_ptrs_ = static_cast<uintptr_t>(uint_exc_ptrs);
352
353 // Retrieve the "database" argument.
354 database_dir_ = cmd_line.GetSwitchValuePath("database");
355 if (database_dir_.empty())
356 return false;
357
358 // Everything checks out, take ownership of the process handle.
359 process_ = base::Process(process_handle);
360
361 return true;
362 }
363
364 bool FallbackCrashHandler::GenerateCrashDump(const std::string& product,
365 const std::string& version,
366 const std::string& channel,
367 const std::string& process_type) {
368 std::unique_ptr<crashpad::CrashReportDatabase> database =
369 crashpad::CrashReportDatabase::InitializeWithoutCreating(database_dir_);
370
371 if (!database)
372 return false;
373
374 crashpad::CrashReportDatabase::NewReport* report = nullptr;
375 crashpad::CrashReportDatabase::OperationStatus status =
376 database->PrepareNewCrashReport(&report);
377 if (status != crashpad::CrashReportDatabase::kNoError)
378 return false;
379
380 // Make sure we release the report on early exit.
381 crashpad::CrashReportDatabase::CallErrorWritingCrashReport on_error(
382 database.get(), report);
383
384 // TODO(siggi): Go big on the detail here for Canary/Dev channels.
385 const uint32_t kMinidumpType = MiniDumpWithUnloadedModules |
386 MiniDumpWithProcessThreadData |
387 MiniDumpWithThreadInfo;
388
389 MINIDUMP_EXCEPTION_INFORMATION exc_info = {};
390 exc_info.ThreadId = thread_id_;
391 exc_info.ExceptionPointers =
392 reinterpret_cast<EXCEPTION_POINTERS*>(exception_ptrs_);
393 exc_info.ClientPointers = TRUE; // ExceptionPointers in client.
394
395 // Mandatory crash keys. These will be read by Crashpad and used as
396 // http request parameters for the upload. Keys and values need to match
397 // server side configuration.
398 #if defined(ARCH_CPU_64_BITS)
399 const char* platform = "Win64";
400 #else
401 const char* platform = "Win32";
402 #endif
403 std::map<std::string, std::string> crash_keys = {{"prod", product},
404 {"ver", version},
405 {"channel", channel},
406 {"plat", platform},
407 {"ptype", process_type}};
408
409 crashpad::UUID client_id;
410 crashpad::Settings* settings = database->GetSettings();
411 if (settings) {
412 // If GetSettings() or GetClientID() fails client_id will be left at its
413 // default value, all zeroes, which is appropriate.
414 settings->GetClientID(&client_id);
415 }
416
417 base::FilePath dump_file_path;
418 if (!base::CreateTemporaryFile(&dump_file_path))
419 return false;
420
421 // Open the file with delete on close, to try and ensure it's cleaned up on
422 // any kind of failure.
423 base::File dump_file(dump_file_path, base::File::FLAG_OPEN |
424 base::File::FLAG_READ |
425 base::File::FLAG_WRITE |
426 base::File::FLAG_DELETE_ON_CLOSE);
427 if (!dump_file.IsValid())
428 return false;
429
430 // Write the minidump to the temp file, and then copy the data to the
431 // Crashpad-provided handle, as the latter is only open for write.
432 if (!MiniDumpWriteDumpWithCrashpadInfo(process_, kMinidumpType, &exc_info,
433 crash_keys, client_id, report->uuid,
434 &dump_file) ||
435 !AppendFileContents(&dump_file, report->handle)) {
436 return false;
437 }
438
439 on_error.Disarm();
440
441 crashpad::UUID report_id = {};
442 status = database->FinishedWritingCrashReport(report, &report_id);
443 if (status != crashpad::CrashReportDatabase::kNoError)
444 return false;
445
446 return true;
447 }
448
449 } // namespace crash_reporter
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698