summaryrefslogtreecommitdiffstats
path: root/components/crash/content/tools
diff options
context:
space:
mode:
Diffstat (limited to 'components/crash/content/tools')
-rw-r--r--components/crash/content/tools/BUILD.gn18
-rw-r--r--components/crash/content/tools/crash_service.cc487
-rw-r--r--components/crash/content/tools/crash_service.h125
-rwxr-xr-xcomponents/crash/content/tools/dmp2minidump.py51
-rwxr-xr-xcomponents/crash/content/tools/generate_breakpad_symbols.py262
5 files changed, 943 insertions, 0 deletions
diff --git a/components/crash/content/tools/BUILD.gn b/components/crash/content/tools/BUILD.gn
new file mode 100644
index 0000000..82f282b
--- /dev/null
+++ b/components/crash/content/tools/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (is_win) {
+ source_set("crash_service") {
+ sources = [
+ "crash_service.cc",
+ "crash_service.h",
+ ]
+
+ deps = [
+ "//base",
+ "//breakpad:breakpad_handler",
+ "//breakpad:breakpad_sender",
+ ]
+ }
+}
diff --git a/components/crash/content/tools/crash_service.cc b/components/crash/content/tools/crash_service.cc
new file mode 100644
index 0000000..ed55bbf
--- /dev/null
+++ b/components/crash/content/tools/crash_service.cc
@@ -0,0 +1,487 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crash/content/tools/crash_service.h"
+
+#include <windows.h>
+
+#include <sddl.h>
+#include <fstream>
+#include <map>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/win/windows_version.h"
+#include "breakpad/src/client/windows/crash_generation/client_info.h"
+#include "breakpad/src/client/windows/crash_generation/crash_generation_server.h"
+#include "breakpad/src/client/windows/sender/crash_report_sender.h"
+
+namespace breakpad {
+
+namespace {
+
+const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
+
+const wchar_t kCrashReportURL[] = L"https://clients2.google.com/cr/report";
+const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt";
+
+typedef std::map<std::wstring, std::wstring> CrashMap;
+
+bool CustomInfoToMap(const google_breakpad::ClientInfo* client_info,
+ const std::wstring& reporter_tag, CrashMap* map) {
+ google_breakpad::CustomClientInfo info = client_info->GetCustomInfo();
+
+ for (uintptr_t i = 0; i < info.count; ++i) {
+ (*map)[info.entries[i].name] = info.entries[i].value;
+ }
+
+ (*map)[L"rept"] = reporter_tag;
+
+ return !map->empty();
+}
+
+bool WriteCustomInfoToFile(const std::wstring& dump_path, const CrashMap& map) {
+ std::wstring file_path(dump_path);
+ size_t last_dot = file_path.rfind(L'.');
+ if (last_dot == std::wstring::npos)
+ return false;
+ file_path.resize(last_dot);
+ file_path += L".txt";
+
+ std::wofstream file(file_path.c_str(),
+ std::ios_base::out | std::ios_base::app | std::ios::binary);
+ if (!file.is_open())
+ return false;
+
+ CrashMap::const_iterator pos;
+ for (pos = map.begin(); pos != map.end(); ++pos) {
+ std::wstring line = pos->first;
+ line += L':';
+ line += pos->second;
+ line += L'\n';
+ file.write(line.c_str(), static_cast<std::streamsize>(line.length()));
+ }
+ return true;
+}
+
+// The window procedure task is to handle when a) the user logs off.
+// b) the system shuts down or c) when the user closes the window.
+LRESULT __stdcall CrashSvcWndProc(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam) {
+ switch (message) {
+ case WM_CLOSE:
+ case WM_ENDSESSION:
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ break;
+ default:
+ return DefWindowProc(hwnd, message, wparam, lparam);
+ }
+ return 0;
+}
+
+// This is the main and only application window.
+HWND g_top_window = NULL;
+
+bool CreateTopWindow(HINSTANCE instance, bool visible) {
+ WNDCLASSEXW wcx = {0};
+ wcx.cbSize = sizeof(wcx);
+ wcx.style = CS_HREDRAW | CS_VREDRAW;
+ wcx.lpfnWndProc = CrashSvcWndProc;
+ wcx.hInstance = instance;
+ wcx.lpszClassName = L"crash_svc_class";
+ ::RegisterClassExW(&wcx);
+ DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED;
+
+ // The window size is zero but being a popup window still shows in the
+ // task bar and can be closed using the system menu or using task manager.
+ HWND window = CreateWindowExW(0, wcx.lpszClassName, L"crash service", style,
+ CW_USEDEFAULT, CW_USEDEFAULT, 0, 0,
+ NULL, NULL, instance, NULL);
+ if (!window)
+ return false;
+
+ ::UpdateWindow(window);
+ VLOG(1) << "window handle is " << window;
+ g_top_window = window;
+ return true;
+}
+
+// Simple helper class to keep the process alive until the current request
+// finishes.
+class ProcessingLock {
+ public:
+ ProcessingLock() {
+ ::InterlockedIncrement(&op_count_);
+ }
+ ~ProcessingLock() {
+ ::InterlockedDecrement(&op_count_);
+ }
+ static bool IsWorking() {
+ return (op_count_ != 0);
+ }
+ private:
+ static volatile LONG op_count_;
+};
+
+volatile LONG ProcessingLock::op_count_ = 0;
+
+// This structure contains the information that the worker thread needs to
+// send a crash dump to the server.
+struct DumpJobInfo {
+ DWORD pid;
+ CrashService* self;
+ CrashMap map;
+ std::wstring dump_path;
+
+ DumpJobInfo(DWORD process_id, CrashService* service,
+ const CrashMap& crash_map, const std::wstring& path)
+ : pid(process_id), self(service), map(crash_map), dump_path(path) {
+ }
+};
+
+} // namespace
+
+// Command line switches:
+const char CrashService::kMaxReports[] = "max-reports";
+const char CrashService::kNoWindow[] = "no-window";
+const char CrashService::kReporterTag[] = "reporter";
+const char CrashService::kDumpsDir[] = "dumps-dir";
+const char CrashService::kPipeName[] = "pipe-name";
+
+CrashService::CrashService()
+ : dumper_(NULL),
+ sender_(NULL),
+ requests_handled_(0),
+ requests_sent_(0),
+ clients_connected_(0),
+ clients_terminated_(0) {
+}
+
+CrashService::~CrashService() {
+ base::AutoLock lock(sending_);
+ delete dumper_;
+ delete sender_;
+}
+
+bool CrashService::Initialize(const base::FilePath& operating_dir,
+ const base::FilePath& dumps_path) {
+ using google_breakpad::CrashReportSender;
+ using google_breakpad::CrashGenerationServer;
+
+ std::wstring pipe_name = kTestPipeName;
+ int max_reports = -1;
+
+ // The checkpoint file allows CrashReportSender to enforce the the maximum
+ // reports per day quota. Does not seem to serve any other purpose.
+ base::FilePath checkpoint_path = operating_dir.Append(kCheckPointFile);
+
+ base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
+
+ base::FilePath dumps_path_to_use = dumps_path;
+
+ if (cmd_line.HasSwitch(kDumpsDir)) {
+ dumps_path_to_use =
+ base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir));
+ }
+
+ // We can override the send reports quota with a command line switch.
+ if (cmd_line.HasSwitch(kMaxReports))
+ max_reports = _wtoi(cmd_line.GetSwitchValueNative(kMaxReports).c_str());
+
+ // Allow the global pipe name to be overridden for better testability.
+ if (cmd_line.HasSwitch(kPipeName))
+ pipe_name = cmd_line.GetSwitchValueNative(kPipeName);
+
+#ifdef _WIN64
+ pipe_name += L"-x64";
+#endif
+
+ if (max_reports > 0) {
+ // Create the http sender object.
+ sender_ = new CrashReportSender(checkpoint_path.value());
+ sender_->set_max_reports_per_day(max_reports);
+ }
+
+ SECURITY_ATTRIBUTES security_attributes = {0};
+ SECURITY_ATTRIBUTES* security_attributes_actual = NULL;
+
+ if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
+ SECURITY_DESCRIPTOR* security_descriptor =
+ reinterpret_cast<SECURITY_DESCRIPTOR*>(
+ GetSecurityDescriptorForLowIntegrity());
+ DCHECK(security_descriptor != NULL);
+
+ security_attributes.nLength = sizeof(security_attributes);
+ security_attributes.lpSecurityDescriptor = security_descriptor;
+ security_attributes.bInheritHandle = FALSE;
+
+ security_attributes_actual = &security_attributes;
+ }
+
+ // Create the OOP crash generator object.
+ dumper_ = new CrashGenerationServer(pipe_name, security_attributes_actual,
+ &CrashService::OnClientConnected, this,
+ &CrashService::OnClientDumpRequest, this,
+ &CrashService::OnClientExited, this,
+ NULL, NULL,
+ true, &dumps_path_to_use.value());
+
+ if (!dumper_) {
+ LOG(ERROR) << "could not create dumper";
+ if (security_attributes.lpSecurityDescriptor)
+ LocalFree(security_attributes.lpSecurityDescriptor);
+ return false;
+ }
+
+ if (!CreateTopWindow(::GetModuleHandleW(NULL),
+ !cmd_line.HasSwitch(kNoWindow))) {
+ LOG(ERROR) << "could not create window";
+ if (security_attributes.lpSecurityDescriptor)
+ LocalFree(security_attributes.lpSecurityDescriptor);
+ return false;
+ }
+
+ reporter_tag_ = L"crash svc";
+ if (cmd_line.HasSwitch(kReporterTag))
+ reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag);
+
+ // Log basic information.
+ VLOG(1) << "pipe name is " << pipe_name
+ << "\ndumps at " << dumps_path_to_use.value();
+
+ if (sender_) {
+ VLOG(1) << "checkpoint is " << checkpoint_path.value()
+ << "\nserver is " << kCrashReportURL
+ << "\nmaximum " << sender_->max_reports_per_day() << " reports/day"
+ << "\nreporter is " << reporter_tag_;
+ }
+ // Start servicing clients.
+ if (!dumper_->Start()) {
+ LOG(ERROR) << "could not start dumper";
+ if (security_attributes.lpSecurityDescriptor)
+ LocalFree(security_attributes.lpSecurityDescriptor);
+ return false;
+ }
+
+ if (security_attributes.lpSecurityDescriptor)
+ LocalFree(security_attributes.lpSecurityDescriptor);
+
+ // This is throwaway code. We don't need to sync with the browser process
+ // once Google Update is updated to a version supporting OOP crash handling.
+ // Create or open an event to signal the browser process that the crash
+ // service is initialized.
+ HANDLE running_event =
+ ::CreateEventW(NULL, TRUE, TRUE, L"g_chrome_crash_svc");
+ // If the browser already had the event open, the CreateEvent call did not
+ // signal it. We need to do it manually.
+ ::SetEvent(running_event);
+
+ return true;
+}
+
+void CrashService::OnClientConnected(void* context,
+ const google_breakpad::ClientInfo* client_info) {
+ ProcessingLock lock;
+ VLOG(1) << "client start. pid = " << client_info->pid();
+ CrashService* self = static_cast<CrashService*>(context);
+ ::InterlockedIncrement(&self->clients_connected_);
+}
+
+void CrashService::OnClientExited(void* context,
+ const google_breakpad::ClientInfo* client_info) {
+ ProcessingLock lock;
+ VLOG(1) << "client end. pid = " << client_info->pid();
+ CrashService* self = static_cast<CrashService*>(context);
+ ::InterlockedIncrement(&self->clients_terminated_);
+
+ if (!self->sender_)
+ return;
+
+ // When we are instructed to send reports we need to exit if there are
+ // no more clients to service. The next client that runs will start us.
+ // Only chrome.exe starts crash_service with a non-zero max_reports.
+ if (self->clients_connected_ > self->clients_terminated_)
+ return;
+ if (self->sender_->max_reports_per_day() > 0) {
+ // Wait for the other thread to send crashes, if applicable. The sender
+ // thread takes the sending_ lock, so the sleep is just to give it a
+ // chance to start.
+ ::Sleep(1000);
+ base::AutoLock lock(self->sending_);
+ // Some people can restart chrome very fast, check again if we have
+ // a new client before exiting for real.
+ if (self->clients_connected_ == self->clients_terminated_) {
+ VLOG(1) << "zero clients. exiting";
+ ::PostMessage(g_top_window, WM_CLOSE, 0, 0);
+ }
+ }
+}
+
+void CrashService::OnClientDumpRequest(void* context,
+ const google_breakpad::ClientInfo* client_info,
+ const std::wstring* file_path) {
+ ProcessingLock lock;
+
+ if (!file_path) {
+ LOG(ERROR) << "dump with no file path";
+ return;
+ }
+ if (!client_info) {
+ LOG(ERROR) << "dump with no client info";
+ return;
+ }
+
+ CrashService* self = static_cast<CrashService*>(context);
+ if (!self) {
+ LOG(ERROR) << "dump with no context";
+ return;
+ }
+
+ CrashMap map;
+ CustomInfoToMap(client_info, self->reporter_tag_, &map);
+
+ // Move dump file to the directory under client breakpad dump location.
+ base::FilePath dump_location = base::FilePath(*file_path);
+ CrashMap::const_iterator it = map.find(L"breakpad-dump-location");
+ if (it != map.end()) {
+ base::FilePath alternate_dump_location = base::FilePath(it->second);
+ base::CreateDirectoryW(alternate_dump_location);
+ alternate_dump_location = alternate_dump_location.Append(
+ dump_location.BaseName());
+ base::Move(dump_location, alternate_dump_location);
+ dump_location = alternate_dump_location;
+ }
+
+ DWORD pid = client_info->pid();
+ VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value();
+
+ if (!WriteCustomInfoToFile(dump_location.value(), map)) {
+ LOG(ERROR) << "could not write custom info file";
+ }
+
+ if (!self->sender_)
+ return;
+
+ // Send the crash dump using a worker thread. This operation has retry
+ // logic in case there is no internet connection at the time.
+ DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map,
+ dump_location.value());
+ if (!::QueueUserWorkItem(&CrashService::AsyncSendDump,
+ dump_job, WT_EXECUTELONGFUNCTION)) {
+ LOG(ERROR) << "could not queue job";
+ }
+}
+
+// We are going to try sending the report several times. If we can't send,
+// we sleep from one minute to several hours depending on the retry round.
+unsigned long CrashService::AsyncSendDump(void* context) {
+ if (!context)
+ return 0;
+
+ DumpJobInfo* info = static_cast<DumpJobInfo*>(context);
+
+ std::wstring report_id = L"<unsent>";
+
+ const DWORD kOneMinute = 60*1000;
+ const DWORD kOneHour = 60*kOneMinute;
+
+ const DWORD kSleepSchedule[] = {
+ 24*kOneHour,
+ 8*kOneHour,
+ 4*kOneHour,
+ kOneHour,
+ 15*kOneMinute,
+ 0};
+
+ int retry_round = arraysize(kSleepSchedule) - 1;
+
+ do {
+ ::Sleep(kSleepSchedule[retry_round]);
+ {
+ // Take the server lock while sending. This also prevent early
+ // termination of the service object.
+ base::AutoLock lock(info->self->sending_);
+ VLOG(1) << "trying to send report for pid = " << info->pid;
+ google_breakpad::ReportResult send_result
+ = info->self->sender_->SendCrashReport(kCrashReportURL, info->map,
+ info->dump_path, &report_id);
+ switch (send_result) {
+ case google_breakpad::RESULT_FAILED:
+ report_id = L"<network issue>";
+ break;
+ case google_breakpad::RESULT_REJECTED:
+ report_id = L"<rejected>";
+ ++info->self->requests_handled_;
+ retry_round = 0;
+ break;
+ case google_breakpad::RESULT_SUCCEEDED:
+ ++info->self->requests_sent_;
+ ++info->self->requests_handled_;
+ retry_round = 0;
+ break;
+ case google_breakpad::RESULT_THROTTLED:
+ report_id = L"<throttled>";
+ break;
+ default:
+ report_id = L"<unknown>";
+ break;
+ };
+ }
+
+ VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id;
+ --retry_round;
+ } while (retry_round >= 0);
+
+ if (!::DeleteFileW(info->dump_path.c_str()))
+ LOG(WARNING) << "could not delete " << info->dump_path;
+
+ delete info;
+ return 0;
+}
+
+int CrashService::ProcessingLoop() {
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ VLOG(1) << "session ending..";
+ while (ProcessingLock::IsWorking()) {
+ ::Sleep(50);
+ }
+
+ VLOG(1) << "clients connected :" << clients_connected_
+ << "\nclients terminated :" << clients_terminated_
+ << "\ndumps serviced :" << requests_handled_
+ << "\ndumps reported :" << requests_sent_;
+
+ return static_cast<int>(msg.wParam);
+}
+
+PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() {
+ // Build the SDDL string for the label.
+ std::wstring sddl = L"S:(ML;;NW;;;S-1-16-4096)";
+
+ PSECURITY_DESCRIPTOR sec_desc = NULL;
+
+ PACL sacl = NULL;
+ BOOL sacl_present = FALSE;
+ BOOL sacl_defaulted = FALSE;
+
+ if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),
+ SDDL_REVISION,
+ &sec_desc, NULL)) {
+ if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
+ &sacl_defaulted)) {
+ return sec_desc;
+ }
+ }
+
+ return NULL;
+}
+
+} // namespace breakpad
diff --git a/components/crash/content/tools/crash_service.h b/components/crash/content/tools/crash_service.h
new file mode 100644
index 0000000..39335e3
--- /dev/null
+++ b/components/crash/content/tools/crash_service.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CRASH_CONTENT_TOOLS_CRASH_SERVICE_H_
+#define COMPONENTS_CRASH_CONTENT_TOOLS_CRASH_SERVICE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/synchronization/lock.h"
+
+namespace google_breakpad {
+
+class CrashReportSender;
+class CrashGenerationServer;
+class ClientInfo;
+
+}
+
+namespace breakpad {
+
+// This class implements an out-of-process crash server. It uses breakpad's
+// CrashGenerationServer and CrashReportSender to generate and then send the
+// crash dumps. Internally, it uses OS specific pipe to allow applications to
+// register for crash dumps and later on when a registered application crashes
+// it will signal an event that causes this code to wake up and perform a
+// crash dump on the signaling process. The dump is then stored on disk and
+// possibly sent to the crash2 servers.
+class CrashService {
+ public:
+ CrashService();
+ ~CrashService();
+
+ // Starts servicing crash dumps. Returns false if it failed. Do not use
+ // other members in that case. |operating_dir| is where the CrashService
+ // should store breakpad's checkpoint file. |dumps_path| is the directory
+ // where the crash dumps should be stored.
+ bool Initialize(const base::FilePath& operating_dir,
+ const base::FilePath& dumps_path);
+
+ // Command line switches:
+ //
+ // --max-reports=<number>
+ // Allows to override the maximum number for reports per day. Normally
+ // the crash dumps are never sent so if you want to send any you must
+ // specify a positive number here.
+ static const char kMaxReports[];
+ // --no-window
+ // Does not create a visible window on the desktop. The window does not have
+ // any other functionality other than allowing the crash service to be
+ // gracefully closed.
+ static const char kNoWindow[];
+ // --reporter=<string>
+ // Allows to specify a custom string that appears on the detail crash report
+ // page in the crash server. This should be a 25 chars or less string.
+ // The default tag if not specified is 'crash svc'.
+ static const char kReporterTag[];
+ // --dumps-dir=<directory-path>
+ // Override the directory to which crash dump files will be written.
+ static const char kDumpsDir[];
+ // --pipe-name=<string>
+ // Override the name of the Windows named pipe on which we will
+ // listen for crash dump request messages.
+ static const char kPipeName[];
+
+ // Returns number of crash dumps handled.
+ int requests_handled() const {
+ return requests_handled_;
+ }
+ // Returns number of crash clients registered.
+ int clients_connected() const {
+ return clients_connected_;
+ }
+ // Returns number of crash clients terminated.
+ int clients_terminated() const {
+ return clients_terminated_;
+ }
+
+ // Starts the processing loop. This function does not return unless the
+ // user is logging off or the user closes the crash service window. The
+ // return value is a good number to pass in ExitProcess().
+ int ProcessingLoop();
+
+ private:
+ static void OnClientConnected(void* context,
+ const google_breakpad::ClientInfo* client_info);
+
+ static void OnClientDumpRequest(
+ void* context,
+ const google_breakpad::ClientInfo* client_info,
+ const std::wstring* file_path);
+
+ static void OnClientExited(void* context,
+ const google_breakpad::ClientInfo* client_info);
+
+ // This routine sends the crash dump to the server. It takes the sending_
+ // lock when it is performing the send.
+ static unsigned long __stdcall AsyncSendDump(void* context);
+
+ // Returns the security descriptor which access to low integrity processes
+ // The caller is supposed to free the security descriptor by calling
+ // LocalFree.
+ PSECURITY_DESCRIPTOR GetSecurityDescriptorForLowIntegrity();
+
+ google_breakpad::CrashGenerationServer* dumper_;
+ google_breakpad::CrashReportSender* sender_;
+
+ // the extra tag sent to the server with each dump.
+ std::wstring reporter_tag_;
+
+ // clients serviced statistics:
+ int requests_handled_;
+ int requests_sent_;
+ volatile long clients_connected_;
+ volatile long clients_terminated_;
+ base::Lock sending_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrashService);
+};
+
+} // namespace breakpad
+
+#endif // COMPONENTS_CRASH_CONTENT_TOOLS_CRASH_SERVICE_H_
diff --git a/components/crash/content/tools/dmp2minidump.py b/components/crash/content/tools/dmp2minidump.py
new file mode 100755
index 0000000..7823d48
--- /dev/null
+++ b/components/crash/content/tools/dmp2minidump.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A tool to extract minidumps from dmp crash dumps."""
+
+import os
+import sys
+from cgi import parse_multipart
+
+
+def ProcessDump(dump_file, minidump_file):
+ """Extracts the part of the dump file that minidump_stackwalk can read.
+
+ The dump files generated by the breakpad integration multi-part form data
+ that include the minidump as file attachment.
+
+ Args:
+ dump_file: the dump file that needs to be processed.
+ minidump_file: the file to write the minidump to.
+ """
+ try:
+ dump = open(dump_file, 'rb')
+ boundary = dump.readline().strip()[2:]
+ data = parse_multipart(dump, {'boundary': boundary})
+ except:
+ print 'Failed to read dmp file %s' % dump_file
+ return
+
+ if not 'upload_file_minidump' in data:
+ print 'Could not find minidump file in dump.'
+ return
+
+ f = open(minidump_file, 'w')
+ f.write("\r\n".join(data['upload_file_minidump']))
+ f.close()
+
+
+def main():
+ if len(sys.argv) != 3:
+ print 'Usage: %s [dmp file] [minidump]' % sys.argv[0]
+ print ''
+ print 'Extracts the minidump stored in the crash dump file'
+ return 1
+
+ ProcessDump(sys.argv[1], sys.argv[2])
+
+
+if '__main__' == __name__:
+ sys.exit(main())
diff --git a/components/crash/content/tools/generate_breakpad_symbols.py b/components/crash/content/tools/generate_breakpad_symbols.py
new file mode 100755
index 0000000..5f8fb85
--- /dev/null
+++ b/components/crash/content/tools/generate_breakpad_symbols.py
@@ -0,0 +1,262 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A tool to generate symbols for a binary suitable for breakpad.
+
+Currently, the tool only supports Linux, Android, and Mac. Support for other
+platforms is planned.
+"""
+
+import errno
+import optparse
+import os
+import Queue
+import re
+import shutil
+import subprocess
+import sys
+import threading
+
+
+CONCURRENT_TASKS=4
+
+
+def GetCommandOutput(command):
+ """Runs the command list, returning its output.
+
+ Prints the given command (which should be a list of one or more strings),
+ then runs it and returns its output (stdout) as a string.
+
+ From chromium_utils.
+ """
+ devnull = open(os.devnull, 'w')
+ proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull,
+ bufsize=1)
+ output = proc.communicate()[0]
+ return output
+
+
+def GetDumpSymsBinary(build_dir=None):
+ """Returns the path to the dump_syms binary."""
+ DUMP_SYMS = 'dump_syms'
+ dump_syms_bin = os.path.join(os.path.expanduser(build_dir), DUMP_SYMS)
+ if not os.access(dump_syms_bin, os.X_OK):
+ print 'Cannot find %s.' % dump_syms_bin
+ return None
+
+ return dump_syms_bin
+
+
+def Resolve(path, exe_path, loader_path, rpaths):
+ """Resolve a dyld path.
+
+ @executable_path is replaced with |exe_path|
+ @loader_path is replaced with |loader_path|
+ @rpath is replaced with the first path in |rpaths| where the referenced file
+ is found
+ """
+ path = path.replace('@loader_path', loader_path)
+ path = path.replace('@executable_path', exe_path)
+ if path.find('@rpath') != -1:
+ for rpath in rpaths:
+ new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path,
+ [])
+ if os.access(new_path, os.X_OK):
+ return new_path
+ return ''
+ return path
+
+
+def GetSharedLibraryDependenciesLinux(binary):
+ """Return absolute paths to all shared library dependecies of the binary.
+
+ This implementation assumes that we're running on a Linux system."""
+ ldd = GetCommandOutput(['ldd', binary])
+ lib_re = re.compile('\t.* => (.+) \(.*\)$')
+ result = []
+ for line in ldd.splitlines():
+ m = lib_re.match(line)
+ if m:
+ result.append(m.group(1))
+ return result
+
+
+def GetSharedLibraryDependenciesMac(binary, exe_path):
+ """Return absolute paths to all shared library dependecies of the binary.
+
+ This implementation assumes that we're running on a Mac system."""
+ loader_path = os.path.dirname(binary)
+ otool = GetCommandOutput(['otool', '-l', binary]).splitlines()
+ rpaths = []
+ for idx, line in enumerate(otool):
+ if line.find('cmd LC_RPATH') != -1:
+ m = re.match(' *path (.*) \(offset .*\)$', otool[idx+2])
+ rpaths.append(m.group(1))
+
+ otool = GetCommandOutput(['otool', '-L', binary]).splitlines()
+ lib_re = re.compile('\t(.*) \(compatibility .*\)$')
+ deps = []
+ for line in otool:
+ m = lib_re.match(line)
+ if m:
+ dep = Resolve(m.group(1), exe_path, loader_path, rpaths)
+ if dep:
+ deps.append(os.path.normpath(dep))
+ return deps
+
+
+def GetSharedLibraryDependencies(options, binary, exe_path):
+ """Return absolute paths to all shared library dependecies of the binary."""
+ deps = []
+ if sys.platform.startswith('linux'):
+ deps = GetSharedLibraryDependenciesLinux(binary)
+ elif sys.platform == 'darwin':
+ deps = GetSharedLibraryDependenciesMac(binary, exe_path)
+ else:
+ print "Platform not supported."
+ sys.exit(1)
+
+ result = []
+ build_dir = os.path.abspath(options.build_dir)
+ for dep in deps:
+ if (os.access(dep, os.X_OK) and
+ os.path.abspath(os.path.dirname(dep)).startswith(build_dir)):
+ result.append(dep)
+ return result
+
+
+def mkdir_p(path):
+ """Simulates mkdir -p."""
+ try:
+ os.makedirs(path)
+ except OSError as e:
+ if e.errno == errno.EEXIST and os.path.isdir(path):
+ pass
+ else: raise
+
+
+def GenerateSymbols(options, binaries):
+ """Dumps the symbols of binary and places them in the given directory."""
+
+ queue = Queue.Queue()
+ print_lock = threading.Lock()
+
+ def _Worker():
+ while True:
+ binary = queue.get()
+
+ should_dump_syms = True
+ reason = "no reason"
+
+ output_path = os.path.join(
+ options.symbols_dir, os.path.basename(binary))
+ if os.path.isdir(output_path):
+ if os.path.getmtime(binary) < os.path.getmtime(output_path):
+ should_dump_syms = False
+ reason = "symbols are more current than binary"
+
+ if not should_dump_syms:
+ if options.verbose:
+ with print_lock:
+ print "Skipping %s (%s)" % (binary, reason)
+ queue.task_done()
+ continue
+
+ if options.verbose:
+ with print_lock:
+ print "Generating symbols for %s" % binary
+
+ if os.path.isdir(output_path):
+ os.utime(output_path, None)
+
+ syms = GetCommandOutput([GetDumpSymsBinary(options.build_dir), '-r',
+ binary])
+ module_line = re.match("MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n", syms)
+ output_path = os.path.join(options.symbols_dir, module_line.group(2),
+ module_line.group(1))
+ mkdir_p(output_path)
+ symbol_file = "%s.sym" % module_line.group(2)
+ try:
+ f = open(os.path.join(output_path, symbol_file), 'w')
+ f.write(syms)
+ f.close()
+ except Exception, e:
+ # Not much we can do about this.
+ with print_lock:
+ print e
+
+ queue.task_done()
+
+ for binary in binaries:
+ queue.put(binary)
+
+ for _ in range(options.jobs):
+ t = threading.Thread(target=_Worker)
+ t.daemon = True
+ t.start()
+
+ queue.join()
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('', '--build-dir', default='',
+ help='The build output directory.')
+ parser.add_option('', '--symbols-dir', default='',
+ help='The directory where to write the symbols file.')
+ parser.add_option('', '--binary', default='',
+ help='The path of the binary to generate symbols for.')
+ parser.add_option('', '--clear', default=False, action='store_true',
+ help='Clear the symbols directory before writing new '
+ 'symbols.')
+ parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store',
+ type='int', help='Number of parallel tasks to run.')
+ parser.add_option('-v', '--verbose', action='store_true',
+ help='Print verbose status output.')
+
+ (options, _) = parser.parse_args()
+
+ if not options.symbols_dir:
+ print "Required option --symbols-dir missing."
+ return 1
+
+ if not options.build_dir:
+ print "Required option --build-dir missing."
+ return 1
+
+ if not options.binary:
+ print "Required option --binary missing."
+ return 1
+
+ if not os.access(options.binary, os.X_OK):
+ print "Cannot find %s." % options.binary
+ return 1
+
+ if options.clear:
+ try:
+ shutil.rmtree(options.symbols_dir)
+ except:
+ pass
+
+ if not GetDumpSymsBinary(options.build_dir):
+ return 1
+
+ # Build the transitive closure of all dependencies.
+ binaries = set([options.binary])
+ queue = [options.binary]
+ exe_path = os.path.dirname(options.binary)
+ while queue:
+ deps = GetSharedLibraryDependencies(options, queue.pop(0), exe_path)
+ new_deps = set(deps) - binaries
+ binaries |= new_deps
+ queue.extend(list(new_deps))
+
+ GenerateSymbols(options, binaries)
+
+ return 0
+
+
+if '__main__' == __name__:
+ sys.exit(main())