diff options
Diffstat (limited to 'components/crash/content')
30 files changed, 6006 insertions, 0 deletions
diff --git a/components/crash/content/app/BUILD.gn b/components/crash/content/app/BUILD.gn new file mode 100644 index 0000000..e0985c5 --- /dev/null +++ b/components/crash/content/app/BUILD.gn @@ -0,0 +1,79 @@ +# 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_android) { + import("//build/config/android/config.gni") +} + +source_set("lib") { + sources = [ + "crash_keys_win.cc", + "crash_keys_win.h", + "crash_reporter_client.cc", + "crash_reporter_client.h", + ] + + include_dirs = [ "../../../../breakpad/src" ] +} + +# Note: if you depend on this target, you need to either link in +# content.gyp:content_common, or add content/public/common/content_switches.cc +# to your sources. +# +# GYP version: components/crash.gypi:crash_component +source_set("app") { + sources = [ + "breakpad_linux_impl.h", + "breakpad_mac.h", + "breakpad_mac.mm", + "breakpad_win.cc", + "breakpad_win.h", + "hard_error_handler_win.cc", + "hard_error_handler_win.h", + ] + + if (is_android) { + libs = [ "log" ] + } + + if (is_android || is_linux) { + # Want these files on both Linux and Android. + set_sources_assignment_filter([]) + sources += [ + "breakpad_linux.cc", + "breakpad_linux.h", + ] + } + + defines = [ "CRASH_IMPLEMENTATION" ] + + deps = [ + "//base", + "//base:base_static", + ":lib", + ] + + if (is_android) { + defines += [ "CHROME_BUILD_ID=" + android_chrome_build_id ] + } + + if (is_mac) { + deps += [ "//breakpad" ] + } else if (is_win) { + deps += [ + "//sandbox", + "//breakpad:breakpad_handler", + + #'../breakpad/breakpad.gyp:breakpad_sender', TODO(GYP) + ] + } else if (is_posix && !is_ios) { + deps += [ "//breakpad:client" ] + } +} + +source_set("test_support") { + deps = [ + ":lib", + ] +} diff --git a/components/crash/content/app/DEPS b/components/crash/content/app/DEPS new file mode 100644 index 0000000..dc5e932 --- /dev/null +++ b/components/crash/content/app/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "+sandbox", + + "+content/public/common/content_descriptors.h", + "+content/public/common/result_codes.h", + "+third_party/crashpad", + "+third_party/lss/linux_syscall_support.h", +] diff --git a/components/crash/content/app/breakpad_linux.cc b/components/crash/content/app/breakpad_linux.cc new file mode 100644 index 0000000..c08a7f9 --- /dev/null +++ b/components/crash/content/app/breakpad_linux.cc @@ -0,0 +1,1760 @@ +// 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. + +// For linux_syscall_support.h. This makes it safe to call embedded system +// calls when in seccomp mode. + +#include "components/crash/content/app/breakpad_linux.h" + +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include <algorithm> +#include <string> + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/debug/crash_logging.h" +#include "base/debug/dump_without_crashing.h" +#include "base/files/file_path.h" +#include "base/linux_util.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/global_descriptors.h" +#include "base/process/memory.h" +#include "base/strings/string_util.h" +#include "breakpad/src/client/linux/crash_generation/crash_generation_client.h" +#include "breakpad/src/client/linux/handler/exception_handler.h" +#include "breakpad/src/client/linux/minidump_writer/directory_reader.h" +#include "breakpad/src/common/linux/linux_libc_support.h" +#include "breakpad/src/common/memory.h" +#include "build/build_config.h" +#include "components/crash/content/app/breakpad_linux_impl.h" +#include "components/crash/content/app/crash_reporter_client.h" +#include "content/public/common/content_descriptors.h" + +#if defined(OS_ANDROID) +#include <android/log.h> +#include <sys/stat.h> + +#include "base/android/build_info.h" +#include "base/android/path_utils.h" +#include "base/debug/leak_annotations.h" +#endif +#include "third_party/lss/linux_syscall_support.h" + +#if defined(ADDRESS_SANITIZER) +#include <ucontext.h> // for getcontext(). +#endif + +#if defined(OS_ANDROID) +#define STAT_STRUCT struct stat +#define FSTAT_FUNC fstat +#else +#define STAT_STRUCT struct kernel_stat +#define FSTAT_FUNC sys_fstat +#endif + +// Some versions of gcc are prone to warn about unused return values. In cases +// where we either a) know the call cannot fail, or b) there is nothing we +// can do when a call fails, we mark the return code as ignored. This avoids +// spurious compiler warnings. +#define IGNORE_RET(x) do { if (x); } while (0) + +using crash_reporter::GetCrashReporterClient; +using google_breakpad::ExceptionHandler; +using google_breakpad::MinidumpDescriptor; + +namespace breakpad { + +namespace { + +#if !defined(OS_CHROMEOS) +const char kUploadURL[] = "https://clients2.google.com/cr/report"; +#endif + +bool g_is_crash_reporter_enabled = false; +uint64_t g_process_start_time = 0; +pid_t g_pid = 0; +char* g_crash_log_path = nullptr; +ExceptionHandler* g_breakpad = nullptr; + +#if defined(ADDRESS_SANITIZER) +const char* g_asan_report_str = nullptr; +#endif +#if defined(OS_ANDROID) +char* g_process_type = nullptr; +ExceptionHandler* g_microdump = nullptr; +const char* g_microdump_build_fingerprint = nullptr; +const char* g_microdump_product_info = nullptr; +#endif + +CrashKeyStorage* g_crash_keys = nullptr; + +// Writes the value |v| as 16 hex characters to the memory pointed at by +// |output|. +void write_uint64_hex(char* output, uint64_t v) { + static const char hextable[] = "0123456789abcdef"; + + for (int i = 15; i >= 0; --i) { + output[i] = hextable[v & 15]; + v >>= 4; + } +} + +// The following helper functions are for calculating uptime. + +// Converts a struct timeval to milliseconds. +uint64_t timeval_to_ms(struct timeval *tv) { + uint64_t ret = tv->tv_sec; // Avoid overflow by explicitly using a uint64_t. + ret *= 1000; + ret += tv->tv_usec / 1000; + return ret; +} + +// Converts a struct timeval to milliseconds. +uint64_t kernel_timeval_to_ms(struct kernel_timeval *tv) { + uint64_t ret = tv->tv_sec; // Avoid overflow by explicitly using a uint64_t. + ret *= 1000; + ret += tv->tv_usec / 1000; + return ret; +} + +// String buffer size to use to convert a uint64_t to string. +const size_t kUint64StringSize = 21; + +void SetProcessStartTime() { + // Set the base process start time value. + struct timeval tv; + if (!gettimeofday(&tv, nullptr)) + g_process_start_time = timeval_to_ms(&tv); + else + g_process_start_time = 0; +} + +// uint64_t version of my_int_len() from +// breakpad/src/common/linux/linux_libc_support.h. Return the length of the +// given, non-negative integer when expressed in base 10. +unsigned my_uint64_len(uint64_t i) { + if (!i) + return 1; + + unsigned len = 0; + while (i) { + len++; + i /= 10; + } + + return len; +} + +// uint64_t version of my_uitos() from +// breakpad/src/common/linux/linux_libc_support.h. Convert a non-negative +// integer to a string (not null-terminated). +void my_uint64tos(char* output, uint64_t i, unsigned i_len) { + for (unsigned index = i_len; index; --index, i /= 10) + output[index - 1] = '0' + (i % 10); +} + +#if !defined(OS_CHROMEOS) +bool my_isxdigit(char c) { + return (c >= '0' && c <= '9') || ((c | 0x20) >= 'a' && (c | 0x20) <= 'f'); +} +#endif + +size_t LengthWithoutTrailingSpaces(const char* str, size_t len) { + while (len > 0 && str[len - 1] == ' ') { + len--; + } + return len; +} + +void SetClientIdFromCommandLine(const base::CommandLine& command_line) { + // Get the guid from the command line switch. + std::string switch_value = + command_line.GetSwitchValueASCII(switches::kEnableCrashReporter); + GetCrashReporterClient()->SetCrashReporterClientIdFromGUID(switch_value); +} + +// MIME substrings. +#if defined(OS_CHROMEOS) +const char g_sep[] = ":"; +#endif +const char g_rn[] = "\r\n"; +const char g_form_data_msg[] = "Content-Disposition: form-data; name=\""; +const char g_quote_msg[] = "\""; +const char g_dashdash_msg[] = "--"; +const char g_dump_msg[] = "upload_file_minidump\"; filename=\"dump\""; +#if defined(ADDRESS_SANITIZER) +const char g_log_msg[] = "upload_file_log\"; filename=\"log\""; +#endif +const char g_content_type_msg[] = "Content-Type: application/octet-stream"; + +// MimeWriter manages an iovec for writing MIMEs to a file. +class MimeWriter { + public: + static const int kIovCapacity = 30; + static const size_t kMaxCrashChunkSize = 64; + + MimeWriter(int fd, const char* const mime_boundary); + ~MimeWriter(); + + // Append boundary. + virtual void AddBoundary(); + + // Append end of file boundary. + virtual void AddEnd(); + + // Append key/value pair with specified sizes. + virtual void AddPairData(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size); + + // Append key/value pair. + void AddPairString(const char* msg_type, + const char* msg_data) { + AddPairData(msg_type, my_strlen(msg_type), msg_data, my_strlen(msg_data)); + } + + // Append key/value pair, splitting value into chunks no larger than + // |chunk_size|. |chunk_size| cannot be greater than |kMaxCrashChunkSize|. + // The msg_type string will have a counter suffix to distinguish each chunk. + virtual void AddPairDataInChunks(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size, + size_t chunk_size, + bool strip_trailing_spaces); + + // Add binary file contents to be uploaded with the specified filename. + virtual void AddFileContents(const char* filename_msg, + uint8_t* file_data, + size_t file_size); + + // Flush any pending iovecs to the output file. + void Flush() { + IGNORE_RET(sys_writev(fd_, iov_, iov_index_)); + iov_index_ = 0; + } + + protected: + void AddItem(const void* base, size_t size); + // Minor performance trade-off for easier-to-maintain code. + void AddString(const char* str) { + AddItem(str, my_strlen(str)); + } + void AddItemWithoutTrailingSpaces(const void* base, size_t size); + + struct kernel_iovec iov_[kIovCapacity]; + int iov_index_; + + // Output file descriptor. + int fd_; + + const char* const mime_boundary_; + + private: + DISALLOW_COPY_AND_ASSIGN(MimeWriter); +}; + +MimeWriter::MimeWriter(int fd, const char* const mime_boundary) + : iov_index_(0), + fd_(fd), + mime_boundary_(mime_boundary) { +} + +MimeWriter::~MimeWriter() { +} + +void MimeWriter::AddBoundary() { + AddString(mime_boundary_); + AddString(g_rn); +} + +void MimeWriter::AddEnd() { + AddString(mime_boundary_); + AddString(g_dashdash_msg); + AddString(g_rn); +} + +void MimeWriter::AddPairData(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size) { + AddString(g_form_data_msg); + AddItem(msg_type, msg_type_size); + AddString(g_quote_msg); + AddString(g_rn); + AddString(g_rn); + AddItem(msg_data, msg_data_size); + AddString(g_rn); +} + +void MimeWriter::AddPairDataInChunks(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size, + size_t chunk_size, + bool strip_trailing_spaces) { + if (chunk_size > kMaxCrashChunkSize) + return; + + unsigned i = 0; + size_t done = 0, msg_length = msg_data_size; + + while (msg_length) { + char num[kUint64StringSize]; + const unsigned num_len = my_uint_len(++i); + my_uitos(num, i, num_len); + + size_t chunk_len = std::min(chunk_size, msg_length); + + AddString(g_form_data_msg); + AddItem(msg_type, msg_type_size); + AddItem(num, num_len); + AddString(g_quote_msg); + AddString(g_rn); + AddString(g_rn); + if (strip_trailing_spaces) { + AddItemWithoutTrailingSpaces(msg_data + done, chunk_len); + } else { + AddItem(msg_data + done, chunk_len); + } + AddString(g_rn); + AddBoundary(); + Flush(); + + done += chunk_len; + msg_length -= chunk_len; + } +} + +void MimeWriter::AddFileContents(const char* filename_msg, uint8_t* file_data, + size_t file_size) { + AddString(g_form_data_msg); + AddString(filename_msg); + AddString(g_rn); + AddString(g_content_type_msg); + AddString(g_rn); + AddString(g_rn); + AddItem(file_data, file_size); + AddString(g_rn); +} + +void MimeWriter::AddItem(const void* base, size_t size) { + // Check if the iovec is full and needs to be flushed to output file. + if (iov_index_ == kIovCapacity) { + Flush(); + } + iov_[iov_index_].iov_base = const_cast<void*>(base); + iov_[iov_index_].iov_len = size; + ++iov_index_; +} + +void MimeWriter::AddItemWithoutTrailingSpaces(const void* base, size_t size) { + AddItem(base, LengthWithoutTrailingSpaces(static_cast<const char*>(base), + size)); +} + +#if defined(OS_CHROMEOS) +// This subclass is used on Chromium OS to report crashes in a format easy for +// the central crash reporting facility to understand. +// Format is <name>:<data length in decimal>:<data> +class CrashReporterWriter : public MimeWriter { + public: + explicit CrashReporterWriter(int fd); + + void AddBoundary() override; + + void AddEnd() override; + + void AddPairData(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size) override; + + void AddPairDataInChunks(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size, + size_t chunk_size, + bool strip_trailing_spaces) override; + + void AddFileContents(const char* filename_msg, + uint8_t* file_data, + size_t file_size) override; + + private: + DISALLOW_COPY_AND_ASSIGN(CrashReporterWriter); +}; + + +CrashReporterWriter::CrashReporterWriter(int fd) : MimeWriter(fd, "") {} + +// No-ops. +void CrashReporterWriter::AddBoundary() {} +void CrashReporterWriter::AddEnd() {} + +void CrashReporterWriter::AddPairData(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size) { + char data[kUint64StringSize]; + const unsigned data_len = my_uint_len(msg_data_size); + my_uitos(data, msg_data_size, data_len); + + AddItem(msg_type, msg_type_size); + AddString(g_sep); + AddItem(data, data_len); + AddString(g_sep); + AddItem(msg_data, msg_data_size); + Flush(); +} + +void CrashReporterWriter::AddPairDataInChunks(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size, + size_t chunk_size, + bool strip_trailing_spaces) { + if (chunk_size > kMaxCrashChunkSize) + return; + + unsigned i = 0; + size_t done = 0; + size_t msg_length = msg_data_size; + + while (msg_length) { + char num[kUint64StringSize]; + const unsigned num_len = my_uint_len(++i); + my_uitos(num, i, num_len); + + size_t chunk_len = std::min(chunk_size, msg_length); + + size_t write_len = chunk_len; + if (strip_trailing_spaces) { + // Take care of this here because we need to know the exact length of + // what is going to be written. + write_len = LengthWithoutTrailingSpaces(msg_data + done, write_len); + } + + char data[kUint64StringSize]; + const unsigned data_len = my_uint_len(write_len); + my_uitos(data, write_len, data_len); + + AddItem(msg_type, msg_type_size); + AddItem(num, num_len); + AddString(g_sep); + AddItem(data, data_len); + AddString(g_sep); + AddItem(msg_data + done, write_len); + Flush(); + + done += chunk_len; + msg_length -= chunk_len; + } +} + +void CrashReporterWriter::AddFileContents(const char* filename_msg, + uint8_t* file_data, + size_t file_size) { + char data[kUint64StringSize]; + const unsigned data_len = my_uint_len(file_size); + my_uitos(data, file_size, data_len); + + AddString(filename_msg); + AddString(g_sep); + AddItem(data, data_len); + AddString(g_sep); + AddItem(file_data, file_size); + Flush(); +} +#endif // defined(OS_CHROMEOS) + +void DumpProcess() { + if (g_breakpad) + g_breakpad->WriteMinidump(); + +#if defined(OS_ANDROID) + // If microdumps are enabled write also a microdump on the system log. + if (g_microdump) + g_microdump->WriteMinidump(); +#endif +} + +#if defined(OS_ANDROID) +const char kGoogleBreakpad[] = "google-breakpad"; +#endif + +size_t WriteLog(const char* buf, size_t nbytes) { +#if defined(OS_ANDROID) + return __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, buf); +#else + return sys_write(2, buf, nbytes); +#endif +} + +size_t WriteNewline() { + return WriteLog("\n", 1); +} + +#if defined(OS_ANDROID) +void AndroidLogWriteHorizontalRule() { + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + "### ### ### ### ### ### ### ### ### ### ### ### ###"); +} + +// Android's native crash handler outputs a diagnostic tombstone to the device +// log. By returning false from the HandlerCallbacks, breakpad will reinstall +// the previous (i.e. native) signal handlers before returning from its own +// handler. A Chrome build fingerprint is written to the log, so that the +// specific build of Chrome and the location of the archived Chrome symbols can +// be determined directly from it. +bool FinalizeCrashDoneAndroid(bool is_browser_process) { + base::android::BuildInfo* android_build_info = + base::android::BuildInfo::GetInstance(); + + AndroidLogWriteHorizontalRule(); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + "Chrome build fingerprint:"); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + android_build_info->package_version_name()); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + android_build_info->package_version_code()); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + CHROME_BUILD_ID); + AndroidLogWriteHorizontalRule(); + + if (!is_browser_process && + android_build_info->sdk_int() >= 18 && + my_strcmp(android_build_info->build_type(), "eng") != 0 && + my_strcmp(android_build_info->build_type(), "userdebug") != 0) { + // On JB MR2 and later, the system crash handler displays a dialog. For + // renderer crashes, this is a bad user experience and so this is disabled + // for user builds of Android. + // TODO(cjhopman): There should be some way to recover the crash stack from + // non-uploading user clients. See http://crbug.com/273706. + __android_log_write(ANDROID_LOG_WARN, + kGoogleBreakpad, + "Tombstones are disabled on JB MR2+ user builds."); + AndroidLogWriteHorizontalRule(); + return true; + } + return false; +} +#endif + +bool CrashDone(const MinidumpDescriptor& minidump, + const bool upload, + const bool succeeded) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + if (!succeeded) { + const char msg[] = "Failed to generate minidump."; + WriteLog(msg, sizeof(msg) - 1); + return false; + } + + DCHECK(!minidump.IsFD()); + + BreakpadInfo info = {0}; + info.filename = minidump.path(); + info.fd = minidump.fd(); +#if defined(ADDRESS_SANITIZER) + google_breakpad::PageAllocator allocator; + const size_t log_path_len = my_strlen(minidump.path()); + char* log_path = reinterpret_cast<char*>(allocator.Alloc(log_path_len + 1)); + my_memcpy(log_path, minidump.path(), log_path_len); + my_memcpy(log_path + log_path_len - 4, ".log", 4); + log_path[log_path_len] = '\0'; + info.log_filename = log_path; +#endif + info.process_type = "browser"; + info.process_type_length = 7; + info.distro = base::g_linux_distro; + info.distro_length = my_strlen(base::g_linux_distro); + info.upload = upload; + info.process_start_time = g_process_start_time; + info.oom_size = base::g_oom_size; + info.pid = g_pid; + info.crash_keys = g_crash_keys; + HandleCrashDump(info); +#if defined(OS_ANDROID) + return FinalizeCrashDoneAndroid(true /* is_browser_process */); +#else + return true; +#endif +} + +// Wrapper function, do not add more code here. +bool CrashDoneNoUpload(const MinidumpDescriptor& minidump, + void* context, + bool succeeded) { + return CrashDone(minidump, false, succeeded); +} + +#if !defined(OS_ANDROID) +// Wrapper function, do not add more code here. +bool CrashDoneUpload(const MinidumpDescriptor& minidump, + void* context, + bool succeeded) { + return CrashDone(minidump, true, succeeded); +} +#endif + +#if defined(ADDRESS_SANITIZER) +extern "C" +void __asan_set_error_report_callback(void (*cb)(const char*)); + +extern "C" +void AsanLinuxBreakpadCallback(const char* report) { + g_asan_report_str = report; + // Send minidump here. + g_breakpad->SimulateSignalDelivery(SIGKILL); +} +#endif + +void EnableCrashDumping(bool unattended) { + g_is_crash_reporter_enabled = true; + + base::FilePath tmp_path("/tmp"); + PathService::Get(base::DIR_TEMP, &tmp_path); + + base::FilePath dumps_path(tmp_path); + if (GetCrashReporterClient()->GetCrashDumpLocation(&dumps_path)) { + base::FilePath logfile = + dumps_path.Append(GetCrashReporterClient()->GetReporterLogFilename()); + std::string logfile_str = logfile.value(); + const size_t crash_log_path_len = logfile_str.size() + 1; + g_crash_log_path = new char[crash_log_path_len]; + strncpy(g_crash_log_path, logfile_str.c_str(), crash_log_path_len); + } + DCHECK(!g_breakpad); + MinidumpDescriptor minidump_descriptor(dumps_path.value()); + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kFullMemoryCrashReport)) { + minidump_descriptor.set_size_limit(-1); // unlimited. + } else { + minidump_descriptor.set_size_limit(kMaxMinidumpFileSize); + } +#if defined(OS_ANDROID) + unattended = true; // Android never uploads directly. +#endif + if (unattended) { + g_breakpad = new ExceptionHandler( + minidump_descriptor, + nullptr, + CrashDoneNoUpload, + nullptr, + true, // Install handlers. + -1); // Server file descriptor. -1 for in-process. + return; + } + +#if !defined(OS_ANDROID) + // Attended mode + g_breakpad = new ExceptionHandler( + minidump_descriptor, + nullptr, + CrashDoneUpload, + nullptr, + true, // Install handlers. + -1); // Server file descriptor. -1 for in-process. +#endif +} + +#if defined(OS_ANDROID) +bool MicrodumpCrashDone(const MinidumpDescriptor& minidump, + void* context, + bool succeeded) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + if (!succeeded) { + static const char msg[] = "Microdump crash handler failed.\n"; + WriteLog(msg, sizeof(msg) - 1); + return false; + } + + const bool is_browser_process = (context != nullptr); + return FinalizeCrashDoneAndroid(is_browser_process); + } + +// The microdump handler does NOT upload anything. It just dumps out on the +// system console (logcat) a restricted and serialized variant of a minidump. +// See crbug.com/410294 for more details. +void InitMicrodumpCrashHandlerIfNecessary(const std::string& process_type) { + if (!GetCrashReporterClient()->ShouldEnableBreakpadMicrodumps()) + return; + + VLOG(1) << "Enabling microdumps crash handler (process_type:" + << process_type << ")"; + + // The exception handler runs in a compromised context and cannot use c_str() + // as that would require the heap. Therefore, we have to guarantee that the + // build fingerprint and product info pointers are always valid. + const char* product_name = nullptr; + const char* product_version = nullptr; + GetCrashReporterClient()->GetProductNameAndVersion(&product_name, + &product_version); + + MinidumpDescriptor descriptor(MinidumpDescriptor::kMicrodumpOnConsole); + + if (product_name && product_version) { + g_microdump_product_info = strdup( + (product_name + std::string(":") + product_version).c_str()); + ANNOTATE_LEAKING_OBJECT_PTR(g_microdump_product_info); + descriptor.SetMicrodumpProductInfo(g_microdump_product_info); + } + + const char* android_build_fp = + base::android::BuildInfo::GetInstance()->android_build_fp(); + if (android_build_fp) { + g_microdump_build_fingerprint = strdup(android_build_fp); + ANNOTATE_LEAKING_OBJECT_PTR(g_microdump_build_fingerprint); + descriptor.SetMicrodumpBuildFingerprint(g_microdump_build_fingerprint); + } + + DCHECK(!g_microdump); + bool is_browser_process = process_type.empty() || process_type == "webview"; + g_microdump = new ExceptionHandler( + descriptor, + nullptr, + MicrodumpCrashDone, + reinterpret_cast<void*>(is_browser_process), + true, // Install handlers. + -1); // Server file descriptor. -1 for in-process. + return; +} + +bool CrashDoneInProcessNoUpload( + const google_breakpad::MinidumpDescriptor& descriptor, + void* context, + const bool succeeded) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + if (!succeeded) { + static const char msg[] = "Crash dump generation failed.\n"; + WriteLog(msg, sizeof(msg) - 1); + return false; + } + + // Start constructing the message to send to the browser. + BreakpadInfo info = {0}; + info.filename = nullptr; + info.fd = descriptor.fd(); + info.process_type = g_process_type; + info.process_type_length = my_strlen(g_process_type); + info.distro = nullptr; + info.distro_length = 0; + info.upload = false; + info.process_start_time = g_process_start_time; + info.pid = g_pid; + info.crash_keys = g_crash_keys; + HandleCrashDump(info); + return FinalizeCrashDoneAndroid(false /* is_browser_process */); +} + +void EnableNonBrowserCrashDumping(const std::string& process_type, + int minidump_fd) { + // This will guarantee that the BuildInfo has been initialized and subsequent + // calls will not require memory allocation. + base::android::BuildInfo::GetInstance(); + SetClientIdFromCommandLine(*base::CommandLine::ForCurrentProcess()); + + // On Android, the current sandboxing uses process isolation, in which the + // child process runs with a different UID. That breaks the normal crash + // reporting where the browser process generates the minidump by inspecting + // the child process. This is because the browser process now does not have + // the permission to access the states of the child process (as it has a + // different UID). + // TODO(jcivelli): http://b/issue?id=6776356 we should use a watchdog + // process forked from the renderer process that generates the minidump. + if (minidump_fd == -1) { + LOG(ERROR) << "Minidump file descriptor not found, crash reporting will " + " not work."; + return; + } + SetProcessStartTime(); + g_pid = getpid(); + + g_is_crash_reporter_enabled = true; + // Save the process type (it is leaked). + const size_t process_type_len = process_type.size() + 1; + g_process_type = new char[process_type_len]; + strncpy(g_process_type, process_type.c_str(), process_type_len); + new google_breakpad::ExceptionHandler(MinidumpDescriptor(minidump_fd), + nullptr, CrashDoneInProcessNoUpload, nullptr, true, -1); +} +#else +// Non-Browser = Extension, Gpu, Plugins, Ppapi and Renderer +class NonBrowserCrashHandler : public google_breakpad::CrashGenerationClient { + public: + NonBrowserCrashHandler() + : server_fd_(base::GlobalDescriptors::GetInstance()->Get( + kCrashDumpSignal)) { + } + + ~NonBrowserCrashHandler() override {} + + bool RequestDump(const void* crash_context, + size_t crash_context_size) override { + int fds[2] = { -1, -1 }; + if (sys_socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + static const char msg[] = "Failed to create socket for crash dumping.\n"; + WriteLog(msg, sizeof(msg) - 1); + return false; + } + + // Start constructing the message to send to the browser. + char b; // Dummy variable for sys_read below. + const char* b_addr = &b; // Get the address of |b| so we can create the + // expected /proc/[pid]/syscall content in the + // browser to convert namespace tids. + + // The length of the control message: + static const unsigned kControlMsgSize = sizeof(int); + static const unsigned kControlMsgSpaceSize = CMSG_SPACE(kControlMsgSize); + static const unsigned kControlMsgLenSize = CMSG_LEN(kControlMsgSize); + + struct kernel_msghdr msg; + my_memset(&msg, 0, sizeof(struct kernel_msghdr)); + struct kernel_iovec iov[kCrashIovSize]; + iov[0].iov_base = const_cast<void*>(crash_context); + iov[0].iov_len = crash_context_size; + iov[1].iov_base = &b_addr; + iov[1].iov_len = sizeof(b_addr); + iov[2].iov_base = &fds[0]; + iov[2].iov_len = sizeof(fds[0]); + iov[3].iov_base = &g_process_start_time; + iov[3].iov_len = sizeof(g_process_start_time); + iov[4].iov_base = &base::g_oom_size; + iov[4].iov_len = sizeof(base::g_oom_size); + google_breakpad::SerializedNonAllocatingMap* serialized_map; + iov[5].iov_len = g_crash_keys->Serialize( + const_cast<const google_breakpad::SerializedNonAllocatingMap**>( + &serialized_map)); + iov[5].iov_base = serialized_map; +#if !defined(ADDRESS_SANITIZER) + static_assert(5 == kCrashIovSize - 1, "kCrashIovSize should equal 6"); +#else + iov[6].iov_base = const_cast<char*>(g_asan_report_str); + iov[6].iov_len = kMaxAsanReportSize + 1; + static_assert(6 == kCrashIovSize - 1, "kCrashIovSize should equal 7"); +#endif + + msg.msg_iov = iov; + msg.msg_iovlen = kCrashIovSize; + char cmsg[kControlMsgSpaceSize]; + my_memset(cmsg, 0, kControlMsgSpaceSize); + msg.msg_control = cmsg; + msg.msg_controllen = sizeof(cmsg); + + struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_RIGHTS; + hdr->cmsg_len = kControlMsgLenSize; + ((int*)CMSG_DATA(hdr))[0] = fds[1]; + + if (HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0)) < 0) { + static const char errmsg[] = "Failed to tell parent about crash.\n"; + WriteLog(errmsg, sizeof(errmsg) - 1); + IGNORE_RET(sys_close(fds[0])); + IGNORE_RET(sys_close(fds[1])); + return false; + } + IGNORE_RET(sys_close(fds[1])); + + if (HANDLE_EINTR(sys_read(fds[0], &b, 1)) != 1) { + static const char errmsg[] = "Parent failed to complete crash dump.\n"; + WriteLog(errmsg, sizeof(errmsg) - 1); + } + IGNORE_RET(sys_close(fds[0])); + + return true; + } + + private: + // The pipe FD to the browser process, which will handle the crash dumping. + const int server_fd_; + + DISALLOW_COPY_AND_ASSIGN(NonBrowserCrashHandler); +}; + +void EnableNonBrowserCrashDumping() { + g_is_crash_reporter_enabled = true; + // We deliberately leak this object. + DCHECK(!g_breakpad); + + g_breakpad = new ExceptionHandler( + MinidumpDescriptor("/tmp"), // Unused but needed or Breakpad will assert. + nullptr, + nullptr, + nullptr, + true, + -1); + g_breakpad->set_crash_generation_client(new NonBrowserCrashHandler()); +} +#endif // defined(OS_ANDROID) + +void SetCrashKeyValue(const base::StringPiece& key, + const base::StringPiece& value) { + g_crash_keys->SetKeyValue(key.data(), value.data()); +} + +void ClearCrashKey(const base::StringPiece& key) { + g_crash_keys->RemoveKey(key.data()); +} + +// GetCrashReporterClient() cannot call any Set methods until after +// InitCrashKeys(). +void InitCrashKeys() { + g_crash_keys = new CrashKeyStorage; + GetCrashReporterClient()->RegisterCrashKeys(); + base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValue, &ClearCrashKey); +} + +// Miscellaneous initialization functions to call after Breakpad has been +// enabled. +void PostEnableBreakpadInitialization() { + SetProcessStartTime(); + g_pid = getpid(); + + base::debug::SetDumpWithoutCrashingFunction(&DumpProcess); +#if defined(ADDRESS_SANITIZER) + // Register the callback for AddressSanitizer error reporting. + __asan_set_error_report_callback(AsanLinuxBreakpadCallback); +#endif +} + +} // namespace + +void LoadDataFromFD(google_breakpad::PageAllocator& allocator, + int fd, bool close_fd, uint8_t** file_data, size_t* size) { + STAT_STRUCT st; + if (FSTAT_FUNC(fd, &st) != 0) { + static const char msg[] = "Cannot upload crash dump: stat failed\n"; + WriteLog(msg, sizeof(msg) - 1); + if (close_fd) + IGNORE_RET(sys_close(fd)); + return; + } + + *file_data = reinterpret_cast<uint8_t*>(allocator.Alloc(st.st_size)); + if (!(*file_data)) { + static const char msg[] = "Cannot upload crash dump: cannot alloc\n"; + WriteLog(msg, sizeof(msg) - 1); + if (close_fd) + IGNORE_RET(sys_close(fd)); + return; + } + my_memset(*file_data, 0xf, st.st_size); + + *size = st.st_size; + int byte_read = sys_read(fd, *file_data, *size); + if (byte_read == -1) { + static const char msg[] = "Cannot upload crash dump: read failed\n"; + WriteLog(msg, sizeof(msg) - 1); + if (close_fd) + IGNORE_RET(sys_close(fd)); + return; + } + + if (close_fd) + IGNORE_RET(sys_close(fd)); +} + +void LoadDataFromFile(google_breakpad::PageAllocator& allocator, + const char* filename, + int* fd, uint8_t** file_data, size_t* size) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + *fd = sys_open(filename, O_RDONLY, 0); + *size = 0; + + if (*fd < 0) { + static const char msg[] = "Cannot upload crash dump: failed to open\n"; + WriteLog(msg, sizeof(msg) - 1); + return; + } + + LoadDataFromFD(allocator, *fd, true, file_data, size); +} + +// Spawn the appropriate upload process for the current OS: +// - generic Linux invokes wget. +// - ChromeOS invokes crash_reporter. +// |dumpfile| is the path to the dump data file. +// |mime_boundary| is only used on Linux. +// |exe_buf| is only used on CrOS and is the crashing process' name. +void ExecUploadProcessOrTerminate(const BreakpadInfo& info, + const char* dumpfile, + const char* mime_boundary, + const char* exe_buf, + google_breakpad::PageAllocator* allocator) { +#if defined(OS_CHROMEOS) + // CrOS uses crash_reporter instead of wget to report crashes, + // it needs to know where the crash dump lives and the pid and uid of the + // crashing process. + static const char kCrashReporterBinary[] = "/sbin/crash_reporter"; + + char pid_buf[kUint64StringSize]; + uint64_t pid_str_length = my_uint64_len(info.pid); + my_uint64tos(pid_buf, info.pid, pid_str_length); + pid_buf[pid_str_length] = '\0'; + + char uid_buf[kUint64StringSize]; + uid_t uid = geteuid(); + uint64_t uid_str_length = my_uint64_len(uid); + my_uint64tos(uid_buf, uid, uid_str_length); + uid_buf[uid_str_length] = '\0'; + + const char kChromeFlag[] = "--chrome="; + size_t buf_len = my_strlen(dumpfile) + sizeof(kChromeFlag); + char* chrome_flag = reinterpret_cast<char*>(allocator->Alloc(buf_len)); + chrome_flag[0] = '\0'; + my_strlcat(chrome_flag, kChromeFlag, buf_len); + my_strlcat(chrome_flag, dumpfile, buf_len); + + const char kPidFlag[] = "--pid="; + buf_len = my_strlen(pid_buf) + sizeof(kPidFlag); + char* pid_flag = reinterpret_cast<char*>(allocator->Alloc(buf_len)); + pid_flag[0] = '\0'; + my_strlcat(pid_flag, kPidFlag, buf_len); + my_strlcat(pid_flag, pid_buf, buf_len); + + const char kUidFlag[] = "--uid="; + buf_len = my_strlen(uid_buf) + sizeof(kUidFlag); + char* uid_flag = reinterpret_cast<char*>(allocator->Alloc(buf_len)); + uid_flag[0] = '\0'; + my_strlcat(uid_flag, kUidFlag, buf_len); + my_strlcat(uid_flag, uid_buf, buf_len); + + const char kExeBuf[] = "--exe="; + buf_len = my_strlen(exe_buf) + sizeof(kExeBuf); + char* exe_flag = reinterpret_cast<char*>(allocator->Alloc(buf_len)); + exe_flag[0] = '\0'; + my_strlcat(exe_flag, kExeBuf, buf_len); + my_strlcat(exe_flag, exe_buf, buf_len); + + const char* args[] = { + kCrashReporterBinary, + chrome_flag, + pid_flag, + uid_flag, + exe_flag, + nullptr, + }; + static const char msg[] = "Cannot upload crash dump: cannot exec " + "/sbin/crash_reporter\n"; +#else + // Compress |dumpfile| with gzip. + const pid_t gzip_child = sys_fork(); + if (gzip_child < 0) { + static const char msg[] = "sys_fork() for gzip process failed.\n"; + WriteLog(msg, sizeof(msg) - 1); + sys__exit(1); + } + if (!gzip_child) { + // gzip process. + const char* args[] = { + "/bin/gzip", + "-f", // Do not prompt to verify before overwriting. + dumpfile, + nullptr, + }; + execve(args[0], const_cast<char**>(args), environ); + static const char msg[] = "Cannot exec gzip.\n"; + WriteLog(msg, sizeof(msg) - 1); + sys__exit(1); + } + // Wait for gzip process. + int status = 0; + if (sys_waitpid(gzip_child, &status, 0) != gzip_child || + !WIFEXITED(status) || WEXITSTATUS(status) != 0) { + static const char msg[] = "sys_waitpid() for gzip process failed.\n"; + WriteLog(msg, sizeof(msg) - 1); + sys_kill(gzip_child, SIGKILL); + sys__exit(1); + } + + static const char kGzipExtension[] = ".gz"; + const size_t gzip_file_size = my_strlen(dumpfile) + sizeof(kGzipExtension); + char* const gzip_file = reinterpret_cast<char*>(allocator->Alloc( + gzip_file_size)); + my_strlcpy(gzip_file, dumpfile, gzip_file_size); + my_strlcat(gzip_file, kGzipExtension, gzip_file_size); + + // Rename |gzip_file| to |dumpfile| (the original file was deleted by gzip). + if (rename(gzip_file, dumpfile)) { + static const char msg[] = "Failed to rename gzipped file.\n"; + WriteLog(msg, sizeof(msg) - 1); + sys__exit(1); + } + + // The --header argument to wget looks like: + // --header=Content-Encoding: gzip + // --header=Content-Type: multipart/form-data; boundary=XYZ + // where the boundary has two fewer leading '-' chars + static const char header_content_encoding[] = + "--header=Content-Encoding: gzip"; + static const char header_msg[] = + "--header=Content-Type: multipart/form-data; boundary="; + const size_t header_content_type_size = + sizeof(header_msg) - 1 + my_strlen(mime_boundary) - 2 + 1; + char* const header_content_type = reinterpret_cast<char*>(allocator->Alloc( + header_content_type_size)); + my_strlcpy(header_content_type, header_msg, header_content_type_size); + my_strlcat(header_content_type, mime_boundary + 2, header_content_type_size); + + // The --post-file argument to wget looks like: + // --post-file=/tmp/... + static const char post_file_msg[] = "--post-file="; + const size_t post_file_size = + sizeof(post_file_msg) - 1 + my_strlen(dumpfile) + 1; + char* const post_file = reinterpret_cast<char*>(allocator->Alloc( + post_file_size)); + my_strlcpy(post_file, post_file_msg, post_file_size); + my_strlcat(post_file, dumpfile, post_file_size); + + static const char kWgetBinary[] = "/usr/bin/wget"; + const char* args[] = { + kWgetBinary, + header_content_encoding, + header_content_type, + post_file, + kUploadURL, + "--timeout=10", // Set a timeout so we don't hang forever. + "--tries=1", // Don't retry if the upload fails. + "-O", // output reply to fd 3 + "/dev/fd/3", + nullptr, + }; + static const char msg[] = "Cannot upload crash dump: cannot exec " + "/usr/bin/wget\n"; +#endif + execve(args[0], const_cast<char**>(args), environ); + WriteLog(msg, sizeof(msg) - 1); + sys__exit(1); +} + +// Runs in the helper process to wait for the upload process running +// ExecUploadProcessOrTerminate() to finish. Returns the number of bytes written +// to |fd| and save the written contents to |buf|. +// |buf| needs to be big enough to hold |bytes_to_read| + 1 characters. +size_t WaitForCrashReportUploadProcess(int fd, size_t bytes_to_read, + char* buf) { + size_t bytes_read = 0; + + // Upload should finish in about 10 seconds. Add a few more 500 ms + // internals to account for process startup time. + for (size_t wait_count = 0; wait_count < 24; ++wait_count) { + struct kernel_pollfd poll_fd; + poll_fd.fd = fd; + poll_fd.events = POLLIN | POLLPRI | POLLERR; + int ret = sys_poll(&poll_fd, 1, 500); + if (ret < 0) { + // Error + break; + } else if (ret > 0) { + // There is data to read. + ssize_t len = HANDLE_EINTR( + sys_read(fd, buf + bytes_read, bytes_to_read - bytes_read)); + if (len < 0) + break; + bytes_read += len; + if (bytes_read == bytes_to_read) + break; + } + // |ret| == 0 -> timed out, continue waiting. + // or |bytes_read| < |bytes_to_read| still, keep reading. + } + buf[bytes_to_read] = 0; // Always NUL terminate the buffer. + return bytes_read; +} + +// |buf| should be |expected_len| + 1 characters in size and nullptr terminated. +bool IsValidCrashReportId(const char* buf, size_t bytes_read, + size_t expected_len) { + if (bytes_read != expected_len) + return false; +#if defined(OS_CHROMEOS) + return my_strcmp(buf, "_sys_cr_finished") == 0; +#else + for (size_t i = 0; i < bytes_read; ++i) { + if (!my_isxdigit(buf[i])) + return false; + } + return true; +#endif +} + +// |buf| should be |expected_len| + 1 characters in size and nullptr terminated. +void HandleCrashReportId(const char* buf, size_t bytes_read, + size_t expected_len) { + WriteNewline(); + if (!IsValidCrashReportId(buf, bytes_read, expected_len)) { +#if defined(OS_CHROMEOS) + static const char msg[] = + "System crash-reporter failed to process crash report."; +#else + static const char msg[] = "Failed to get crash dump id."; +#endif + WriteLog(msg, sizeof(msg) - 1); + WriteNewline(); + + static const char id_msg[] = "Report Id: "; + WriteLog(id_msg, sizeof(id_msg) - 1); + WriteLog(buf, bytes_read); + WriteNewline(); + return; + } + +#if defined(OS_CHROMEOS) + static const char msg[] = "Crash dump received by crash_reporter\n"; + WriteLog(msg, sizeof(msg) - 1); +#else + // Write crash dump id to stderr. + static const char msg[] = "Crash dump id: "; + WriteLog(msg, sizeof(msg) - 1); + WriteLog(buf, my_strlen(buf)); + WriteNewline(); + + // Write crash dump id to crash log as: seconds_since_epoch,crash_id + struct kernel_timeval tv; + if (g_crash_log_path && !sys_gettimeofday(&tv, nullptr)) { + uint64_t time = kernel_timeval_to_ms(&tv) / 1000; + char time_str[kUint64StringSize]; + const unsigned time_len = my_uint64_len(time); + my_uint64tos(time_str, time, time_len); + + const int kLogOpenFlags = O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC; + int log_fd = sys_open(g_crash_log_path, kLogOpenFlags, 0600); + if (log_fd > 0) { + sys_write(log_fd, time_str, time_len); + sys_write(log_fd, ",", 1); + sys_write(log_fd, buf, my_strlen(buf)); + sys_write(log_fd, "\n", 1); + IGNORE_RET(sys_close(log_fd)); + } + } +#endif +} + +#if defined(OS_CHROMEOS) +const char* GetCrashingProcessName(const BreakpadInfo& info, + google_breakpad::PageAllocator* allocator) { + // Symlink to process binary is at /proc/###/exe. + char linkpath[kUint64StringSize + sizeof("/proc/") + sizeof("/exe")] = + "/proc/"; + uint64_t pid_value_len = my_uint64_len(info.pid); + my_uint64tos(linkpath + sizeof("/proc/") - 1, info.pid, pid_value_len); + linkpath[sizeof("/proc/") - 1 + pid_value_len] = '\0'; + my_strlcat(linkpath, "/exe", sizeof(linkpath)); + + const int kMaxSize = 4096; + char* link = reinterpret_cast<char*>(allocator->Alloc(kMaxSize)); + if (link) { + ssize_t size = readlink(linkpath, link, kMaxSize); + if (size < kMaxSize && size > 0) { + // readlink(2) doesn't add a terminating NUL, so do it now. + link[size] = '\0'; + + const char* name = my_strrchr(link, '/'); + if (name) + return name + 1; + return link; + } + } + // Either way too long, or a read error. + return "chrome-crash-unknown-process"; +} +#endif + +void HandleCrashDump(const BreakpadInfo& info) { + int dumpfd; + bool keep_fd = false; + size_t dump_size; + uint8_t* dump_data; + google_breakpad::PageAllocator allocator; + const char* exe_buf = nullptr; + + if (GetCrashReporterClient()->HandleCrashDump(info.filename)) { + return; + } + +#if defined(OS_CHROMEOS) + // Grab the crashing process' name now, when it should still be available. + // If we try to do this later in our grandchild the crashing process has + // already terminated. + exe_buf = GetCrashingProcessName(info, &allocator); +#endif + + if (info.fd != -1) { + // Dump is provided with an open FD. + keep_fd = true; + dumpfd = info.fd; + + // The FD is pointing to the end of the file. + // Rewind, we'll read the data next. + if (lseek(dumpfd, 0, SEEK_SET) == -1) { + static const char msg[] = "Cannot upload crash dump: failed to " + "reposition minidump FD\n"; + WriteLog(msg, sizeof(msg) - 1); + IGNORE_RET(sys_close(dumpfd)); + return; + } + LoadDataFromFD(allocator, info.fd, false, &dump_data, &dump_size); + } else { + // Dump is provided with a path. + keep_fd = false; + LoadDataFromFile(allocator, info.filename, &dumpfd, &dump_data, &dump_size); + } + + // TODO(jcivelli): make log work when using FDs. +#if defined(ADDRESS_SANITIZER) + int logfd; + size_t log_size; + uint8_t* log_data; + // Load the AddressSanitizer log into log_data. + LoadDataFromFile(allocator, info.log_filename, &logfd, &log_data, &log_size); +#endif + + // We need to build a MIME block for uploading to the server. Since we are + // going to fork and run wget, it needs to be written to a temp file. + const int ufd = sys_open("/dev/urandom", O_RDONLY, 0); + if (ufd < 0) { + static const char msg[] = "Cannot upload crash dump because /dev/urandom" + " is missing\n"; + WriteLog(msg, sizeof(msg) - 1); + return; + } + + static const char temp_file_template[] = + "/tmp/chromium-upload-XXXXXXXXXXXXXXXX"; + char temp_file[sizeof(temp_file_template)]; + int temp_file_fd = -1; + if (keep_fd) { + temp_file_fd = dumpfd; + // Rewind the destination, we are going to overwrite it. + if (lseek(dumpfd, 0, SEEK_SET) == -1) { + static const char msg[] = "Cannot upload crash dump: failed to " + "reposition minidump FD (2)\n"; + WriteLog(msg, sizeof(msg) - 1); + IGNORE_RET(sys_close(dumpfd)); + return; + } + } else { + if (info.upload) { + my_memcpy(temp_file, temp_file_template, sizeof(temp_file_template)); + + for (unsigned i = 0; i < 10; ++i) { + uint64_t t; + sys_read(ufd, &t, sizeof(t)); + write_uint64_hex(temp_file + sizeof(temp_file) - (16 + 1), t); + + temp_file_fd = sys_open(temp_file, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (temp_file_fd >= 0) + break; + } + + if (temp_file_fd < 0) { + static const char msg[] = "Failed to create temporary file in /tmp: " + "cannot upload crash dump\n"; + WriteLog(msg, sizeof(msg) - 1); + IGNORE_RET(sys_close(ufd)); + return; + } + } else { + temp_file_fd = sys_open(info.filename, O_WRONLY, 0600); + if (temp_file_fd < 0) { + static const char msg[] = "Failed to save crash dump: failed to open\n"; + WriteLog(msg, sizeof(msg) - 1); + IGNORE_RET(sys_close(ufd)); + return; + } + } + } + + // The MIME boundary is 28 hyphens, followed by a 64-bit nonce and a NUL. + char mime_boundary[28 + 16 + 1]; + my_memset(mime_boundary, '-', 28); + uint64_t boundary_rand; + sys_read(ufd, &boundary_rand, sizeof(boundary_rand)); + write_uint64_hex(mime_boundary + 28, boundary_rand); + mime_boundary[28 + 16] = 0; + IGNORE_RET(sys_close(ufd)); + + // The MIME block looks like this: + // BOUNDARY \r\n + // Content-Disposition: form-data; name="prod" \r\n \r\n + // Chrome_Linux \r\n + // BOUNDARY \r\n + // Content-Disposition: form-data; name="ver" \r\n \r\n + // 1.2.3.4 \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="ptime" \r\n \r\n + // abcdef \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="ptype" \r\n \r\n + // abcdef \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="lsb-release" \r\n \r\n + // abcdef \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="oom-size" \r\n \r\n + // 1234567890 \r\n + // BOUNDARY \r\n + // + // zero or more (up to CrashKeyStorage::num_entries = 64): + // Content-Disposition: form-data; name=crash-key-name \r\n + // crash-key-value \r\n + // BOUNDARY \r\n + // + // Content-Disposition: form-data; name="dump"; filename="dump" \r\n + // Content-Type: application/octet-stream \r\n \r\n + // <dump contents> + // \r\n BOUNDARY -- \r\n + +#if defined(OS_CHROMEOS) + CrashReporterWriter writer(temp_file_fd); +#else + MimeWriter writer(temp_file_fd, mime_boundary); +#endif + { + const char* product_name = ""; + const char* version = ""; + + GetCrashReporterClient()->GetProductNameAndVersion(&product_name, &version); + + writer.AddBoundary(); + writer.AddPairString("prod", product_name); + writer.AddBoundary(); + writer.AddPairString("ver", version); + writer.AddBoundary(); + if (info.pid > 0) { + char pid_value_buf[kUint64StringSize]; + uint64_t pid_value_len = my_uint64_len(info.pid); + my_uint64tos(pid_value_buf, info.pid, pid_value_len); + static const char pid_key_name[] = "pid"; + writer.AddPairData(pid_key_name, sizeof(pid_key_name) - 1, + pid_value_buf, pid_value_len); + writer.AddBoundary(); + } +#if defined(OS_ANDROID) + // Addtional MIME blocks are added for logging on Android devices. + static const char android_build_id[] = "android_build_id"; + static const char android_build_fp[] = "android_build_fp"; + static const char device[] = "device"; + static const char model[] = "model"; + static const char brand[] = "brand"; + static const char exception_info[] = "exception_info"; + + base::android::BuildInfo* android_build_info = + base::android::BuildInfo::GetInstance(); + writer.AddPairString( + android_build_id, android_build_info->android_build_id()); + writer.AddBoundary(); + writer.AddPairString( + android_build_fp, android_build_info->android_build_fp()); + writer.AddBoundary(); + writer.AddPairString(device, android_build_info->device()); + writer.AddBoundary(); + writer.AddPairString(model, android_build_info->model()); + writer.AddBoundary(); + writer.AddPairString(brand, android_build_info->brand()); + writer.AddBoundary(); + if (android_build_info->java_exception_info() != nullptr) { + writer.AddPairString(exception_info, + android_build_info->java_exception_info()); + writer.AddBoundary(); + } +#endif + writer.Flush(); + } + + if (info.process_start_time > 0) { + struct kernel_timeval tv; + if (!sys_gettimeofday(&tv, nullptr)) { + uint64_t time = kernel_timeval_to_ms(&tv); + if (time > info.process_start_time) { + time -= info.process_start_time; + char time_str[kUint64StringSize]; + const unsigned time_len = my_uint64_len(time); + my_uint64tos(time_str, time, time_len); + + static const char process_time_msg[] = "ptime"; + writer.AddPairData(process_time_msg, sizeof(process_time_msg) - 1, + time_str, time_len); + writer.AddBoundary(); + writer.Flush(); + } + } + } + + if (info.process_type_length) { + writer.AddPairString("ptype", info.process_type); + writer.AddBoundary(); + writer.Flush(); + } + + if (info.distro_length) { + static const char distro_msg[] = "lsb-release"; + writer.AddPairString(distro_msg, info.distro); + writer.AddBoundary(); + writer.Flush(); + } + + if (info.oom_size) { + char oom_size_str[kUint64StringSize]; + const unsigned oom_size_len = my_uint64_len(info.oom_size); + my_uint64tos(oom_size_str, info.oom_size, oom_size_len); + static const char oom_size_msg[] = "oom-size"; + writer.AddPairData(oom_size_msg, sizeof(oom_size_msg) - 1, + oom_size_str, oom_size_len); + writer.AddBoundary(); + writer.Flush(); + } + + if (info.crash_keys) { + CrashKeyStorage::Iterator crash_key_iterator(*info.crash_keys); + const CrashKeyStorage::Entry* entry; + while ((entry = crash_key_iterator.Next())) { + writer.AddPairString(entry->key, entry->value); + writer.AddBoundary(); + writer.Flush(); + } + } + + writer.AddFileContents(g_dump_msg, dump_data, dump_size); +#if defined(ADDRESS_SANITIZER) + // Append a multipart boundary and the contents of the AddressSanitizer log. + writer.AddBoundary(); + writer.AddFileContents(g_log_msg, log_data, log_size); +#endif + writer.AddEnd(); + writer.Flush(); + + IGNORE_RET(sys_close(temp_file_fd)); + +#if defined(OS_ANDROID) + if (info.filename) { + size_t filename_length = my_strlen(info.filename); + + // If this was a file, we need to copy it to the right place and use the + // right file name so it gets uploaded by the browser. + const char msg[] = "Output crash dump file:"; + WriteLog(msg, sizeof(msg) - 1); + WriteLog(info.filename, filename_length); + + char pid_buf[kUint64StringSize]; + size_t pid_str_length = my_uint64_len(info.pid); + my_uint64tos(pid_buf, info.pid, pid_str_length); + pid_buf[pid_str_length] = 0; // my_uint64tos() doesn't null-terminate. + + size_t done_filename_len = filename_length + pid_str_length + 1; + char* done_filename = reinterpret_cast<char*>( + allocator.Alloc(done_filename_len)); + // Rename the file such that the pid is the suffix in order signal to other + // processes that the minidump is complete. The advantage of using the pid + // as the suffix is that it is trivial to associate the minidump with the + // crashed process. + my_strlcpy(done_filename, info.filename, done_filename_len); + my_strlcat(done_filename, pid_buf, done_filename_len); + // Rename the minidump file to signal that it is complete. + if (rename(info.filename, done_filename)) { + const char failed_msg[] = "Failed to rename:"; + WriteLog(failed_msg, sizeof(failed_msg) - 1); + WriteLog(info.filename, filename_length); + const char to_msg[] = "to"; + WriteLog(to_msg, sizeof(to_msg) - 1); + WriteLog(done_filename, done_filename_len - 1); + } + } +#endif + + if (!info.upload) + return; + + const pid_t child = sys_fork(); + if (!child) { + // Spawned helper process. + // + // This code is called both when a browser is crashing (in which case, + // nothing really matters any more) and when a renderer/plugin crashes, in + // which case we need to continue. + // + // Since we are a multithreaded app, if we were just to fork(), we might + // grab file descriptors which have just been created in another thread and + // hold them open for too long. + // + // Thus, we have to loop and try and close everything. + const int fd = sys_open("/proc/self/fd", O_DIRECTORY | O_RDONLY, 0); + if (fd < 0) { + for (unsigned i = 3; i < 8192; ++i) + IGNORE_RET(sys_close(i)); + } else { + google_breakpad::DirectoryReader reader(fd); + const char* name; + while (reader.GetNextEntry(&name)) { + int i; + if (my_strtoui(&i, name) && i > 2 && i != fd) + IGNORE_RET(sys_close(i)); + reader.PopEntry(); + } + + IGNORE_RET(sys_close(fd)); + } + + IGNORE_RET(sys_setsid()); + + // Leave one end of a pipe in the upload process and watch for it getting + // closed by the upload process exiting. + int fds[2]; + if (sys_pipe(fds) >= 0) { + const pid_t upload_child = sys_fork(); + if (!upload_child) { + // Upload process. + IGNORE_RET(sys_close(fds[0])); + IGNORE_RET(sys_dup2(fds[1], 3)); + ExecUploadProcessOrTerminate(info, temp_file, mime_boundary, exe_buf, + &allocator); + } + + // Helper process. + if (upload_child > 0) { + IGNORE_RET(sys_close(fds[1])); + + const size_t kCrashIdLength = 16; + char id_buf[kCrashIdLength + 1]; + size_t bytes_read = + WaitForCrashReportUploadProcess(fds[0], kCrashIdLength, id_buf); + HandleCrashReportId(id_buf, bytes_read, kCrashIdLength); + + if (sys_waitpid(upload_child, nullptr, WNOHANG) == 0) { + // Upload process is still around, kill it. + sys_kill(upload_child, SIGKILL); + } + } + } + + // Helper process. + IGNORE_RET(sys_unlink(info.filename)); +#if defined(ADDRESS_SANITIZER) + IGNORE_RET(sys_unlink(info.log_filename)); +#endif + IGNORE_RET(sys_unlink(temp_file)); + sys__exit(0); + } + + // Main browser process. + if (child <= 0) + return; + (void) HANDLE_EINTR(sys_waitpid(child, nullptr, 0)); +} + +void InitCrashReporter(const std::string& process_type) { +#if defined(OS_ANDROID) + // This will guarantee that the BuildInfo has been initialized and subsequent + // calls will not require memory allocation. + base::android::BuildInfo::GetInstance(); + + // Handler registration is LIFO. Install the microdump handler first, such + // that if conventional minidump crash reporting is enabled below, it takes + // precedence (i.e. its handler is run first) over the microdump handler. + InitMicrodumpCrashHandlerIfNecessary(process_type); +#endif + // Determine the process type and take appropriate action. + const base::CommandLine& parsed_command_line = + *base::CommandLine::ForCurrentProcess(); + if (parsed_command_line.HasSwitch(switches::kDisableBreakpad)) + return; + + if (process_type.empty()) { + bool enable_breakpad = GetCrashReporterClient()->GetCollectStatsConsent() || + GetCrashReporterClient()->IsRunningUnattended(); + enable_breakpad &= + !parsed_command_line.HasSwitch(switches::kDisableBreakpad); + if (!enable_breakpad) { + enable_breakpad = parsed_command_line.HasSwitch( + switches::kEnableCrashReporterForTesting); + } + if (!enable_breakpad) { + VLOG(1) << "Breakpad disabled"; + return; + } + + InitCrashKeys(); + EnableCrashDumping(GetCrashReporterClient()->IsRunningUnattended()); + } else if (GetCrashReporterClient()->EnableBreakpadForProcess(process_type)) { +#if defined(OS_ANDROID) + NOTREACHED() << "Breakpad initialized with InitCrashReporter() instead of " + "InitNonBrowserCrashReporter in " << process_type << " process."; + return; +#else + // We might be chrooted in a zygote or renderer process so we cannot call + // GetCollectStatsConsent because that needs access the the user's home + // dir. Instead, we set a command line flag for these processes. + // Even though plugins are not chrooted, we share the same code path for + // simplicity. + if (!parsed_command_line.HasSwitch(switches::kEnableCrashReporter)) + return; + InitCrashKeys(); + SetClientIdFromCommandLine(parsed_command_line); + EnableNonBrowserCrashDumping(); + VLOG(1) << "Non Browser crash dumping enabled for: " << process_type; +#endif // #if defined(OS_ANDROID) + } + + PostEnableBreakpadInitialization(); +} + +#if defined(OS_ANDROID) +void InitNonBrowserCrashReporterForAndroid(const std::string& process_type) { + const base::CommandLine* command_line = + base::CommandLine::ForCurrentProcess(); + + // Handler registration is LIFO. Install the microdump handler first, such + // that if conventional minidump crash reporting is enabled below, it takes + // precedence (i.e. its handler is run first) over the microdump handler. + InitMicrodumpCrashHandlerIfNecessary(process_type); + + if (command_line->HasSwitch(switches::kEnableCrashReporter)) { + // On Android we need to provide a FD to the file where the minidump is + // generated as the renderer and browser run with different UIDs + // (preventing the browser from inspecting the renderer process). + int minidump_fd = base::GlobalDescriptors::GetInstance()->MaybeGet( + GetCrashReporterClient()->GetAndroidMinidumpDescriptor()); + if (minidump_fd < 0) { + NOTREACHED() << "Could not find minidump FD, crash reporting disabled."; + } else { + InitCrashKeys(); + EnableNonBrowserCrashDumping(process_type, minidump_fd); + } + } +} +#endif // OS_ANDROID + +bool IsCrashReporterEnabled() { + return g_is_crash_reporter_enabled; +} + +} // namespace breakpad diff --git a/components/crash/content/app/breakpad_linux.h b/components/crash/content/app/breakpad_linux.h new file mode 100644 index 0000000..4bfe0ce --- /dev/null +++ b/components/crash/content/app/breakpad_linux.h @@ -0,0 +1,32 @@ +// 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. + +// Public interface for enabling Breakpad on Linux systems. + +#ifndef COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_LINUX_H_ +#define COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_LINUX_H_ + +#include <string> + +#include "build/build_config.h" + +namespace breakpad { + +// Turns on the crash reporter in any process. +extern void InitCrashReporter(const std::string& process_type); + +// Enables the crash reporter in child processes. +#if defined(OS_ANDROID) +extern void InitNonBrowserCrashReporterForAndroid( + const std::string& process_type); +#endif + +// Checks if crash reporting is enabled. Note that this is not the same as +// being opted into metrics reporting (and crash reporting), which controls +// whether InitCrashReporter() is called. +bool IsCrashReporterEnabled(); + +} // namespace breakpad + +#endif // COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_LINUX_H_ diff --git a/components/crash/content/app/breakpad_linux_impl.h b/components/crash/content/app/breakpad_linux_impl.h new file mode 100644 index 0000000..3c94d72 --- /dev/null +++ b/components/crash/content/app/breakpad_linux_impl.h @@ -0,0 +1,65 @@ +// 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. + +// Internal header file for the Linux breakpad implementation. This file is +// shared between crash_handler_host_linux.cc and breakpad_linux.cc. It should +// only be used in files compiled with linux_breakpad=1. + +#ifndef COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_LINUX_IMPL_H_ +#define COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_LINUX_IMPL_H_ + +#include <sys/types.h> + +#include "base/basictypes.h" +#include "breakpad/src/common/simple_string_dictionary.h" +#include "components/crash/content/app/breakpad_linux.h" + +namespace breakpad { + +typedef google_breakpad::NonAllocatingMap<256, 256, 64> CrashKeyStorage; + +#if defined(ADDRESS_SANITIZER) +static const size_t kMaxAsanReportSize = 1 << 16; +#endif +// Define a preferred limit on minidump sizes, because Crash Server currently +// throws away any larger than 1.2MB (1.2 * 1024 * 1024). A value of -1 means +// no limit. +static const off_t kMaxMinidumpFileSize = 1258291; + +// The size of the iovec used to transfer crash data from a child back to the +// browser. +#if !defined(ADDRESS_SANITIZER) +const size_t kCrashIovSize = 6; +#else +// Additional field to pass the AddressSanitizer log to the crash handler. +const size_t kCrashIovSize = 7; +#endif + +// BreakpadInfo describes a crash report. +// The minidump information can either be contained in a file descriptor (fd) or +// in a file (whose path is in filename). +struct BreakpadInfo { + int fd; // File descriptor to the Breakpad dump data. + const char* filename; // Path to the Breakpad dump data. +#if defined(ADDRESS_SANITIZER) + const char* log_filename; // Path to the ASan log file. + const char* asan_report_str; // ASan report. + unsigned asan_report_length; // Length of |asan_report_length|. +#endif + const char* process_type; // Process type, e.g. "renderer". + unsigned process_type_length; // Length of |process_type|. + const char* distro; // Linux distro string. + unsigned distro_length; // Length of |distro|. + bool upload; // Whether to upload or save crash dump. + uint64_t process_start_time; // Uptime of the crashing process. + size_t oom_size; // Amount of memory requested if OOM. + uint64_t pid; // PID where applicable. + CrashKeyStorage* crash_keys; +}; + +extern void HandleCrashDump(const BreakpadInfo& info); + +} // namespace breakpad + +#endif // COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_LINUX_IMPL_H_ diff --git a/components/crash/content/app/breakpad_mac.h b/components/crash/content/app/breakpad_mac.h new file mode 100644 index 0000000..d392e13 --- /dev/null +++ b/components/crash/content/app/breakpad_mac.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_MAC_H_ +#define COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_MAC_H_ + +#include <string> + +// This header defines the entry points for Breakpad integration. + +namespace breakpad { + +// Initializes Breakpad. +void InitCrashReporter(const std::string& process_type); + +// Give Breakpad a chance to store information about the current process. +// Extra information requires a parsed command line, so call this after +// CommandLine::Init has been called. +void InitCrashProcessInfo(const std::string& process_type_switch); + +// Is Breakpad enabled? +bool IsCrashReporterEnabled(); + +} // namespace breakpad + +#endif // COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_MAC_H_ diff --git a/components/crash/content/app/breakpad_mac.mm b/components/crash/content/app/breakpad_mac.mm new file mode 100644 index 0000000..84ef742 --- /dev/null +++ b/components/crash/content/app/breakpad_mac.mm @@ -0,0 +1,286 @@ +// 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. + +#import "components/crash/content/app/breakpad_mac.h" + +#include <CoreFoundation/CoreFoundation.h> +#import <Foundation/Foundation.h> + +#include "base/auto_reset.h" +#include "base/base_switches.h" +#import "base/basictypes.h" +#include "base/command_line.h" +#include "base/debug/crash_logging.h" +#include "base/debug/dump_without_crashing.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#import "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/foundation_util.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#import "base/mac/scoped_nsautorelease_pool.h" +#include "base/strings/sys_string_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" +#import "breakpad/src/client/mac/Framework/Breakpad.h" +#include "components/crash/content/app/crash_reporter_client.h" + +using crash_reporter::GetCrashReporterClient; + +namespace breakpad { + +namespace { + +BreakpadRef gBreakpadRef = NULL; + +void SetCrashKeyValue(NSString* key, NSString* value) { + // Comment repeated from header to prevent confusion: + // IMPORTANT: On OS X, the key/value pairs are sent to the crash server + // out of bounds and not recorded on disk in the minidump, this means + // that if you look at the minidump file locally you won't see them! + if (gBreakpadRef == NULL) { + return; + } + + BreakpadAddUploadParameter(gBreakpadRef, key, value); +} + +void ClearCrashKeyValue(NSString* key) { + if (gBreakpadRef == NULL) { + return; + } + + BreakpadRemoveUploadParameter(gBreakpadRef, key); +} + +void SetCrashKeyValueImpl(const base::StringPiece& key, + const base::StringPiece& value) { + @autoreleasepool { + SetCrashKeyValue(base::SysUTF8ToNSString(key.as_string()), + base::SysUTF8ToNSString(value.as_string())); + } +} + +void ClearCrashKeyValueImpl(const base::StringPiece& key) { + @autoreleasepool { + ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string())); + } +} + +bool FatalMessageHandler(int severity, const char* file, int line, + size_t message_start, const std::string& str) { + // Do not handle non-FATAL. + if (severity != logging::LOG_FATAL) + return false; + + // In case of OOM condition, this code could be reentered when + // constructing and storing the key. Using a static is not + // thread-safe, but if multiple threads are in the process of a + // fatal crash at the same time, this should work. + static bool guarded = false; + if (guarded) + return false; + + base::AutoReset<bool> guard(&guarded, true); + + // Only log last path component. This matches logging.cc. + if (file) { + const char* slash = strrchr(file, '/'); + if (slash) + file = slash + 1; + } + + NSString* fatal_key = @"LOG_FATAL"; + NSString* fatal_value = + [NSString stringWithFormat:@"%s:%d: %s", + file, line, str.c_str() + message_start]; + SetCrashKeyValue(fatal_key, fatal_value); + + // Rather than including the code to force the crash here, allow the + // caller to do it. + return false; +} + +// BreakpadGenerateAndSendReport() does not report the current +// thread. This class can be used to spin up a thread to run it. +class DumpHelper : public base::PlatformThread::Delegate { + public: + static void DumpWithoutCrashing() { + DumpHelper dumper; + base::PlatformThreadHandle handle; + if (base::PlatformThread::Create(0, &dumper, &handle)) { + // The entire point of this is to block so that the correct + // stack is logged. + base::ThreadRestrictions::ScopedAllowIO allow_io; + base::PlatformThread::Join(handle); + } + } + + private: + DumpHelper() {} + + void ThreadMain() override { + base::PlatformThread::SetName("CrDumpHelper"); + BreakpadGenerateAndSendReport(gBreakpadRef); + } + + DISALLOW_COPY_AND_ASSIGN(DumpHelper); +}; + +void SIGABRTHandler(int signal) { + // The OSX abort() (link below) masks all signals for the process, + // and all except SIGABRT for the thread. SIGABRT will be masked + // when the SIGABRT is sent, which means at this point only SIGKILL + // and SIGSTOP can be delivered. Unmask others so that the code + // below crashes as desired. + // + // http://www.opensource.apple.com/source/Libc/Libc-825.26/stdlib/FreeBSD/abort.c + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, signal); + pthread_sigmask(SIG_SETMASK, &mask, NULL); + + // Most interesting operations are not safe in a signal handler, just crash. + char* volatile death_ptr = NULL; + *death_ptr = '!'; +} + +} // namespace + +bool IsCrashReporterEnabled() { + return gBreakpadRef != NULL; +} + +// Only called for a branded build of Chrome.app. +void InitCrashReporter(const std::string& process_type) { + DCHECK(!gBreakpadRef); + base::mac::ScopedNSAutoreleasePool autorelease_pool; + + // Check whether crash reporting should be enabled. If enterprise + // configuration management controls crash reporting, it takes precedence. + // Otherwise, check whether the user has consented to stats and crash + // reporting. The browser process can make this determination directly. + // Helper processes may not have access to the disk or to the same data as + // the browser process, so the browser passes the decision to them on the + // command line. + NSBundle* main_bundle = base::mac::FrameworkBundle(); + bool is_browser = !base::mac::IsBackgroundOnlyProcess(); + bool enable_breakpad = false; + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + + if (is_browser) { + // Since the configuration management infrastructure is possibly not + // initialized when this code runs, read the policy preference directly. + if (!GetCrashReporterClient()->ReportingIsEnforcedByPolicy( + &enable_breakpad)) { + // Controlled by the user. The crash reporter may be enabled by + // preference or through an environment variable, but the kDisableBreakpad + // switch overrides both. + enable_breakpad = GetCrashReporterClient()->GetCollectStatsConsent() || + GetCrashReporterClient()->IsRunningUnattended(); + enable_breakpad &= !command_line->HasSwitch(switches::kDisableBreakpad); + } + } else { + // This is a helper process, check the command line switch. + enable_breakpad = command_line->HasSwitch(switches::kEnableCrashReporter); + } + + if (!enable_breakpad) { + VLOG_IF(1, is_browser) << "Breakpad disabled"; + return; + } + + // Tell Breakpad where crash_inspector and crash_report_sender are. + NSString* resource_path = [main_bundle resourcePath]; + NSString *inspector_location = + [resource_path stringByAppendingPathComponent:@"crash_inspector"]; + NSString *reporter_bundle_location = + [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"]; + NSString *reporter_location = + [[NSBundle bundleWithPath:reporter_bundle_location] executablePath]; + + if (!inspector_location || !reporter_location) { + VLOG_IF(1, is_browser && base::mac::AmIBundled()) << "Breakpad disabled"; + return; + } + + NSDictionary* info_dictionary = [main_bundle infoDictionary]; + NSMutableDictionary *breakpad_config = + [[info_dictionary mutableCopy] autorelease]; + [breakpad_config setObject:inspector_location + forKey:@BREAKPAD_INSPECTOR_LOCATION]; + [breakpad_config setObject:reporter_location + forKey:@BREAKPAD_REPORTER_EXE_LOCATION]; + + // In the main application (the browser process), crashes can be passed to + // the system's Crash Reporter. This allows the system to notify the user + // when the application crashes, and provide the user with the option to + // restart it. + if (is_browser) + [breakpad_config setObject:@"NO" forKey:@BREAKPAD_SEND_AND_EXIT]; + + base::FilePath dir_crash_dumps; + GetCrashReporterClient()->GetCrashDumpLocation(&dir_crash_dumps); + [breakpad_config setObject:base::SysUTF8ToNSString(dir_crash_dumps.value()) + forKey:@BREAKPAD_DUMP_DIRECTORY]; + + // Temporarily run Breakpad in-process on 10.10 and later because APIs that + // it depends on got broken (http://crbug.com/386208). + // This can catch crashes in the browser process only. + if (is_browser && base::mac::IsOSYosemiteOrLater()) { + [breakpad_config setObject:[NSNumber numberWithBool:YES] + forKey:@BREAKPAD_IN_PROCESS]; + } + + // Initialize Breakpad. + gBreakpadRef = BreakpadCreate(breakpad_config); + if (!gBreakpadRef) { + LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initialization failed"; + return; + } + + // Initialize the scoped crash key system. + base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl, + &ClearCrashKeyValueImpl); + GetCrashReporterClient()->RegisterCrashKeys(); + + // Set Breakpad metadata values. These values are added to Info.plist during + // the branded Google Chrome.app build. + SetCrashKeyValue(@"ver", [info_dictionary objectForKey:@BREAKPAD_VERSION]); + SetCrashKeyValue(@"prod", [info_dictionary objectForKey:@BREAKPAD_PRODUCT]); + SetCrashKeyValue(@"plat", @"OS X"); + + logging::SetLogMessageHandler(&FatalMessageHandler); + base::debug::SetDumpWithoutCrashingFunction(&DumpHelper::DumpWithoutCrashing); + + // abort() sends SIGABRT, which breakpad does not intercept. + // Register a signal handler to crash in a way breakpad will + // intercept. + struct sigaction sigact; + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_handler = SIGABRTHandler; + CHECK(0 == sigaction(SIGABRT, &sigact, NULL)); +} + +void InitCrashProcessInfo(const std::string& process_type_switch) { + if (gBreakpadRef == NULL) { + return; + } + + // Determine the process type. + NSString* process_type = @"browser"; + if (!process_type_switch.empty()) { + process_type = base::SysUTF8ToNSString(process_type_switch); + } + + // Store process type in crash dump. + SetCrashKeyValue(@"ptype", process_type); + + NSString* pid_value = + [NSString stringWithFormat:@"%d", static_cast<unsigned int>(getpid())]; + SetCrashKeyValue(@"pid", pid_value); +} + +} // namespace breakpad diff --git a/components/crash/content/app/breakpad_mac_stubs.mm b/components/crash/content/app/breakpad_mac_stubs.mm new file mode 100644 index 0000000..329794b --- /dev/null +++ b/components/crash/content/app/breakpad_mac_stubs.mm @@ -0,0 +1,24 @@ +// 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. + +#import "components/crash/content/app/breakpad_mac.h" + +#import <Foundation/Foundation.h> + +// Stubbed out versions of breakpad integration functions so we can compile +// without linking in Breakpad. + +namespace breakpad { + +bool IsCrashReporterEnabled() { + return false; +} + +void InitCrashProcessInfo(const std::string& process_type_switch) { +} + +void InitCrashReporter(const std::string& process_type) { +} + +} // namespace breakpad diff --git a/components/crash/content/app/breakpad_win.cc b/components/crash/content/app/breakpad_win.cc new file mode 100644 index 0000000..5b53ff7 --- /dev/null +++ b/components/crash/content/app/breakpad_win.cc @@ -0,0 +1,745 @@ +// 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. + +#include "components/crash/content/app/breakpad_win.h" + +#include <windows.h> +#include <shellapi.h> +#include <tchar.h> +#include <userenv.h> +#include <winnt.h> + +#include <algorithm> +#include <map> +#include <vector> + +#include "base/base_switches.h" +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/debug/crash_logging.h" +#include "base/debug/dump_without_crashing.h" +#include "base/environment.h" +#include "base/memory/scoped_ptr.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string16.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/lock.h" +#include "base/win/metro.h" +#include "base/win/pe_image.h" +#include "base/win/registry.h" +#include "base/win/win_util.h" +#include "breakpad/src/client/windows/handler/exception_handler.h" +#include "components/crash/content/app/crash_keys_win.h" +#include "components/crash/content/app/crash_reporter_client.h" +#include "components/crash/content/app/hard_error_handler_win.h" +#include "content/public/common/result_codes.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sidestep/preamble_patcher.h" + +// userenv.dll is required for GetProfileType(). +#pragma comment(lib, "userenv.lib") + +#pragma intrinsic(_AddressOfReturnAddress) +#pragma intrinsic(_ReturnAddress) + +#ifdef _WIN64 +// See http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx +typedef struct _UNWIND_INFO { + unsigned char Version : 3; + unsigned char Flags : 5; + unsigned char SizeOfProlog; + unsigned char CountOfCodes; + unsigned char FrameRegister : 4; + unsigned char FrameOffset : 4; + ULONG ExceptionHandler; +} UNWIND_INFO, *PUNWIND_INFO; +#endif + +namespace breakpad { + +using crash_reporter::GetCrashReporterClient; + +namespace { + +// Minidump with stacks, PEB, TEB, and unloaded module list. +const MINIDUMP_TYPE kSmallDumpType = static_cast<MINIDUMP_TYPE>( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules); // Get unloaded modules when available. + +// Minidump with all of the above, plus memory referenced from stack. +const MINIDUMP_TYPE kLargerDumpType = static_cast<MINIDUMP_TYPE>( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules | // Get unloaded modules when available. + MiniDumpWithIndirectlyReferencedMemory); // Get memory referenced by stack. + +// Large dump with all process memory. +const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>( + MiniDumpWithFullMemory | // Full memory from process. + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithHandleData | // Get all handle information. + MiniDumpWithUnloadedModules); // Get unloaded modules when available. + +const char kPipeNameVar[] = "CHROME_BREAKPAD_PIPE_NAME"; + +const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\"; +const wchar_t kChromePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; + +// This is the well known SID for the system principal. +const wchar_t kSystemPrincipalSid[] =L"S-1-5-18"; + +google_breakpad::ExceptionHandler* g_breakpad = NULL; +google_breakpad::ExceptionHandler* g_dumphandler_no_crash = NULL; + +#if !defined(_WIN64) +EXCEPTION_POINTERS g_surrogate_exception_pointers = {0}; +EXCEPTION_RECORD g_surrogate_exception_record = {0}; +CONTEXT g_surrogate_context = {0}; +#endif // !defined(_WIN64) + +typedef NTSTATUS (WINAPI* NtTerminateProcessPtr)(HANDLE ProcessHandle, + NTSTATUS ExitStatus); +char* g_real_terminate_process_stub = NULL; + +} // namespace + +// Dumps the current process memory. +extern "C" void __declspec(dllexport) __cdecl DumpProcess() { + if (g_breakpad) { + g_breakpad->WriteMinidump(); + } +} + +// Used for dumping a process state when there is no crash. +extern "C" void __declspec(dllexport) __cdecl DumpProcessWithoutCrash() { + if (g_dumphandler_no_crash) { + g_dumphandler_no_crash->WriteMinidump(); + } +} + +namespace { + +// We need to prevent ICF from folding DumpForHangDebuggingThread() and +// DumpProcessWithoutCrashThread() together, since that makes them +// indistinguishable in crash dumps. We do this by making the function +// bodies unique, and prevent optimization from shuffling things around. +MSVC_DISABLE_OPTIMIZE() +MSVC_PUSH_DISABLE_WARNING(4748) + +DWORD WINAPI DumpProcessWithoutCrashThread(void*) { + DumpProcessWithoutCrash(); + return 0; +} + +// The following two functions do exactly the same thing as the two above. But +// we want the signatures to be different so that we can easily track them in +// crash reports. +// TODO(yzshen): Remove when enough information is collected and the hang rate +// of pepper/renderer processes is reduced. +DWORD WINAPI DumpForHangDebuggingThread(void*) { + DumpProcessWithoutCrash(); + VLOG(1) << "dumped for hang debugging"; + return 0; +} + +MSVC_POP_WARNING() +MSVC_ENABLE_OPTIMIZE() + +} // namespace + +// Injects a thread into a remote process to dump state when there is no crash. +extern "C" HANDLE __declspec(dllexport) __cdecl +InjectDumpProcessWithoutCrash(HANDLE process) { + return CreateRemoteThread(process, NULL, 0, DumpProcessWithoutCrashThread, + 0, 0, NULL); +} + +extern "C" HANDLE __declspec(dllexport) __cdecl +InjectDumpForHangDebugging(HANDLE process) { + return CreateRemoteThread(process, NULL, 0, DumpForHangDebuggingThread, + 0, 0, NULL); +} + +// Returns a string containing a list of all modifiers for the loaded profile. +std::wstring GetProfileType() { + std::wstring profile_type; + DWORD profile_bits = 0; + if (::GetProfileType(&profile_bits)) { + static const struct { + DWORD bit; + const wchar_t* name; + } kBitNames[] = { + { PT_MANDATORY, L"mandatory" }, + { PT_ROAMING, L"roaming" }, + { PT_TEMPORARY, L"temporary" }, + }; + for (size_t i = 0; i < arraysize(kBitNames); ++i) { + const DWORD this_bit = kBitNames[i].bit; + if ((profile_bits & this_bit) != 0) { + profile_type.append(kBitNames[i].name); + profile_bits &= ~this_bit; + if (profile_bits != 0) + profile_type.append(L", "); + } + } + } else { + DWORD last_error = ::GetLastError(); + base::SStringPrintf(&profile_type, L"error %u", last_error); + } + return profile_type; +} + +namespace { + +// This callback is used when we want to get a dump without crashing the +// process. +bool DumpDoneCallbackWhenNoCrash(const wchar_t*, const wchar_t*, void*, + EXCEPTION_POINTERS* ex_info, + MDRawAssertionInfo*, bool succeeded) { + GetCrashReporterClient()->RecordCrashDumpAttemptResult( + false /* is_real_crash */, succeeded); + return true; +} + +// This callback is executed when the browser process has crashed, after +// the crash dump has been created. We need to minimize the amount of work +// done here since we have potentially corrupted process. Our job is to +// spawn another instance of chrome which will show a 'chrome has crashed' +// dialog. This code needs to live in the exe and thus has no access to +// facilities such as the i18n helpers. +bool DumpDoneCallback(const wchar_t*, const wchar_t*, void*, + EXCEPTION_POINTERS* ex_info, + MDRawAssertionInfo*, bool succeeded) { + GetCrashReporterClient()->RecordCrashDumpAttemptResult( + true /* is_real_crash */, succeeded); + // Check if the exception is one of the kind which would not be solved + // by simply restarting chrome. In this case we show a message box with + // and exit silently. Remember that chrome is in a crashed state so we + // can't show our own UI from this process. + if (HardErrorHandler(ex_info)) + return true; + + if (!GetCrashReporterClient()->AboutToRestart()) + return true; + + // Now we just start chrome browser with the same command line. + STARTUPINFOW si = {sizeof(si)}; + PROCESS_INFORMATION pi; + if (::CreateProcessW(NULL, ::GetCommandLineW(), NULL, NULL, FALSE, + CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &si, &pi)) { + ::CloseHandle(pi.hProcess); + ::CloseHandle(pi.hThread); + } + // After this return we will be terminated. The actual return value is + // not used at all. + return true; +} + +// flag to indicate that we are already handling an exception. +volatile LONG handling_exception = 0; + +// This callback is used when there is no crash. Note: Unlike the +// |FilterCallback| below this does not do dupe detection. It is upto the caller +// to implement it. +bool FilterCallbackWhenNoCrash( + void*, EXCEPTION_POINTERS*, MDRawAssertionInfo*) { + GetCrashReporterClient()->RecordCrashDumpAttempt(false); + return true; +} + +// This callback is executed when the Chrome process has crashed and *before* +// the crash dump is created. To prevent duplicate crash reports we +// make every thread calling this method, except the very first one, +// go to sleep. +bool FilterCallback(void*, EXCEPTION_POINTERS*, MDRawAssertionInfo*) { + // Capture every thread except the first one in the sleep. We don't + // want multiple threads to concurrently report exceptions. + if (::InterlockedCompareExchange(&handling_exception, 1, 0) == 1) { + ::Sleep(INFINITE); + } + GetCrashReporterClient()->RecordCrashDumpAttempt(true); + return true; +} + +// Previous unhandled filter. Will be called if not null when we +// intercept a crash. +LPTOP_LEVEL_EXCEPTION_FILTER previous_filter = NULL; + +// Exception filter used when breakpad is not enabled. We just display +// the "Do you want to restart" message and then we call the previous filter. +long WINAPI ChromeExceptionFilter(EXCEPTION_POINTERS* info) { + DumpDoneCallback(NULL, NULL, NULL, info, NULL, false); + + if (previous_filter) + return previous_filter(info); + + return EXCEPTION_EXECUTE_HANDLER; +} + +// Exception filter for the service process used when breakpad is not enabled. +// We just display the "Do you want to restart" message and then die +// (without calling the previous filter). +long WINAPI ServiceExceptionFilter(EXCEPTION_POINTERS* info) { + DumpDoneCallback(NULL, NULL, NULL, info, NULL, false); + return EXCEPTION_EXECUTE_HANDLER; +} + +#if !defined(COMPONENT_BUILD) +// Installed via base::debug::SetCrashKeyReportingFunctions. +void SetCrashKeyValueForBaseDebug(const base::StringPiece& key, + const base::StringPiece& value) { + DCHECK(CrashKeysWin::keeper()); + CrashKeysWin::keeper()->SetCrashKeyValue(base::UTF8ToUTF16(key), + base::UTF8ToUTF16(value)); +} + +// Installed via base::debug::SetCrashKeyReportingFunctions. +void ClearCrashKeyForBaseDebug(const base::StringPiece& key) { + DCHECK(CrashKeysWin::keeper()); + CrashKeysWin::keeper()->ClearCrashKeyValue(base::UTF8ToUTF16(key)); +} +#endif // !defined(COMPONENT_BUILD) + +} // namespace + +// NOTE: This function is used by SyzyASAN to annotate crash reports. If you +// change the name or signature of this function you will break SyzyASAN +// instrumented releases of Chrome. Please contact syzygy-team@chromium.org +// before doing so! +extern "C" void __declspec(dllexport) __cdecl SetCrashKeyValueImpl( + const wchar_t* key, const wchar_t* value) { + CrashKeysWin* keeper = CrashKeysWin::keeper(); + if (!keeper) + return; + + // TODO(siggi): This doesn't look quite right - there's NULL deref potential + // here, and an implicit std::wstring conversion. Fixme. + keeper->SetCrashKeyValue(key, value); +} + +extern "C" void __declspec(dllexport) __cdecl ClearCrashKeyValueImpl( + const wchar_t* key) { + CrashKeysWin* keeper = CrashKeysWin::keeper(); + if (!keeper) + return; + + // TODO(siggi): This doesn't look quite right - there's NULL deref potential + // here, and an implicit std::wstring conversion. Fixme. + keeper->ClearCrashKeyValue(key); +} + +static bool WrapMessageBoxWithSEH(const wchar_t* text, const wchar_t* caption, + UINT flags, bool* exit_now) { + // We wrap the call to MessageBoxW with a SEH handler because it some + // machines with CursorXP, PeaDict or with FontExplorer installed it crashes + // uncontrollably here. Being this a best effort deal we better go away. + __try { + *exit_now = (IDOK != ::MessageBoxW(NULL, text, caption, flags)); + } __except(EXCEPTION_EXECUTE_HANDLER) { + // Its not safe to continue executing, exit silently here. + ::TerminateProcess(::GetCurrentProcess(), + GetCrashReporterClient()->GetResultCodeRespawnFailed()); + } + + return true; +} + +// This function is executed by the child process that DumpDoneCallback() +// spawned and basically just shows the 'chrome has crashed' dialog if +// the CHROME_CRASHED environment variable is present. +bool ShowRestartDialogIfCrashed(bool* exit_now) { + // If we are being launched in metro mode don't try to show the dialog. + if (base::win::IsMetroProcess()) + return false; + + base::string16 message; + base::string16 title; + bool is_rtl_locale; + if (!GetCrashReporterClient()->ShouldShowRestartDialog( + &title, &message, &is_rtl_locale)) { + return false; + } + + // If the UI layout is right-to-left, we need to pass the appropriate MB_XXX + // flags so that an RTL message box is displayed. + UINT flags = MB_OKCANCEL | MB_ICONWARNING; + if (is_rtl_locale) + flags |= MB_RIGHT | MB_RTLREADING; + + return WrapMessageBoxWithSEH(message.c_str(), title.c_str(), flags, exit_now); +} + +extern "C" void __declspec(dllexport) TerminateProcessWithoutDump() { + // Patched stub exists based on conditions (See InitCrashReporter). + // As a side note this function also gets called from + // WindowProcExceptionFilter. + if (g_real_terminate_process_stub == NULL) { + ::TerminateProcess(::GetCurrentProcess(), content::RESULT_CODE_KILLED); + } else { + NtTerminateProcessPtr real_terminate_proc = + reinterpret_cast<NtTerminateProcessPtr>( + static_cast<char*>(g_real_terminate_process_stub)); + real_terminate_proc(::GetCurrentProcess(), content::RESULT_CODE_KILLED); + } +} + +// Crashes the process after generating a dump for the provided exception. Note +// that the crash reporter should be initialized before calling this function +// for it to do anything. +// NOTE: This function is used by SyzyASAN to invoke a crash. If you change the +// the name or signature of this function you will break SyzyASAN instrumented +// releases of Chrome. Please contact syzygy-team@chromium.org before doing so! +extern "C" int __declspec(dllexport) CrashForException( + EXCEPTION_POINTERS* info) { + if (g_breakpad) { + g_breakpad->WriteMinidumpForException(info); + TerminateProcessWithoutDump(); + } + return EXCEPTION_CONTINUE_SEARCH; +} + +#ifndef _WIN64 +static NTSTATUS WINAPI HookNtTerminateProcess(HANDLE ProcessHandle, + NTSTATUS ExitStatus) { + if (g_breakpad && + (ProcessHandle == ::GetCurrentProcess() || ProcessHandle == NULL)) { + NT_TIB* tib = reinterpret_cast<NT_TIB*>(NtCurrentTeb()); + void* address_on_stack = _AddressOfReturnAddress(); + if (address_on_stack < tib->StackLimit || + address_on_stack > tib->StackBase) { + g_surrogate_exception_record.ExceptionAddress = _ReturnAddress(); + g_surrogate_exception_record.ExceptionCode = DBG_TERMINATE_PROCESS; + g_surrogate_exception_record.ExceptionFlags = EXCEPTION_NONCONTINUABLE; + CrashForException(&g_surrogate_exception_pointers); + } + } + + NtTerminateProcessPtr real_proc = + reinterpret_cast<NtTerminateProcessPtr>( + static_cast<char*>(g_real_terminate_process_stub)); + return real_proc(ProcessHandle, ExitStatus); +} + +static void InitTerminateProcessHooks() { + NtTerminateProcessPtr terminate_process_func_address = + reinterpret_cast<NtTerminateProcessPtr>(::GetProcAddress( + ::GetModuleHandle(L"ntdll.dll"), "NtTerminateProcess")); + if (terminate_process_func_address == NULL) + return; + + DWORD old_protect = 0; + if (!::VirtualProtect(terminate_process_func_address, 5, + PAGE_EXECUTE_READWRITE, &old_protect)) + return; + + g_real_terminate_process_stub = reinterpret_cast<char*>(VirtualAllocEx( + ::GetCurrentProcess(), NULL, sidestep::kMaxPreambleStubSize, + MEM_COMMIT, PAGE_EXECUTE_READWRITE)); + if (g_real_terminate_process_stub == NULL) + return; + + g_surrogate_exception_pointers.ContextRecord = &g_surrogate_context; + g_surrogate_exception_pointers.ExceptionRecord = + &g_surrogate_exception_record; + + sidestep::SideStepError patch_result = + sidestep::PreamblePatcher::Patch( + terminate_process_func_address, HookNtTerminateProcess, + g_real_terminate_process_stub, sidestep::kMaxPreambleStubSize); + if (patch_result != sidestep::SIDESTEP_SUCCESS) { + CHECK(::VirtualFreeEx(::GetCurrentProcess(), g_real_terminate_process_stub, + 0, MEM_RELEASE)); + CHECK(::VirtualProtect(terminate_process_func_address, 5, old_protect, + &old_protect)); + return; + } + + DWORD dummy = 0; + CHECK(::VirtualProtect(terminate_process_func_address, + 5, + old_protect, + &dummy)); + CHECK(::VirtualProtect(g_real_terminate_process_stub, + sidestep::kMaxPreambleStubSize, + old_protect, + &old_protect)); +} +#endif + +static void InitPipeNameEnvVar(bool is_per_user_install) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + if (env->HasVar(kPipeNameVar)) { + // The Breakpad pipe name is already configured: nothing to do. + return; + } + + // Check whether configuration management controls crash reporting. + bool crash_reporting_enabled = true; + bool controlled_by_policy = + GetCrashReporterClient()->ReportingIsEnforcedByPolicy( + &crash_reporting_enabled); + + const base::CommandLine& command = *base::CommandLine::ForCurrentProcess(); + bool use_crash_service = !controlled_by_policy && + (command.HasSwitch(switches::kNoErrorDialogs) || + GetCrashReporterClient()->IsRunningUnattended()); + + std::wstring pipe_name; + if (use_crash_service) { + // Crash reporting is done by crash_service.exe. + pipe_name = kChromePipeName; + } else { + // We want to use the Google Update crash reporting. We need to check if the + // user allows it first (in case the administrator didn't already decide + // via policy). + if (!controlled_by_policy) + crash_reporting_enabled = + GetCrashReporterClient()->GetCollectStatsConsent(); + + if (!crash_reporting_enabled) { + // Crash reporting is disabled, don't set the environment variable. + return; + } + + // Build the pipe name. It can be either: + // System-wide install: "NamedPipe\GoogleCrashServices\S-1-5-18" + // Per-user install: "NamedPipe\GoogleCrashServices\<user SID>" + std::wstring user_sid; + if (is_per_user_install) { + if (!base::win::GetUserSidString(&user_sid)) { + return; + } + } else { + user_sid = kSystemPrincipalSid; + } + + pipe_name = kGoogleUpdatePipeName; + pipe_name += user_sid; + } + env->SetVar(kPipeNameVar, base::UTF16ToASCII(pipe_name)); +} + +void InitDefaultCrashCallback(LPTOP_LEVEL_EXCEPTION_FILTER filter) { + previous_filter = SetUnhandledExceptionFilter(filter); +} + +void InitCrashReporter(const std::string& process_type_switch) { + const base::CommandLine& command = *base::CommandLine::ForCurrentProcess(); + if (command.HasSwitch(switches::kDisableBreakpad)) + return; + + // Disable the message box for assertions. + _CrtSetReportMode(_CRT_ASSERT, 0); + + base::string16 process_type = base::ASCIIToUTF16(process_type_switch); + if (process_type.empty()) + process_type = L"browser"; + + wchar_t exe_path[MAX_PATH]; + exe_path[0] = 0; + GetModuleFileNameW(NULL, exe_path, MAX_PATH); + + bool is_per_user_install = + GetCrashReporterClient()->GetIsPerUserInstall(base::FilePath(exe_path)); + + // This is intentionally leaked. + CrashKeysWin* keeper = new CrashKeysWin(); + + google_breakpad::CustomClientInfo* custom_info = + keeper->GetCustomInfo(exe_path, process_type, GetProfileType(), + base::CommandLine::ForCurrentProcess(), + GetCrashReporterClient()); + +#if !defined(COMPONENT_BUILD) + // chrome/common/child_process_logging_win.cc registers crash keys for + // chrome.dll. In a component build, that is sufficient as chrome.dll and + // chrome.exe share a copy of base (in base.dll). + // In a static build, the EXE must separately initialize the crash keys + // configuration as it has its own statically linked copy of base. + base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueForBaseDebug, + &ClearCrashKeyForBaseDebug); + GetCrashReporterClient()->RegisterCrashKeys(); +#endif + + google_breakpad::ExceptionHandler::MinidumpCallback callback = NULL; + LPTOP_LEVEL_EXCEPTION_FILTER default_filter = NULL; + // We install the post-dump callback only for the browser and service + // processes. It spawns a new browser/service process. + if (process_type == L"browser") { + callback = &DumpDoneCallback; + default_filter = &ChromeExceptionFilter; + } else if (process_type == L"service") { + callback = &DumpDoneCallback; + default_filter = &ServiceExceptionFilter; + } + + if (process_type == L"browser") { + InitPipeNameEnvVar(is_per_user_install); + GetCrashReporterClient()->InitBrowserCrashDumpsRegKey(); + } + + scoped_ptr<base::Environment> env(base::Environment::Create()); + std::string pipe_name_ascii; + if (!env->GetVar(kPipeNameVar, &pipe_name_ascii)) { + // Breakpad is not enabled. Configuration is managed or the user + // did not allow Google Update to send crashes. We need to use + // our default crash handler instead, but only for the + // browser/service processes. + if (default_filter) + InitDefaultCrashCallback(default_filter); + return; + } + base::string16 pipe_name = base::ASCIIToUTF16(pipe_name_ascii); + +#ifdef _WIN64 + // The protocol for connecting to the out-of-process Breakpad crash + // reporter is different for x86-32 and x86-64: the message sizes + // are different because the message struct contains a pointer. As + // a result, there are two different named pipes to connect to. The + // 64-bit one is distinguished with an "-x64" suffix. + pipe_name += L"-x64"; +#endif + + // Get the alternate dump directory. We use the temp path. + wchar_t temp_dir[MAX_PATH] = {0}; + ::GetTempPathW(MAX_PATH, temp_dir); + + MINIDUMP_TYPE dump_type = kSmallDumpType; + // Capture full memory if explicitly instructed to. + if (command.HasSwitch(switches::kFullMemoryCrashReport)) + dump_type = kFullDumpType; + else if (GetCrashReporterClient()->GetShouldDumpLargerDumps( + is_per_user_install)) + dump_type = kLargerDumpType; + + g_breakpad = new google_breakpad::ExceptionHandler(temp_dir, &FilterCallback, + callback, NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL, + dump_type, pipe_name.c_str(), custom_info); + + // Now initialize the non crash dump handler. + g_dumphandler_no_crash = new google_breakpad::ExceptionHandler(temp_dir, + &FilterCallbackWhenNoCrash, + &DumpDoneCallbackWhenNoCrash, + NULL, + // Set the handler to none so this handler would not be added to + // |handler_stack_| in |ExceptionHandler| which is a list of exception + // handlers. + google_breakpad::ExceptionHandler::HANDLER_NONE, + dump_type, pipe_name.c_str(), custom_info); + + // Set the DumpWithoutCrashingFunction for this instance of base.lib. Other + // executable images linked with base should set this again for + // DumpWithoutCrashing to function correctly. + // See chrome_main.cc for example. + base::debug::SetDumpWithoutCrashingFunction(&DumpProcessWithoutCrash); + + if (g_breakpad->IsOutOfProcess()) { + // Tells breakpad to handle breakpoint and single step exceptions. + // This might break JIT debuggers, but at least it will always + // generate a crashdump for these exceptions. + g_breakpad->set_handle_debug_exceptions(true); + +#ifndef _WIN64 + if (process_type != L"browser" && + !GetCrashReporterClient()->IsRunningUnattended()) { + // Initialize the hook TerminateProcess to catch unexpected exits. + InitTerminateProcessHooks(); + } +#endif + } +} + +void ConsumeInvalidHandleExceptions() { + if (g_breakpad) { + g_breakpad->set_consume_invalid_handle_exceptions(true); + } + if (g_dumphandler_no_crash) { + g_dumphandler_no_crash->set_consume_invalid_handle_exceptions(true); + } +} + +// If the user has disabled crash reporting uploads and restarted Chrome, the +// restarted instance will still contain the pipe environment variable, which +// will allow the restarted process to still upload crash reports. This function +// clears the environment variable, so that the restarted Chrome, which inherits +// its environment from the current Chrome, will no longer contain the variable. +extern "C" void __declspec(dllexport) __cdecl + ClearBreakpadPipeEnvironmentVariable() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + env->UnSetVar(kPipeNameVar); +} + +#ifdef _WIN64 +int CrashForExceptionInNonABICompliantCodeRange( + PEXCEPTION_RECORD ExceptionRecord, + ULONG64 EstablisherFrame, + PCONTEXT ContextRecord, + PDISPATCHER_CONTEXT DispatcherContext) { + EXCEPTION_POINTERS info = { ExceptionRecord, ContextRecord }; + return CrashForException(&info); +} + +struct ExceptionHandlerRecord { + RUNTIME_FUNCTION runtime_function; + UNWIND_INFO unwind_info; + unsigned char thunk[12]; +}; + +extern "C" void __declspec(dllexport) __cdecl +RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes) { + ExceptionHandlerRecord* record = + reinterpret_cast<ExceptionHandlerRecord*>(start); + + // We assume that the first page of the code range is executable and + // committed and reserved for breakpad. What could possibly go wrong? + + // All addresses are 32bit relative offsets to start. + record->runtime_function.BeginAddress = 0; + record->runtime_function.EndAddress = + base::checked_cast<DWORD>(size_in_bytes); + record->runtime_function.UnwindData = + offsetof(ExceptionHandlerRecord, unwind_info); + + // Create unwind info that only specifies an exception handler. + record->unwind_info.Version = 1; + record->unwind_info.Flags = UNW_FLAG_EHANDLER; + record->unwind_info.SizeOfProlog = 0; + record->unwind_info.CountOfCodes = 0; + record->unwind_info.FrameRegister = 0; + record->unwind_info.FrameOffset = 0; + record->unwind_info.ExceptionHandler = + offsetof(ExceptionHandlerRecord, thunk); + + // Hardcoded thunk. + // mov imm64, rax + record->thunk[0] = 0x48; + record->thunk[1] = 0xb8; + void* handler = &CrashForExceptionInNonABICompliantCodeRange; + memcpy(&record->thunk[2], &handler, 8); + + // jmp rax + record->thunk[10] = 0xff; + record->thunk[11] = 0xe0; + + // Protect reserved page against modifications. + DWORD old_protect; + CHECK(VirtualProtect( + start, sizeof(ExceptionHandlerRecord), PAGE_EXECUTE_READ, &old_protect)); + CHECK(RtlAddFunctionTable( + &record->runtime_function, 1, reinterpret_cast<DWORD64>(start))); +} + +extern "C" void __declspec(dllexport) __cdecl +UnregisterNonABICompliantCodeRange(void* start) { + ExceptionHandlerRecord* record = + reinterpret_cast<ExceptionHandlerRecord*>(start); + + CHECK(RtlDeleteFunctionTable(&record->runtime_function)); +} +#endif + +} // namespace breakpad diff --git a/components/crash/content/app/breakpad_win.h b/components/crash/content/app/breakpad_win.h new file mode 100644 index 0000000..c4e9349 --- /dev/null +++ b/components/crash/content/app/breakpad_win.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_WIN_H_ +#define COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_WIN_H_ + +#include <windows.h> +#include <string> +#include <vector> + +namespace breakpad { + +void InitCrashReporter(const std::string& process_type_switch); + +// If chrome has been restarted because it crashed, this function will display +// a dialog asking for permission to continue execution or to exit now. +bool ShowRestartDialogIfCrashed(bool* exit_now); + +// Tells Breakpad that our process is shutting down and to consume +// EXCEPTION_INVALID_HANDLE exceptions which occur if bad handle detection is +// enabled and the sandbox handle closer has previously closed handles owned by +// Windows DLLs. +void ConsumeInvalidHandleExceptions(); + +} // namespace breakpad + +#endif // COMPONENTS_CRASH_CONTENT_APP_BREAKPAD_WIN_H_ diff --git a/components/crash/content/app/crash_keys_win.cc b/components/crash/content/app/crash_keys_win.cc new file mode 100644 index 0000000..2c784fa --- /dev/null +++ b/components/crash/content/app/crash_keys_win.cc @@ -0,0 +1,192 @@ +// 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. + +#include "components/crash/content/app/crash_keys_win.h" + +#include <algorithm> + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "components/crash/content/app/crash_reporter_client.h" + +namespace breakpad { + +using crash_reporter::CrashReporterClient; + +namespace { + +const size_t kMaxPluginPathLength = 256; +const size_t kMaxDynamicEntries = 256; + +} // namespace + +CrashKeysWin* CrashKeysWin::keeper_; + +CrashKeysWin::CrashKeysWin() : dynamic_keys_offset_(0) { + DCHECK_EQ(static_cast<CrashKeysWin*>(NULL), keeper_); + keeper_ = this; +} + +CrashKeysWin::~CrashKeysWin() { + DCHECK_EQ(this, keeper_); + keeper_ = NULL; +} + +// Appends the plugin path to |g_custom_entries|. +void CrashKeysWin::SetPluginPath(const std::wstring& path) { + if (path.size() > kMaxPluginPathLength) { + // If the path is too long, truncate from the start rather than the end, + // since we want to be able to recover the DLL name. + SetPluginPath(path.substr(path.size() - kMaxPluginPathLength)); + return; + } + + // The chunk size without terminator. + const size_t kChunkSize = static_cast<size_t>( + google_breakpad::CustomInfoEntry::kValueMaxLength - 1); + + int chunk_index = 0; + size_t chunk_start = 0; // Current position inside |path| + + for (chunk_start = 0; chunk_start < path.size(); chunk_index++) { + size_t chunk_length = std::min(kChunkSize, path.size() - chunk_start); + + custom_entries_.push_back(google_breakpad::CustomInfoEntry( + base::StringPrintf(L"plugin-path-chunk-%i", chunk_index + 1).c_str(), + path.substr(chunk_start, chunk_length).c_str())); + + chunk_start += chunk_length; + } +} + +// Appends the breakpad dump path to |g_custom_entries|. +void CrashKeysWin::SetBreakpadDumpPath(CrashReporterClient* crash_client) { + base::FilePath crash_dumps_dir_path; + if (crash_client->GetAlternativeCrashDumpLocation(&crash_dumps_dir_path)) { + custom_entries_.push_back(google_breakpad::CustomInfoEntry( + L"breakpad-dump-location", crash_dumps_dir_path.value().c_str())); + } +} + +// Returns the custom info structure based on the dll in parameter and the +// process type. +google_breakpad::CustomClientInfo* +CrashKeysWin::GetCustomInfo(const std::wstring& exe_path, + const std::wstring& type, + const std::wstring& profile_type, + base::CommandLine* cmd_line, + CrashReporterClient* crash_client) { + base::string16 version, product; + base::string16 special_build; + base::string16 channel_name; + + crash_client->GetProductNameAndVersion( + base::FilePath(exe_path), + &product, + &version, + &special_build, + &channel_name); + + // We only expect this method to be called once per process. + // Common enties + custom_entries_.push_back( + google_breakpad::CustomInfoEntry(L"ver", version.c_str())); + custom_entries_.push_back( + google_breakpad::CustomInfoEntry(L"prod", product.c_str())); + custom_entries_.push_back( + google_breakpad::CustomInfoEntry(L"plat", L"Win32")); + custom_entries_.push_back( + google_breakpad::CustomInfoEntry(L"ptype", type.c_str())); + custom_entries_.push_back( + google_breakpad::CustomInfoEntry( + L"pid", base::IntToString16(::GetCurrentProcessId()).c_str())); + custom_entries_.push_back( + google_breakpad::CustomInfoEntry(L"channel", channel_name.c_str())); + custom_entries_.push_back( + google_breakpad::CustomInfoEntry(L"profile-type", profile_type.c_str())); + + if (!special_build.empty()) { + custom_entries_.push_back( + google_breakpad::CustomInfoEntry(L"special", special_build.c_str())); + } + + if (type == L"plugin" || type == L"ppapi") { + std::wstring plugin_path = cmd_line->GetSwitchValueNative("plugin-path"); + if (!plugin_path.empty()) + SetPluginPath(plugin_path); + } + + // Check whether configuration management controls crash reporting. + bool crash_reporting_enabled = true; + bool controlled_by_policy = crash_client->ReportingIsEnforcedByPolicy( + &crash_reporting_enabled); + bool use_crash_service = !controlled_by_policy && + (cmd_line->HasSwitch(switches::kNoErrorDialogs) || + crash_client->IsRunningUnattended()); + if (use_crash_service) + SetBreakpadDumpPath(crash_client); + + // Create space for dynamic ad-hoc keys. The names and values are set using + // the API defined in base/debug/crash_logging.h. + dynamic_keys_offset_ = custom_entries_.size(); + for (size_t i = 0; i < kMaxDynamicEntries; ++i) { + // The names will be mutated as they are set. Un-numbered since these are + // merely placeholders. The name cannot be empty because Breakpad's + // HTTPUpload will interpret that as an invalid parameter. + custom_entries_.push_back( + google_breakpad::CustomInfoEntry(L"unspecified-crash-key", L"")); + } + + static google_breakpad::CustomClientInfo custom_client_info; + custom_client_info.entries = &custom_entries_.front(); + custom_client_info.count = custom_entries_.size(); + + return &custom_client_info; +} + +void CrashKeysWin::SetCrashKeyValue( + const std::wstring& key, const std::wstring& value) { + // CustomInfoEntry limits the length of key and value. If they exceed + // their maximum length the underlying string handling functions raise + // an exception and prematurely trigger a crash. Truncate here. + std::wstring safe_key(std::wstring(key).substr( + 0, google_breakpad::CustomInfoEntry::kNameMaxLength - 1)); + std::wstring safe_value(std::wstring(value).substr( + 0, google_breakpad::CustomInfoEntry::kValueMaxLength - 1)); + + // If we already have a value for this key, update it; otherwise, insert + // the new value if we have not exhausted the pre-allocated slots for dynamic + // entries. + base::AutoLock lock(lock_); + + DynamicEntriesMap::iterator it = dynamic_entries_.find(safe_key); + google_breakpad::CustomInfoEntry* entry = NULL; + if (it == dynamic_entries_.end()) { + if (dynamic_entries_.size() >= kMaxDynamicEntries) + return; + entry = &custom_entries_[dynamic_keys_offset_++]; + dynamic_entries_.insert(std::make_pair(safe_key, entry)); + } else { + entry = it->second; + } + + entry->set(safe_key.data(), safe_value.data()); +} + +void CrashKeysWin::ClearCrashKeyValue(const std::wstring& key) { + base::AutoLock lock(lock_); + + std::wstring key_string(key); + DynamicEntriesMap::iterator it = dynamic_entries_.find(key_string); + if (it == dynamic_entries_.end()) + return; + + it->second->set_value(NULL); +} + +} // namespace breakpad diff --git a/components/crash/content/app/crash_keys_win.h b/components/crash/content/app/crash_keys_win.h new file mode 100644 index 0000000..870de77 --- /dev/null +++ b/components/crash/content/app/crash_keys_win.h @@ -0,0 +1,87 @@ +// 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. + +#ifndef COMPONENTS_CRASH_CONTENT_APP_CRASH_KEYS_WIN_H_ +#define COMPONENTS_CRASH_CONTENT_APP_CRASH_KEYS_WIN_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/synchronization/lock.h" +#include "breakpad/src/client/windows/common/ipc_protocol.h" +#include "breakpad/src/client/windows/handler/exception_handler.h" + + +namespace base { +class CommandLine; +} // namespace base + +namespace crash_reporter { +class CrashReporterClient; +} + +namespace breakpad { + +// Manages the breakpad key/value pair stash, there may only be one instance +// of this class per process at one time. +class CrashKeysWin { + public: + CrashKeysWin(); + ~CrashKeysWin(); + + // May only be called once. + // |exe_path| is the path to the executable running, which may be used + // to figure out whether this is a user or system install. + // |type| is the process type, or mode this process is running in e.g. + // something like "browser" or "renderer". + // |profile_type| is a string describing the kind of the user's Windows + // profile, e.g. "mandatory", or "roaming" or similar. + // |cmd_line| is the current process' command line consulted for explicit + // crash reporting flags. + // |crash_client| is consulted for crash reporting settings. + google_breakpad::CustomClientInfo* GetCustomInfo( + const std::wstring& exe_path, + const std::wstring& type, + const std::wstring& profile_type, + base::CommandLine* cmd_line, + crash_reporter::CrashReporterClient* crash_client); + + void SetCrashKeyValue(const std::wstring& key, const std::wstring& value); + void ClearCrashKeyValue(const std::wstring& key); + + const std::vector<google_breakpad::CustomInfoEntry>& custom_info_entries() + const { + return custom_entries_; + } + + static CrashKeysWin* keeper() { return keeper_; } + + private: + // One-time initialization of private key/value pairs. + void SetPluginPath(const std::wstring& path); + void SetBreakpadDumpPath(crash_reporter::CrashReporterClient* crash_client); + + // Must not be resized after GetCustomInfo is invoked. + std::vector<google_breakpad::CustomInfoEntry> custom_entries_; + + typedef std::map<std::wstring, google_breakpad::CustomInfoEntry*> + DynamicEntriesMap; + base::Lock lock_; + // Keeps track of the next index for a new dynamic entry. + size_t dynamic_keys_offset_; // Under lock_. + // Maintains key->entry information for dynamic key/value entries + // in custom_entries_. + DynamicEntriesMap dynamic_entries_; // Under lock_. + + // Stores the sole instance of this class allowed per process. + static CrashKeysWin* keeper_; + + DISALLOW_COPY_AND_ASSIGN(CrashKeysWin); +}; + +} // namespace breakpad + +#endif // COMPONENTS_CRASH_CONTENT_APP_CRASH_KEYS_WIN_H_ diff --git a/components/crash/content/app/crash_keys_win_unittest.cc b/components/crash/content/app/crash_keys_win_unittest.cc new file mode 100644 index 0000000..19d3942 --- /dev/null +++ b/components/crash/content/app/crash_keys_win_unittest.cc @@ -0,0 +1,142 @@ +// 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. + +#include "components/crash/content/app/crash_keys_win.h" + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/strings/stringprintf.h" +#include "components/crash/content/app/crash_reporter_client.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace breakpad { + +using testing::_; +using testing::DoAll; +using testing::Return; +using testing::SetArgPointee; + +class MockCrashReporterClient : public crash_reporter::CrashReporterClient { + public: + MOCK_METHOD1(GetAlternativeCrashDumpLocation, + bool(base::FilePath* crash_dir)); + MOCK_METHOD5(GetProductNameAndVersion, void(const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name)); + MOCK_METHOD3(ShouldShowRestartDialog, bool(base::string16* title, + base::string16* message, + bool* is_rtl_locale)); + MOCK_METHOD0(AboutToRestart, bool()); + MOCK_METHOD1(GetDeferredUploadsSupported, bool(bool is_per_user_install)); + MOCK_METHOD1(GetIsPerUserInstall, bool(const base::FilePath& exe_path)); + MOCK_METHOD1(GetShouldDumpLargerDumps, bool(bool is_per_user_install)); + MOCK_METHOD0(GetResultCodeRespawnFailed, int()); + MOCK_METHOD0(InitBrowserCrashDumpsRegKey, void()); + MOCK_METHOD1(RecordCrashDumpAttempt, void(bool is_real_crash)); + + MOCK_METHOD2(GetProductNameAndVersion, void(std::string* product_name, + std::string* version)); + MOCK_METHOD0(GetReporterLogFilename, base::FilePath()); + MOCK_METHOD1(GetCrashDumpLocation, bool(base::FilePath* crash_dir)); + MOCK_METHOD0(RegisterCrashKeys, size_t()); + MOCK_METHOD0(IsRunningUnattended, bool()); + MOCK_METHOD0(GetCollectStatsConsent, bool()); + MOCK_METHOD1(ReportingIsEnforcedByPolicy, bool(bool* breakpad_enabled)); + MOCK_METHOD0(GetAndroidMinidumpDescriptor, int()); + MOCK_METHOD1(EnableBreakpadForProcess, bool(const std::string& process_type)); +}; + +class CrashKeysWinTest : public testing::Test { + public: + + size_t CountKeyValueOccurences( + const google_breakpad::CustomClientInfo* client_info, + const wchar_t* key, const wchar_t* value); + + protected: + testing::StrictMock<MockCrashReporterClient> crash_client_; +}; + +size_t CrashKeysWinTest::CountKeyValueOccurences( + const google_breakpad::CustomClientInfo* client_info, + const wchar_t* key, const wchar_t* value) { + size_t occurrences = 0; + for (size_t i = 0; i < client_info->count; ++i) { + if (wcscmp(client_info->entries[i].name, key) == 0 && + wcscmp(client_info->entries[i].value, value) == 0) { + ++occurrences; + } + } + + return occurrences; +} + +TEST_F(CrashKeysWinTest, RecordsSelf) { + ASSERT_EQ(static_cast<CrashKeysWin*>(NULL), CrashKeysWin::keeper()); + + { + CrashKeysWin crash_keys; + + ASSERT_EQ(&crash_keys, CrashKeysWin::keeper()); + } + + ASSERT_EQ(static_cast<CrashKeysWin*>(NULL), CrashKeysWin::keeper()); +} + +// Tests the crash keys set up for the most common official build consumer +// scenario. No policy controls, not running unattended and no explicit +// switches. +TEST_F(CrashKeysWinTest, OfficialLikeKeys) { + CrashKeysWin crash_keys; + + const base::FilePath kExePath(L"C:\\temp\\exe_path.exe"); + // The exe path ought to get passed through to the breakpad client. + EXPECT_CALL(crash_client_, GetProductNameAndVersion(kExePath, _, _, _, _)) + .WillRepeatedly(DoAll( + SetArgPointee<1>(L"SomeProdName"), + SetArgPointee<2>(L"1.2.3.4"), + SetArgPointee<3>(L""), + SetArgPointee<4>(L"-devm"))); + + EXPECT_CALL(crash_client_, GetAlternativeCrashDumpLocation(_)) + .WillRepeatedly(DoAll( + SetArgPointee<0>(base::FilePath(L"C:\\temp")), + Return(false))); + + EXPECT_CALL(crash_client_, ReportingIsEnforcedByPolicy(_)) + .WillRepeatedly(Return(false)); + + EXPECT_CALL(crash_client_, IsRunningUnattended()) + .WillRepeatedly(Return(false)); + + // Provide an empty command line. + base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM); + google_breakpad::CustomClientInfo* info = + crash_keys.GetCustomInfo(kExePath.value(), + L"made_up_type", + L"temporary", + &cmd_line, + &crash_client_); + + ASSERT_TRUE(info != NULL); + ASSERT_TRUE(info->entries != NULL); + + // We expect 7 fixed keys and a "freeboard" of 256 keys for dynamic entries. + EXPECT_EQ(256U + 7U, info->count); + + EXPECT_EQ(1, CountKeyValueOccurences(info, L"ver", L"1.2.3.4")); + EXPECT_EQ(1, CountKeyValueOccurences(info, L"prod", L"SomeProdName")); + EXPECT_EQ(1, CountKeyValueOccurences(info, L"plat", L"Win32")); + EXPECT_EQ(1, CountKeyValueOccurences(info, L"ptype", L"made_up_type")); + std::wstring pid_str(base::StringPrintf(L"%d", ::GetCurrentProcessId())); + EXPECT_EQ(1, CountKeyValueOccurences(info, L"pid", pid_str.c_str())); + EXPECT_EQ(1, CountKeyValueOccurences(info, L"channel", L"-devm")); + EXPECT_EQ(1, CountKeyValueOccurences(info, L"profile-type", L"temporary")); + EXPECT_EQ(256, CountKeyValueOccurences(info, L"unspecified-crash-key", L"")); +} + +} // namespace breakpad diff --git a/components/crash/content/app/crash_reporter_client.cc b/components/crash/content/app/crash_reporter_client.cc new file mode 100644 index 0000000..66388e2 --- /dev/null +++ b/components/crash/content/app/crash_reporter_client.cc @@ -0,0 +1,148 @@ +// 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. + +#include "components/crash/content/app/crash_reporter_client.h" + +#include "base/files/file_path.h" +#include "base/logging.h" + +namespace crash_reporter { + +namespace { + +CrashReporterClient* g_client = NULL; + +} // namespace + +void SetCrashReporterClient(CrashReporterClient* client) { + g_client = client; +} + +CrashReporterClient* GetCrashReporterClient() { + DCHECK(g_client); + return g_client; +} + +CrashReporterClient::CrashReporterClient() {} +CrashReporterClient::~CrashReporterClient() {} + +#if !defined(OS_MACOSX) +void CrashReporterClient::SetCrashReporterClientIdFromGUID( + const std::string& client_guid) { +} +#endif + +#if defined(OS_WIN) +bool CrashReporterClient::GetAlternativeCrashDumpLocation( + base::FilePath* crash_dir) { + return false; +} + +void CrashReporterClient::GetProductNameAndVersion( + const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name) { +} + +bool CrashReporterClient::ShouldShowRestartDialog(base::string16* title, + base::string16* message, + bool* is_rtl_locale) { + return false; +} + +bool CrashReporterClient::AboutToRestart() { + return false; +} + +bool CrashReporterClient::GetDeferredUploadsSupported(bool is_per_usr_install) { + return false; +} + +bool CrashReporterClient::GetIsPerUserInstall(const base::FilePath& exe_path) { + return true; +} + +bool CrashReporterClient::GetShouldDumpLargerDumps(bool is_per_user_install) { + return false; +} + +int CrashReporterClient::GetResultCodeRespawnFailed() { + return 0; +} + +void CrashReporterClient::InitBrowserCrashDumpsRegKey() { +} + +void CrashReporterClient::RecordCrashDumpAttempt(bool is_real_crash) { +} + +void CrashReporterClient::RecordCrashDumpAttemptResult(bool is_real_crash, + bool succeeded) { +} +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) +void CrashReporterClient::GetProductNameAndVersion(const char** product_name, + const char** version) { +} + +base::FilePath CrashReporterClient::GetReporterLogFilename() { + return base::FilePath(); +} + +bool CrashReporterClient::HandleCrashDump(const char* crashdump_filename) { + return false; +} +#endif + +bool CrashReporterClient::GetCrashDumpLocation(base::FilePath* crash_dir) { + return false; +} + +size_t CrashReporterClient::RegisterCrashKeys() { + return 0; +} + +bool CrashReporterClient::IsRunningUnattended() { + return true; +} + +bool CrashReporterClient::GetCollectStatsConsent() { + return false; +} + +#if defined(OS_WIN) || defined(OS_MACOSX) +bool CrashReporterClient::ReportingIsEnforcedByPolicy(bool* breakpad_enabled) { + return false; +} +#endif + +#if defined(OS_ANDROID) +int CrashReporterClient::GetAndroidMinidumpDescriptor() { + return 0; +} + +bool CrashReporterClient::ShouldEnableBreakpadMicrodumps() { +// Always enable microdumps on Android when stripping unwind tables. Rationale: +// when unwind tables are stripped out (to save binary size) the stack traces +// produced locally in the case of a crash / CHECK are meaningless. In order to +// provide meaningful development diagnostics (and keep the binary size savings) +// on Android we attach a secondary crash handler which serializes a reduced +// form of logcat on the console. +#if defined(NO_UNWIND_TABLES) + return true; +#else + return false; +#endif +} +#endif + +bool CrashReporterClient::EnableBreakpadForProcess( + const std::string& process_type) { + return false; +} + +} // namespace crash_reporter diff --git a/components/crash/content/app/crash_reporter_client.h b/components/crash/content/app/crash_reporter_client.h new file mode 100644 index 0000000..4a96954 --- /dev/null +++ b/components/crash/content/app/crash_reporter_client.h @@ -0,0 +1,158 @@ +// 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. + +#ifndef COMPONENTS_CRASH_CONTENT_APP_CRASH_REPORTER_CLIENT_H_ +#define COMPONENTS_CRASH_CONTENT_APP_CRASH_REPORTER_CLIENT_H_ + +#include <string> + +#include "base/strings/string16.h" +#include "build/build_config.h" + +namespace base { +class FilePath; +} + +#if defined(OS_MACOSX) +// We don't want to directly include +// breakpad/src/client/mac/Framework/Breakpad.h here, so we repeat the +// definition of BreakpadRef. +// +// On Mac, when compiling without breakpad support, a stub implementation is +// compiled in. Not having any includes of the breakpad library allows for +// reusing this header for the stub. +typedef void* BreakpadRef; +#endif + +namespace crash_reporter { + +class CrashReporterClient; + +// Setter and getter for the client. The client should be set early, before any +// crash reporter code is called, and should stay alive throughout the entire +// runtime. +void SetCrashReporterClient(CrashReporterClient* client); + +#if defined(CRASH_IMPLEMENTATION) +// The components's embedder API should only be used by the component. +CrashReporterClient* GetCrashReporterClient(); +#endif + +// Interface that the embedder implements. +class CrashReporterClient { + public: + CrashReporterClient(); + virtual ~CrashReporterClient(); + +#if !defined(OS_MACOSX) + // Sets the crash reporting client ID, a unique identifier for the client + // that is sending crash reports. After it is set, it should not be changed. + // |client_guid| may either be a full GUID or a GUID that was already stripped + // from its dashes. + // + // On Mac OS X, this is the responsibility of Crashpad, and can not be set + // directly by the client. + virtual void SetCrashReporterClientIdFromGUID(const std::string& client_guid); +#endif + +#if defined(OS_WIN) + // Returns true if an alternative location to store the minidump files was + // specified. Returns true if |crash_dir| was set. + virtual bool GetAlternativeCrashDumpLocation(base::FilePath* crash_dir); + + // Returns a textual description of the product type and version to include + // in the crash report. + virtual void GetProductNameAndVersion(const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name); + + // Returns true if a restart dialog should be displayed. In that case, + // |message| and |title| are set to a message to display in a dialog box with + // the given title before restarting, and |is_rtl_locale| indicates whether + // to display the text as RTL. + virtual bool ShouldShowRestartDialog(base::string16* title, + base::string16* message, + bool* is_rtl_locale); + + // Returns true if it is ok to restart the application. Invoked right before + // restarting after a crash. + virtual bool AboutToRestart(); + + // Returns true if the crash report uploader supports deferred uploads. + virtual bool GetDeferredUploadsSupported(bool is_per_user_install); + + // Returns true if the running binary is a per-user installation. + virtual bool GetIsPerUserInstall(const base::FilePath& exe_path); + + // Returns true if larger crash dumps should be dumped. + virtual bool GetShouldDumpLargerDumps(bool is_per_user_install); + + // Returns the result code to return when breakpad failed to respawn a + // crashed process. + virtual int GetResultCodeRespawnFailed(); + + // Invoked when initializing the crash reporter in the browser process. + virtual void InitBrowserCrashDumpsRegKey(); + + // Invoked before attempting to write a minidump. + virtual void RecordCrashDumpAttempt(bool is_real_crash); + + // Invoked with the results of a minidump attempt. + virtual void RecordCrashDumpAttemptResult(bool is_real_crash, bool succeeded); +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) + // Returns a textual description of the product type and version to include + // in the crash report. Neither out parameter should be set to NULL. + virtual void GetProductNameAndVersion(const char** product_name, + const char** version); + + virtual base::FilePath GetReporterLogFilename(); + + // Custom crash minidump handler after the minidump is generated. + // Returns true if the minidump is handled (client); otherwise, return false + // to fallback to default handler. + // WARNING: this handler runs in a compromised context. It may not call into + // libc nor allocate memory normally. + virtual bool HandleCrashDump(const char* crashdump_filename); +#endif + + // The location where minidump files should be written. Returns true if + // |crash_dir| was set. + virtual bool GetCrashDumpLocation(base::FilePath* crash_dir); + + // Register all of the potential crash keys that can be sent to the crash + // reporting server. Returns the size of the union of all keys. + virtual size_t RegisterCrashKeys(); + + // Returns true if running in unattended mode (for automated testing). + virtual bool IsRunningUnattended(); + + // Returns true if the user has given consent to collect stats. + virtual bool GetCollectStatsConsent(); + +#if defined(OS_WIN) || defined(OS_MACOSX) + // Returns true if crash reporting is enforced via management policies. In + // that case, |breakpad_enabled| is set to the value enforced by policies. + virtual bool ReportingIsEnforcedByPolicy(bool* breakpad_enabled); +#endif + +#if defined(OS_ANDROID) + // Returns the descriptor key of the android minidump global descriptor. + virtual int GetAndroidMinidumpDescriptor(); + + // Returns true if breakpad microdumps should be enabled. This orthogonal to + // the standard minidump uploader (which depends on the user consent). + virtual bool ShouldEnableBreakpadMicrodumps(); +#endif + + // Returns true if breakpad should run in the given process type. + virtual bool EnableBreakpadForProcess(const std::string& process_type); +}; + +} // namespace crash_reporter + +#endif // COMPONENTS_CRASH_CONTENT_APP_CRASH_REPORTER_CLIENT_H_ diff --git a/components/crash/content/app/crashpad_mac.h b/components/crash/content/app/crashpad_mac.h new file mode 100644 index 0000000..b46cce5 --- /dev/null +++ b/components/crash/content/app/crashpad_mac.h @@ -0,0 +1,54 @@ +// Copyright 2015 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_APP_CRASHPAD_MAC_H_ +#define COMPONENTS_CRASH_CONTENT_APP_CRASHPAD_MAC_H_ + +#include <time.h> + +#include <string> +#include <vector> + +namespace crash_reporter { + +// Initializes Crashpad in a way that is appropriate for process_type. If +// process_type is empty, initializes Crashpad for the browser process, which +// starts crashpad_handler and sets it as the exception handler. Other process +// types inherit this exception handler from the browser, but still need to +// perform additional initialization. +void InitializeCrashpad(const std::string& process_type); + +// Enables or disables crash report upload. This is a property of the Crashpad +// database. In a newly-created database, uploads will be disabled. This +// function only has an effect when called in the browser process. Its effect is +// immediate and applies to all other process types, including processes that +// are already running. +void SetUploadsEnabled(bool enabled); + +// Determines whether uploads are enabled or disabled. This information is only +// available in the browser process. +bool GetUploadsEnabled(); + +struct UploadedReport { + std::string local_id; + std::string remote_id; + time_t creation_time; +}; + +// Obtains a list of reports uploaded to the collection server. This function +// only operates when called in the browser process. All reports in the Crashpad +// database that have been successfully uploaded will be included in this list. +// The list will be sorted in descending order by report creation time (newest +// reports first). +// +// TODO(mark): The about:crashes UI expects to show only uploaded reports. If it +// is ever enhanced to work well with un-uploaded reports, those should be +// returned as well. Un-uploaded reports may have a pending upload, may have +// experienced upload failure, or may have been collected while uploads were +// disabled. +void GetUploadedReports(std::vector<UploadedReport>* uploaded_reports); + +} // namespace crash_reporter + +#endif // COMPONENTS_CRASH_CONTENT_APP_CRASHPAD_MAC_H_ diff --git a/components/crash/content/app/crashpad_mac.mm b/components/crash/content/app/crashpad_mac.mm new file mode 100644 index 0000000..1082db4 --- /dev/null +++ b/components/crash/content/app/crashpad_mac.mm @@ -0,0 +1,251 @@ +// Copyright 2015 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/app/crashpad_mac.h" + +#include <string.h> +#include <unistd.h> + +#include <algorithm> +#include <map> +#include <vector> + +#include "base/auto_reset.h" +#include "base/debug/crash_logging.h" +#include "base/debug/dump_without_crashing.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/foundation_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "components/crash/content/app/crash_reporter_client.h" +#include "third_party/crashpad/crashpad/client/crash_report_database.h" +#include "third_party/crashpad/crashpad/client/crashpad_client.h" +#include "third_party/crashpad/crashpad/client/crashpad_info.h" +#include "third_party/crashpad/crashpad/client/settings.h" +#include "third_party/crashpad/crashpad/client/simple_string_dictionary.h" +#include "third_party/crashpad/crashpad/client/simulate_crash.h" + +namespace crash_reporter { + +namespace { + +crashpad::SimpleStringDictionary* g_simple_string_dictionary; +crashpad::CrashReportDatabase* g_database; + +void SetCrashKeyValue(const base::StringPiece& key, + const base::StringPiece& value) { + g_simple_string_dictionary->SetKeyValue(key.data(), value.data()); +} + +void ClearCrashKey(const base::StringPiece& key) { + g_simple_string_dictionary->RemoveKey(key.data()); +} + +bool LogMessageHandler(int severity, + const char* file, + int line, + size_t message_start, + const std::string& string) { + // Only handle FATAL. + if (severity != logging::LOG_FATAL) { + return false; + } + + // In case of an out-of-memory condition, this code could be reentered when + // constructing and storing the key. Using a static is not thread-safe, but if + // multiple threads are in the process of a fatal crash at the same time, this + // should work. + static bool guarded = false; + if (guarded) { + return false; + } + base::AutoReset<bool> guard(&guarded, true); + + // Only log last path component. This matches logging.cc. + if (file) { + const char* slash = strrchr(file, '/'); + if (slash) { + file = slash + 1; + } + } + + std::string message = base::StringPrintf("%s:%d: %s", file, line, + string.c_str() + message_start); + SetCrashKeyValue("LOG_FATAL", message); + + // Rather than including the code to force the crash here, allow the caller to + // do it. + return false; +} + +void DumpWithoutCrashing() { + CRASHPAD_SIMULATE_CRASH(); +} + +} // namespace + +void InitializeCrashpad(const std::string& process_type) { + static bool initialized = false; + DCHECK(!initialized); + initialized = true; + + const bool browser_process = process_type.empty(); + CrashReporterClient* crash_reporter_client = GetCrashReporterClient(); + + base::FilePath database_path; // Only valid in the browser process. + + if (browser_process) { + @autoreleasepool { + base::FilePath framework_bundle_path = base::mac::FrameworkBundlePath(); + base::FilePath handler_path = + framework_bundle_path.Append("Helpers").Append("crashpad_handler"); + + // Is there a way to recover if this fails? + crash_reporter_client->GetCrashDumpLocation(&database_path); + + // TODO(mark): Reading the Breakpad keys is temporary and transitional. At + // the very least, they should be renamed to Crashpad. For the time being, + // this isn't the worst thing: Crashpad is still uploading to a + // Breakpad-type server, after all. + NSBundle* framework_bundle = base::mac::FrameworkBundle(); + NSString* product = base::mac::ObjCCast<NSString>( + [framework_bundle objectForInfoDictionaryKey:@"BreakpadProduct"]); + NSString* version = base::mac::ObjCCast<NSString>( + [framework_bundle objectForInfoDictionaryKey:@"BreakpadVersion"]); + NSString* url_ns = base::mac::ObjCCast<NSString>( + [framework_bundle objectForInfoDictionaryKey:@"BreakpadURL"]); + + std::string url = base::SysNSStringToUTF8(url_ns); + + std::map<std::string, std::string> process_annotations; + process_annotations["prod"] = base::SysNSStringToUTF8(product); + process_annotations["ver"] = base::SysNSStringToUTF8(version); + process_annotations["plat"] = std::string("OS X"); + + crashpad::CrashpadClient crashpad_client; + if (crashpad_client.StartHandler(handler_path, database_path, url, + process_annotations, + std::vector<std::string>())) { + crashpad_client.UseHandler(); + } + } // @autoreleasepool + } + + crashpad::CrashpadInfo* crashpad_info = + crashpad::CrashpadInfo::GetCrashpadInfo(); + +#if defined(NDEBUG) + const bool is_debug_build = false; +#else + const bool is_debug_build = true; +#endif + + // Disable forwarding to the system's crash reporter in processes other than + // the browser process. For the browser, the system's crash reporter presents + // the crash UI to the user, so it's desirable there. Additionally, having + // crash reports appear in ~/Library/Logs/DiagnosticReports provides a + // fallback. Forwarding is turned off for debug-mode builds even for the + // browser process, because the system's crash reporter can take a very long + // time to chew on symbols. + if (!browser_process || is_debug_build) { + crashpad_info->set_system_crash_reporter_forwarding( + crashpad::TriState::kDisabled); + } + + g_simple_string_dictionary = new crashpad::SimpleStringDictionary(); + crashpad_info->set_simple_annotations(g_simple_string_dictionary); + + base::debug::SetCrashKeyReportingFunctions(SetCrashKeyValue, ClearCrashKey); + crash_reporter_client->RegisterCrashKeys(); + + SetCrashKeyValue("ptype", browser_process ? base::StringPiece("browser") + : base::StringPiece(process_type)); + SetCrashKeyValue("pid", base::IntToString(getpid())); + + logging::SetLogMessageHandler(LogMessageHandler); + + // If clients called CRASHPAD_SIMULATE_CRASH() instead of + // base::debug::DumpWithoutCrashing(), these dumps would appear as crashes in + // the correct function, at the correct file and line. This would be + // preferable to having all occurrences show up in DumpWithoutCrashing() at + // the same file and line. + base::debug::SetDumpWithoutCrashingFunction(DumpWithoutCrashing); + + if (browser_process) { + g_database = + crashpad::CrashReportDatabase::Initialize(database_path).release(); + + bool enable_uploads = false; + if (!crash_reporter_client->ReportingIsEnforcedByPolicy(&enable_uploads)) { + enable_uploads = crash_reporter_client->GetCollectStatsConsent() && + !crash_reporter_client->IsRunningUnattended(); + // Breakpad provided a --disable-breakpad switch to disable crash dumping + // (not just uploading) here. Crashpad doesn't need it: dumping is enabled + // unconditionally and uploading is gated on consent, which tests/bots + // shouldn't have. As a precaution, we also force disable uploading on + // bots even if consent is present. + } + + SetUploadsEnabled(enable_uploads); + } +} + +void SetUploadsEnabled(bool enable_uploads) { + if (g_database) { + crashpad::Settings* settings = g_database->GetSettings(); + settings->SetUploadsEnabled(enable_uploads); + } +} + +bool GetUploadsEnabled() { + if (g_database) { + crashpad::Settings* settings = g_database->GetSettings(); + bool enable_uploads; + if (settings->GetUploadsEnabled(&enable_uploads)) { + return enable_uploads; + } + } + + return false; +} + +void GetUploadedReports(std::vector<UploadedReport>* uploaded_reports) { + uploaded_reports->clear(); + + if (!g_database) { + return; + } + + std::vector<crashpad::CrashReportDatabase::Report> completed_reports; + crashpad::CrashReportDatabase::OperationStatus status = + g_database->GetCompletedReports(&completed_reports); + if (status != crashpad::CrashReportDatabase::kNoError) { + return; + } + + for (const crashpad::CrashReportDatabase::Report& completed_report : + completed_reports) { + if (completed_report.uploaded) { + UploadedReport uploaded_report; + uploaded_report.local_id = completed_report.uuid.ToString(); + uploaded_report.remote_id = completed_report.id; + uploaded_report.creation_time = completed_report.creation_time; + + uploaded_reports->push_back(uploaded_report); + } + } + + struct { + bool operator()(const UploadedReport& a, const UploadedReport& b) { + return a.creation_time >= b.creation_time; + } + } sort_by_time; + std::sort(uploaded_reports->begin(), uploaded_reports->end(), sort_by_time); +} + +} // namespace crash_reporter diff --git a/components/crash/content/app/hard_error_handler_win.cc b/components/crash/content/app/hard_error_handler_win.cc new file mode 100644 index 0000000..a310666 --- /dev/null +++ b/components/crash/content/app/hard_error_handler_win.cc @@ -0,0 +1,112 @@ +// 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. + +#include "components/crash/content/app/hard_error_handler_win.h" + +#include <DelayIMP.h> +#include <winternl.h> + +#include "base/basictypes.h" +#include "base/strings/string_util.h" +#include "components/crash/content/app/crash_reporter_client.h" + +namespace breakpad { + +using crash_reporter::GetCrashReporterClient; + +namespace { +const DWORD kExceptionModuleNotFound = VcppException(ERROR_SEVERITY_ERROR, + ERROR_MOD_NOT_FOUND); +const DWORD kExceptionEntryPtNotFound = VcppException(ERROR_SEVERITY_ERROR, + ERROR_PROC_NOT_FOUND); +// This is defined in <ntstatus.h> but we can't include this file here. +const DWORD FACILITY_GRAPHICS_KERNEL = 0x1E; +const DWORD NT_STATUS_ENTRYPOINT_NOT_FOUND = 0xC0000139; +const DWORD NT_STATUS_DLL_NOT_FOUND = 0xC0000135; + +// We assume that exception codes are NT_STATUS codes. +DWORD FacilityFromException(DWORD exception_code) { + return (exception_code >> 16) & 0x0FFF; +} + +// This is not a generic function. It only works with some |nt_status| values. +// Check the strings here http://msdn.microsoft.com/en-us/library/cc704588.aspx +// before attempting to use this function. +void RaiseHardErrorMsg(long nt_status, const std::string& p1, + const std::string& p2) { + // If headless just exit silently. + if (GetCrashReporterClient()->IsRunningUnattended()) + return; + + HMODULE ntdll = ::GetModuleHandleA("NTDLL.DLL"); + wchar_t* msg_template = NULL; + size_t count = ::FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_HMODULE, + ntdll, + nt_status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast<wchar_t*>(&msg_template), + 0, + NULL); + + if (!count) + return; + count += p1.size() + p2.size() + 1; + base::string16 message; + ::wsprintf(base::WriteInto(&message, count), msg_template, + p1.c_str(), p2.c_str()); + // The MB_SERVICE_NOTIFICATION causes this message to be displayed by + // csrss. This means that we are not creating windows or pumping WM messages + // in this process. + ::MessageBox(NULL, message.c_str(), + L"chrome.exe", + MB_OK | MB_SERVICE_NOTIFICATION); + ::LocalFree(msg_template); +} + +void ModuleNotFoundHardError(const EXCEPTION_RECORD* ex_record) { + DelayLoadInfo* dli = reinterpret_cast<DelayLoadInfo*>( + ex_record->ExceptionInformation[0]); + if (!dli->szDll) + return; + RaiseHardErrorMsg(NT_STATUS_DLL_NOT_FOUND, dli->szDll, std::string()); +} + +void EntryPointNotFoundHardError(const EXCEPTION_RECORD* ex_record) { + DelayLoadInfo* dli = reinterpret_cast<DelayLoadInfo*>( + ex_record->ExceptionInformation[0]); + if (!dli->dlp.fImportByName) + return; + if (!dli->dlp.szProcName) + return; + if (!dli->szDll) + return; + RaiseHardErrorMsg(NT_STATUS_ENTRYPOINT_NOT_FOUND, + dli->dlp.szProcName, dli->szDll); +} + +} // namespace + +bool HardErrorHandler(EXCEPTION_POINTERS* ex_info) { + if (!ex_info) + return false; + if (!ex_info->ExceptionRecord) + return false; + + long exception = ex_info->ExceptionRecord->ExceptionCode; + if (exception == kExceptionModuleNotFound) { + ModuleNotFoundHardError(ex_info->ExceptionRecord); + return true; + } else if (exception == kExceptionEntryPtNotFound) { + EntryPointNotFoundHardError(ex_info->ExceptionRecord); + return true; + } else if (FacilityFromException(exception) == FACILITY_GRAPHICS_KERNEL) { + RaiseHardErrorMsg(exception, std::string(), std::string()); + return true; + } + return false; +} + +} // namespace breakpad diff --git a/components/crash/content/app/hard_error_handler_win.h b/components/crash/content/app/hard_error_handler_win.h new file mode 100644 index 0000000..f1afc6b --- /dev/null +++ b/components/crash/content/app/hard_error_handler_win.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef COMPONENTS_CRASH_CONTENT_APP_HARD_ERROR_HANDLER_WIN_H_ +#define COMPONENTS_CRASH_CONTENT_APP_HARD_ERROR_HANDLER_WIN_H_ + +#include <windows.h> + +namespace breakpad { + +// This function is in charge of displaying a dialog box that informs the +// user of a fatal condition in chrome. It is meant to be called from +// breakpad's unhandled exception handler after the crash dump has been +// created. The return value will be true if we are to retry launching +// chrome (and show the 'chrome has crashed' dialog) or to silently exit. +// +// This function only handles a few known exceptions, currently: +// - Failure to load a delayload dll. +// - Failure to bind to a delayloaded import. +// - Fatal Graphics card failure (aura build only). +// +// If any of these conditions are encountered, a message box shown by +// the operating system CSRSS process via NtRaiseHardError is invoked. +// The wording and localization is up to the operating system. +// +// Do not call this function for memory related errors like heap corruption +// or stack exahustion. This function assumes that memory allocations are +// possible. +bool HardErrorHandler(EXCEPTION_POINTERS* ex_info); + +} // namespace breakpad + +#endif // COMPONENTS_CRASH_CONTENT_APP_HARD_ERROR_HANDLER_WIN_H_ diff --git a/components/crash/content/browser/BUILD.gn b/components/crash/content/browser/BUILD.gn new file mode 100644 index 0000000..1af0d89 --- /dev/null +++ b/components/crash/content/browser/BUILD.gn @@ -0,0 +1,38 @@ +# 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_android) { + import("//build/config/android/config.gni") +} + +source_set("browser") { + sources = [ + "crash_dump_manager_android.cc", + "crash_dump_manager_android.h", + ] + + if (is_linux || is_android) { + set_sources_assignment_filter([]) + + # Want this file on both Linux and Android. + sources += [ + "crash_handler_host_linux.cc", + "crash_handler_host_linux.h", + ] + } + + deps = [ + "//base", + "//components/crash/content/app", + "//content/public/browser", + "//content/public/common", + ] + + # This is not in the GYP build but this target includes breakpad client + # headers, so add the dependency here. + if (is_posix && !is_ios) { + configs += [ "//breakpad:client_config" ] + public_configs = [ "//breakpad:client_config" ] + } +} diff --git a/components/crash/content/browser/DEPS b/components/crash/content/browser/DEPS new file mode 100644 index 0000000..c24130e --- /dev/null +++ b/components/crash/content/browser/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+content/public/browser", + "+content/public/common", +] diff --git a/components/crash/content/browser/crash_dump_manager_android.cc b/components/crash/content/browser/crash_dump_manager_android.cc new file mode 100644 index 0000000..62e77ad --- /dev/null +++ b/components/crash/content/browser/crash_dump_manager_android.cc @@ -0,0 +1,174 @@ +// 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. + +#include "components/crash/content/browser/crash_dump_manager_android.h" + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/posix/global_descriptors.h" +#include "base/process/process.h" +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/browser/file_descriptor_info.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_process_host.h" + +using content::BrowserThread; + +namespace breakpad { + +// static +CrashDumpManager* CrashDumpManager::instance_ = NULL; + +// static +CrashDumpManager* CrashDumpManager::GetInstance() { + CHECK(instance_); + return instance_; +} + +CrashDumpManager::CrashDumpManager(const base::FilePath& crash_dump_dir) + : crash_dump_dir_(crash_dump_dir) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(!instance_); + + instance_ = this; + + notification_registrar_.Add(this, + content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, + content::NotificationService::AllSources()); + notification_registrar_.Add(this, + content::NOTIFICATION_RENDERER_PROCESS_CLOSED, + content::NotificationService::AllSources()); + + BrowserChildProcessObserver::Add(this); +} + +CrashDumpManager::~CrashDumpManager() { + instance_ = NULL; + + BrowserChildProcessObserver::Remove(this); +} + +base::File CrashDumpManager::CreateMinidumpFile(int child_process_id) { + DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER); + base::FilePath minidump_path; + if (!base::CreateTemporaryFile(&minidump_path)) + return base::File(); + + // We need read permission as the minidump is generated in several phases + // and needs to be read at some point. + int flags = base::File::FLAG_OPEN | base::File::FLAG_READ | + base::File::FLAG_WRITE; + base::File minidump_file(minidump_path, flags); + if (!minidump_file.IsValid()) { + LOG(ERROR) << "Failed to create temporary file, crash won't be reported."; + return base::File(); + } + + { + base::AutoLock auto_lock(child_process_id_to_minidump_path_lock_); + DCHECK(!ContainsKey(child_process_id_to_minidump_path_, child_process_id)); + child_process_id_to_minidump_path_[child_process_id] = minidump_path; + } + return minidump_file.Pass(); +} + +// static +void CrashDumpManager::ProcessMinidump(const base::FilePath& minidump_path, + base::ProcessHandle pid) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + CHECK(instance_); + int64 file_size = 0; + int r = base::GetFileSize(minidump_path, &file_size); + DCHECK(r) << "Failed to retrieve size for minidump " + << minidump_path.value(); + + if (file_size == 0) { + // Empty minidump, this process did not crash. Just remove the file. + r = base::DeleteFile(minidump_path, false); + DCHECK(r) << "Failed to delete temporary minidump file " + << minidump_path.value(); + return; + } + + // We are dealing with a valid minidump. Copy it to the crash report + // directory from where Java code will upload it later on. + if (instance_->crash_dump_dir_.empty()) { + NOTREACHED() << "Failed to retrieve the crash dump directory."; + return; + } + const uint64 rand = base::RandUint64(); + const std::string filename = + base::StringPrintf("chromium-renderer-minidump-%016" PRIx64 ".dmp%d", + rand, pid); + base::FilePath dest_path = instance_->crash_dump_dir_.Append(filename); + r = base::Move(minidump_path, dest_path); + if (!r) { + LOG(ERROR) << "Failed to move crash dump from " << minidump_path.value() + << " to " << dest_path.value(); + base::DeleteFile(minidump_path, false); + return; + } + VLOG(1) << "Crash minidump successfully generated: " << + instance_->crash_dump_dir_.Append(filename).value(); +} + +void CrashDumpManager::BrowserChildProcessHostDisconnected( + const content::ChildProcessData& data) { + OnChildExit(data.id, data.handle); +} + +void CrashDumpManager::BrowserChildProcessCrashed( + const content::ChildProcessData& data, + int exit_code) { + OnChildExit(data.id, data.handle); +} + +void CrashDumpManager::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: + // NOTIFICATION_RENDERER_PROCESS_TERMINATED is sent when the renderer + // process is cleanly shutdown. However, we need to fallthrough so that + // we close the minidump_fd we kept open. + case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: { + content::RenderProcessHost* rph = + content::Source<content::RenderProcessHost>(source).ptr(); + OnChildExit(rph->GetID(), rph->GetHandle()); + break; + } + default: + NOTREACHED(); + return; + } +} + +void CrashDumpManager::OnChildExit(int child_process_id, + base::ProcessHandle pid) { + base::FilePath minidump_path; + { + base::AutoLock auto_lock(child_process_id_to_minidump_path_lock_); + ChildProcessIDToMinidumpPath::iterator iter = + child_process_id_to_minidump_path_.find(child_process_id); + if (iter == child_process_id_to_minidump_path_.end()) { + // We might get a NOTIFICATION_RENDERER_PROCESS_TERMINATED and a + // NOTIFICATION_RENDERER_PROCESS_CLOSED. + return; + } + minidump_path = iter->second; + child_process_id_to_minidump_path_.erase(iter); + } + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&CrashDumpManager::ProcessMinidump, minidump_path, pid)); +} + +} // namespace breakpad diff --git a/components/crash/content/browser/crash_dump_manager_android.h b/components/crash/content/browser/crash_dump_manager_android.h new file mode 100644 index 0000000..e3f2391 --- /dev/null +++ b/components/crash/content/browser/crash_dump_manager_android.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_DUMP_MANAGER_ANDROID_H_ +#define COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_DUMP_MANAGER_ANDROID_H_ + +#include <map> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/process/process.h" +#include "base/synchronization/lock.h" +#include "content/public/browser/browser_child_process_observer.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +namespace content { +class RenderProcessHost; +} + +namespace breakpad { + +// This class manages the crash minidumps. +// On Android, because of process isolation, each renderer process runs with a +// different UID. As a result, we cannot generate the minidumps in the browser +// (as the browser process does not have access to some system files for the +// crashed process). So the minidump is generated in the renderer process. +// Since the isolated process cannot open files, we provide it on creation with +// a file descriptor where to write the minidump in the event of a crash. +// This class creates these file descriptors and associates them with render +// processes and take the appropriate action when the render process terminates. +class CrashDumpManager : public content::BrowserChildProcessObserver, + public content::NotificationObserver { + public: + // The embedder should create a single instance of the CrashDumpManager. + static CrashDumpManager* GetInstance(); + + // Should be created on the UI thread. + explicit CrashDumpManager(const base::FilePath& crash_dump_dir); + + ~CrashDumpManager() override; + + // Returns a file that should be used to generate a minidump for the process + // |child_process_id|. + base::File CreateMinidumpFile(int child_process_id); + + private: + typedef std::map<int, base::FilePath> ChildProcessIDToMinidumpPath; + + static void ProcessMinidump(const base::FilePath& minidump_path, + base::ProcessHandle pid); + + // content::BrowserChildProcessObserver implementation: + void BrowserChildProcessHostDisconnected( + const content::ChildProcessData& data) override; + void BrowserChildProcessCrashed( + const content::ChildProcessData& data, + int exit_code) override; + + // NotificationObserver implementation: + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + + // Called on child process exit (including crash). + void OnChildExit(int child_process_id, base::ProcessHandle pid); + + content::NotificationRegistrar notification_registrar_; + + // This map should only be accessed with its lock aquired as it is accessed + // from the PROCESS_LAUNCHER and UI threads. + base::Lock child_process_id_to_minidump_path_lock_; + ChildProcessIDToMinidumpPath child_process_id_to_minidump_path_; + + base::FilePath crash_dump_dir_; + + static CrashDumpManager* instance_; + + DISALLOW_COPY_AND_ASSIGN(CrashDumpManager); +}; + +} // namespace breakpad + +#endif // COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_DUMP_MANAGER_ANDROID_H_ diff --git a/components/crash/content/browser/crash_handler_host_linux.cc b/components/crash/content/browser/crash_handler_host_linux.cc new file mode 100644 index 0000000..a29a3ed --- /dev/null +++ b/components/crash/content/browser/crash_handler_host_linux.cc @@ -0,0 +1,437 @@ +// 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. + +#include "components/crash/content/browser/crash_handler_host_linux.h" + +#include <stdint.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/format_macros.h" +#include "base/linux_util.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/rand_util.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread.h" +#include "breakpad/src/client/linux/handler/exception_handler.h" +#include "breakpad/src/client/linux/minidump_writer/linux_dumper.h" +#include "breakpad/src/client/linux/minidump_writer/minidump_writer.h" +#include "components/crash/content/app/breakpad_linux_impl.h" +#include "content/public/browser/browser_thread.h" + +#if defined(OS_ANDROID) && !defined(__LP64__) +#include <sys/linux-syscalls.h> + +#define SYS_read __NR_read +#endif + +using content::BrowserThread; +using google_breakpad::ExceptionHandler; + +namespace breakpad { + +namespace { + +const size_t kNumFDs = 1; +// The length of the control message: +const size_t kControlMsgSize = + CMSG_SPACE(kNumFDs * sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); +// The length of the regular payload: +const size_t kCrashContextSize = sizeof(ExceptionHandler::CrashContext); + +// Handles the crash dump and frees the allocated BreakpadInfo struct. +void CrashDumpTask(CrashHandlerHostLinux* handler, + scoped_ptr<BreakpadInfo> info) { + if (handler->IsShuttingDown() && info->upload) { + base::DeleteFile(base::FilePath(info->filename), false); +#if defined(ADDRESS_SANITIZER) + base::DeleteFile(base::FilePath(info->log_filename), false); +#endif + return; + } + + HandleCrashDump(*info); + delete[] info->filename; +#if defined(ADDRESS_SANITIZER) + delete[] info->log_filename; + delete[] info->asan_report_str; +#endif + delete[] info->process_type; + delete[] info->distro; + delete info->crash_keys; +} + +} // namespace + +// Since instances of CrashHandlerHostLinux are leaked, they are only destroyed +// at the end of the processes lifetime, which is greater in span than the +// lifetime of the IO message loop. Thus, all calls to base::Bind() use +// non-refcounted pointers. + +CrashHandlerHostLinux::CrashHandlerHostLinux(const std::string& process_type, + const base::FilePath& dumps_path, + bool upload) + : process_type_(process_type), + dumps_path_(dumps_path), + upload_(upload), + shutting_down_(false), + worker_pool_token_(BrowserThread::GetBlockingPool()->GetSequenceToken()) { + int fds[2]; + // We use SOCK_SEQPACKET rather than SOCK_DGRAM to prevent the process from + // sending datagrams to other sockets on the system. The sandbox may prevent + // the process from calling socket() to create new sockets, but it'll still + // inherit some sockets. With PF_UNIX+SOCK_DGRAM, it can call sendmsg to send + // a datagram to any (abstract) socket on the same system. With + // SOCK_SEQPACKET, this is prevented. + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + static const int on = 1; + + // Enable passcred on the server end of the socket + CHECK_EQ(0, setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))); + + process_socket_ = fds[0]; + browser_socket_ = fds[1]; + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&CrashHandlerHostLinux::Init, base::Unretained(this))); +} + +CrashHandlerHostLinux::~CrashHandlerHostLinux() { + close(process_socket_); + close(browser_socket_); +} + +void CrashHandlerHostLinux::StartUploaderThread() { + uploader_thread_.reset( + new base::Thread(process_type_ + "_crash_uploader")); + uploader_thread_->Start(); +} + +void CrashHandlerHostLinux::Init() { + base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); + CHECK(ml->WatchFileDescriptor( + browser_socket_, true /* persistent */, + base::MessageLoopForIO::WATCH_READ, + &file_descriptor_watcher_, this)); + ml->AddDestructionObserver(this); +} + +void CrashHandlerHostLinux::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +void CrashHandlerHostLinux::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_EQ(browser_socket_, fd); + + // A process has crashed and has signaled us by writing a datagram + // to the death signal socket. The datagram contains the crash context needed + // for writing the minidump as well as a file descriptor and a credentials + // block so that they can't lie about their pid. + // + // The message sender is in components/crash/content/app/breakpad_linux.cc. + + struct msghdr msg = {0}; + struct iovec iov[kCrashIovSize]; + + scoped_ptr<char[]> crash_context(new char[kCrashContextSize]); +#if defined(ADDRESS_SANITIZER) + scoped_ptr<char[]> asan_report(new char[kMaxAsanReportSize + 1]); +#endif + + scoped_ptr<CrashKeyStorage> crash_keys(new CrashKeyStorage); + google_breakpad::SerializedNonAllocatingMap* serialized_crash_keys; + size_t crash_keys_size = crash_keys->Serialize( + const_cast<const google_breakpad::SerializedNonAllocatingMap**>( + &serialized_crash_keys)); + + char* tid_buf_addr = NULL; + int tid_fd = -1; + uint64_t uptime; + size_t oom_size; + char control[kControlMsgSize]; + const ssize_t expected_msg_size = + kCrashContextSize + + sizeof(tid_buf_addr) + sizeof(tid_fd) + + sizeof(uptime) + +#if defined(ADDRESS_SANITIZER) + kMaxAsanReportSize + 1 + +#endif + sizeof(oom_size) + + crash_keys_size; + iov[0].iov_base = crash_context.get(); + iov[0].iov_len = kCrashContextSize; + iov[1].iov_base = &tid_buf_addr; + iov[1].iov_len = sizeof(tid_buf_addr); + iov[2].iov_base = &tid_fd; + iov[2].iov_len = sizeof(tid_fd); + iov[3].iov_base = &uptime; + iov[3].iov_len = sizeof(uptime); + iov[4].iov_base = &oom_size; + iov[4].iov_len = sizeof(oom_size); + iov[5].iov_base = serialized_crash_keys; + iov[5].iov_len = crash_keys_size; +#if !defined(ADDRESS_SANITIZER) + static_assert(5 == kCrashIovSize - 1, "kCrashIovSize should equal 6"); +#else + iov[6].iov_base = asan_report.get(); + iov[6].iov_len = kMaxAsanReportSize + 1; + static_assert(6 == kCrashIovSize - 1, "kCrashIovSize should equal 7"); +#endif + msg.msg_iov = iov; + msg.msg_iovlen = kCrashIovSize; + msg.msg_control = control; + msg.msg_controllen = kControlMsgSize; + + const ssize_t msg_size = HANDLE_EINTR(recvmsg(browser_socket_, &msg, 0)); + if (msg_size < 0) { + LOG(ERROR) << "Error reading from death signal socket. Crash dumping" + << " is disabled." + << " msg_size:" << msg_size + << " errno:" << errno; + file_descriptor_watcher_.StopWatchingFileDescriptor(); + return; + } + const bool bad_message = (msg_size != expected_msg_size || + msg.msg_controllen != kControlMsgSize || + msg.msg_flags & ~MSG_TRUNC); + base::ScopedFD signal_fd; + pid_t crashing_pid = -1; + if (msg.msg_controllen > 0) { + // Walk the control payload and extract the file descriptor and + // validated pid. + for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; + hdr = CMSG_NXTHDR(&msg, hdr)) { + if (hdr->cmsg_level != SOL_SOCKET) + continue; + if (hdr->cmsg_type == SCM_RIGHTS) { + const size_t len = hdr->cmsg_len - + (((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); + DCHECK_EQ(0U, len % sizeof(int)); + const size_t num_fds = len / sizeof(int); + if (num_fds != kNumFDs) { + // A nasty process could try and send us too many descriptors and + // force a leak. + LOG(ERROR) << "Death signal contained wrong number of descriptors;" + << " num_fds:" << num_fds; + for (size_t i = 0; i < num_fds; ++i) + close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i]); + return; + } + DCHECK(!signal_fd.is_valid()); + int fd = reinterpret_cast<int*>(CMSG_DATA(hdr))[0]; + DCHECK_GE(fd, 0); // The kernel should never send a negative fd. + signal_fd.reset(fd); + } else if (hdr->cmsg_type == SCM_CREDENTIALS) { + DCHECK_EQ(-1, crashing_pid); + const struct ucred *cred = + reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + crashing_pid = cred->pid; + } + } + } + + if (bad_message) { + LOG(ERROR) << "Received death signal message with the wrong size;" + << " msg.msg_controllen:" << msg.msg_controllen + << " msg.msg_flags:" << msg.msg_flags + << " kCrashContextSize:" << kCrashContextSize + << " kControlMsgSize:" << kControlMsgSize; + return; + } + if (crashing_pid == -1 || !signal_fd.is_valid()) { + LOG(ERROR) << "Death signal message didn't contain all expected control" + << " messages"; + return; + } + + // The crashing TID set inside the compromised context via + // sys_gettid() in ExceptionHandler::HandleSignal might be wrong (if + // the kernel supports PID namespacing) and may need to be + // translated. + // + // We expect the crashing thread to be in sys_read(), waiting for us to + // write to |signal_fd|. Most newer kernels where we have the different pid + // namespaces also have /proc/[pid]/syscall, so we can look through + // |actual_crashing_pid|'s thread group and find the thread that's in the + // read syscall with the right arguments. + + std::string expected_syscall_data; + // /proc/[pid]/syscall is formatted as follows: + // syscall_number arg1 ... arg6 sp pc + // but we just check syscall_number through arg3. + base::StringAppendF(&expected_syscall_data, "%d 0x%x %p 0x1 ", + SYS_read, tid_fd, tid_buf_addr); + bool syscall_supported = false; + pid_t crashing_tid = + base::FindThreadIDWithSyscall(crashing_pid, + expected_syscall_data, + &syscall_supported); + if (crashing_tid == -1) { + // We didn't find the thread we want. Maybe it didn't reach + // sys_read() yet or the thread went away. We'll just take a + // guess here and assume the crashing thread is the thread group + // leader. If procfs syscall is not supported by the kernel, then + // we assume the kernel also does not support TID namespacing and + // trust the TID passed by the crashing process. + LOG(WARNING) << "Could not translate tid - assuming crashing thread is " + "thread group leader; syscall_supported=" << syscall_supported; + crashing_tid = crashing_pid; + } + + ExceptionHandler::CrashContext* bad_context = + reinterpret_cast<ExceptionHandler::CrashContext*>(crash_context.get()); + bad_context->tid = crashing_tid; + + scoped_ptr<BreakpadInfo> info(new BreakpadInfo); + + info->fd = -1; + info->process_type_length = process_type_.length(); + // Freed in CrashDumpTask(). + char* process_type_str = new char[info->process_type_length + 1]; + process_type_.copy(process_type_str, info->process_type_length); + process_type_str[info->process_type_length] = '\0'; + info->process_type = process_type_str; + + // Memory released from scoped_ptrs below are also freed in CrashDumpTask(). + info->crash_keys = crash_keys.release(); +#if defined(ADDRESS_SANITIZER) + asan_report[kMaxAsanReportSize] = '\0'; + info->asan_report_str = asan_report.release(); + info->asan_report_length = strlen(info->asan_report_str); +#endif + + info->process_start_time = uptime; + info->oom_size = oom_size; +#if defined(OS_ANDROID) + // Nothing gets uploaded in android. + info->upload = false; +#else + info->upload = upload_; +#endif + + + BrowserThread::GetBlockingPool()->PostSequencedWorkerTask( + worker_pool_token_, + FROM_HERE, + base::Bind(&CrashHandlerHostLinux::WriteDumpFile, + base::Unretained(this), + base::Passed(&info), + base::Passed(&crash_context), + crashing_pid, + signal_fd.release())); +} + +void CrashHandlerHostLinux::WriteDumpFile(scoped_ptr<BreakpadInfo> info, + scoped_ptr<char[]> crash_context, + pid_t crashing_pid, + int signal_fd) { + DCHECK(BrowserThread::GetBlockingPool()->IsRunningSequenceOnCurrentThread( + worker_pool_token_)); + + // Set |info->distro| here because base::GetLinuxDistro() needs to run on a + // blocking thread. + std::string distro = base::GetLinuxDistro(); + info->distro_length = distro.length(); + // Freed in CrashDumpTask(). + char* distro_str = new char[info->distro_length + 1]; + distro.copy(distro_str, info->distro_length); + distro_str[info->distro_length] = '\0'; + info->distro = distro_str; + + base::FilePath dumps_path("/tmp"); + PathService::Get(base::DIR_TEMP, &dumps_path); + if (!info->upload) + dumps_path = dumps_path_; + const std::string minidump_filename = + base::StringPrintf("%s/chromium-%s-minidump-%016" PRIx64 ".dmp", + dumps_path.value().c_str(), + process_type_.c_str(), + base::RandUint64()); + + if (!google_breakpad::WriteMinidump(minidump_filename.c_str(), + kMaxMinidumpFileSize, + crashing_pid, + crash_context.get(), + kCrashContextSize, + google_breakpad::MappingList(), + google_breakpad::AppMemoryList())) { + LOG(ERROR) << "Failed to write crash dump for pid " << crashing_pid; + } +#if defined(ADDRESS_SANITIZER) + // Create a temporary file holding the AddressSanitizer report. + const base::FilePath log_path = + base::FilePath(minidump_filename).ReplaceExtension("log"); + base::WriteFile(log_path, info->asan_report_str, info->asan_report_length); +#endif + + // Freed in CrashDumpTask(). + char* minidump_filename_str = new char[minidump_filename.length() + 1]; + minidump_filename.copy(minidump_filename_str, minidump_filename.length()); + minidump_filename_str[minidump_filename.length()] = '\0'; + info->filename = minidump_filename_str; +#if defined(ADDRESS_SANITIZER) + // Freed in CrashDumpTask(). + char* minidump_log_filename_str = new char[minidump_filename.length() + 1]; + minidump_filename.copy(minidump_log_filename_str, minidump_filename.length()); + memcpy(minidump_log_filename_str + minidump_filename.length() - 3, "log", 3); + minidump_log_filename_str[minidump_filename.length()] = '\0'; + info->log_filename = minidump_log_filename_str; +#endif + info->pid = crashing_pid; + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&CrashHandlerHostLinux::QueueCrashDumpTask, + base::Unretained(this), + base::Passed(&info), + signal_fd)); +} + +void CrashHandlerHostLinux::QueueCrashDumpTask(scoped_ptr<BreakpadInfo> info, + int signal_fd) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + // Send the done signal to the process: it can exit now. + struct msghdr msg = {0}; + struct iovec done_iov; + done_iov.iov_base = const_cast<char*>("\x42"); + done_iov.iov_len = 1; + msg.msg_iov = &done_iov; + msg.msg_iovlen = 1; + + HANDLE_EINTR(sendmsg(signal_fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL)); + close(signal_fd); + + uploader_thread_->task_runner()->PostTask( + FROM_HERE, + base::Bind(&CrashDumpTask, base::Unretained(this), base::Passed(&info))); +} + +void CrashHandlerHostLinux::WillDestroyCurrentMessageLoop() { + file_descriptor_watcher_.StopWatchingFileDescriptor(); + + // If we are quitting and there are crash dumps in the queue, turn them into + // no-ops. + shutting_down_ = true; + uploader_thread_->Stop(); +} + +bool CrashHandlerHostLinux::IsShuttingDown() const { + return shutting_down_; +} + +} // namespace breakpad diff --git a/components/crash/content/browser/crash_handler_host_linux.h b/components/crash/content/browser/crash_handler_host_linux.h new file mode 100644 index 0000000..92ef522 --- /dev/null +++ b/components/crash/content/browser/crash_handler_host_linux.h @@ -0,0 +1,93 @@ +// 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. + +#ifndef COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_HANDLER_HOST_LINUX_H_ +#define COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_HANDLER_HOST_LINUX_H_ + +#include <sys/types.h> + +#include <string> + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/sequenced_worker_pool.h" + +namespace base { +class Thread; +} + +namespace breakpad { + +struct BreakpadInfo; + +// This is the host for processes which run breakpad inside the sandbox on +// Linux or Android. We perform the crash dump from the browser because it +// allows us to be outside the sandbox. +// +// Processes signal that they need to be dumped by sending a datagram over a +// UNIX domain socket. All processes of the same type share the client end of +// this socket which is installed in their descriptor table before exec. +class CrashHandlerHostLinux : public base::MessageLoopForIO::Watcher, + public base::MessageLoop::DestructionObserver { + public: + CrashHandlerHostLinux(const std::string& process_type, + const base::FilePath& dumps_path, + bool upload); + ~CrashHandlerHostLinux() override; + + // Starts the uploader thread. Must be called immediately after creating the + // class. + void StartUploaderThread(); + + // Get the file descriptor which processes should be given in order to signal + // crashes to the browser. + int GetDeathSignalSocket() const { + return process_socket_; + } + + // MessagePumbLibevent::Watcher impl: + void OnFileCanWriteWithoutBlocking(int fd) override; + void OnFileCanReadWithoutBlocking(int fd) override; + + // MessageLoop::DestructionObserver impl: + void WillDestroyCurrentMessageLoop() override; + + // Whether we are shutting down or not. + bool IsShuttingDown() const; + + private: + void Init(); + + // Do work in a sequenced worker pool for OnFileCanReadWithoutBlocking(). + void WriteDumpFile(scoped_ptr<BreakpadInfo> info, + scoped_ptr<char[]> crash_context, + pid_t crashing_pid, + int signal_fd); + + // Continue OnFileCanReadWithoutBlocking()'s work on the IO thread. + void QueueCrashDumpTask(scoped_ptr<BreakpadInfo> info, int signal_fd); + + std::string process_type_; + base::FilePath dumps_path_; + bool upload_; + + int process_socket_; + int browser_socket_; + + base::MessageLoopForIO::FileDescriptorWatcher file_descriptor_watcher_; + scoped_ptr<base::Thread> uploader_thread_; + bool shutting_down_; + + // Unique sequence token so that writing crash dump won't be blocked + // by other tasks. + base::SequencedWorkerPool::SequenceToken worker_pool_token_; + + DISALLOW_COPY_AND_ASSIGN(CrashHandlerHostLinux); +}; + +} // namespace breakpad + +#endif // COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_HANDLER_HOST_LINUX_H_ 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()) |