diff --git a/atom.gyp b/atom.gyp index 106d7f262f71..8e17e043db43 100644 --- a/atom.gyp +++ b/atom.gyp @@ -170,6 +170,8 @@ 'common/crash_reporter/crash_reporter_mac.mm', 'common/crash_reporter/crash_reporter_win.cc', 'common/crash_reporter/crash_reporter_win.h', + 'common/crash_reporter/linux/crash_dump_handler.cc', + 'common/crash_reporter/linux/crash_dump_handler.h', 'common/crash_reporter/win/crash_service.cc', 'common/crash_reporter/win/crash_service.h', 'common/crash_reporter/win/crash_service_main.cc', @@ -411,6 +413,13 @@ '-rdynamic', ], }, + # Required settings of using breakpad. + 'include_dirs': [ + 'vendor/breakpad/src', + ], + 'cflags': [ + '-Wno-empty-body', + ], 'dependencies': [ 'vendor/breakpad/breakpad.gyp:breakpad_client', ], diff --git a/common/crash_reporter/crash_reporter_linux.cc b/common/crash_reporter/crash_reporter_linux.cc index 2589c99aa35b..8f1cd3d2134c 100644 --- a/common/crash_reporter/crash_reporter_linux.cc +++ b/common/crash_reporter/crash_reporter_linux.cc @@ -1,28 +1,186 @@ -// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Copyright (c) 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 "common/crash_reporter/crash_reporter_linux.h" +// #include +// #include +// #include +// #include +// #include +#include +// #include +// #include +// #include +// #include +#include + +#include "base/debug/crash_logging.h" +#include "base/files/file_path.h" +#include "base/linux_util.h" +#include "base/logging.h" +#include "base/path_service.h" +// #include "base/platform_file.h" +// #include "base/posix/global_descriptors.h" +#include "base/process/memory.h" #include "base/memory/singleton.h" +// #include "base/strings/string_util.h" +#include "common/crash_reporter/linux/crash_dump_handler.h" +// #include "content/public/common/content_descriptors.h" +#include "vendor/breakpad/src/client/linux/handler/exception_handler.h" +#include "vendor/breakpad/src/common/linux/linux_libc_support.h" + +using google_breakpad::ExceptionHandler; +using google_breakpad::MinidumpDescriptor; namespace crash_reporter { +namespace { + +static const size_t kDistroSize = 128; + +// 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; + +uint64_t g_process_start_time = 0; +pid_t g_pid = 0; +ExceptionHandler* g_breakpad = NULL; + +CrashKeyStorage* g_crash_keys = NULL; + +// 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; +} + +void SetProcessStartTime() { + // Set the base process start time value. + struct timeval tv; + if (!gettimeofday(&tv, NULL)) + g_process_start_time = timeval_to_ms(&tv); + else + g_process_start_time = 0; +} + +// Populates the passed in allocated string and its size with the distro of +// the crashing process. +// The passed string is expected to be at least kDistroSize bytes long. +void PopulateDistro(char* distro, size_t* distro_len_param) { + size_t distro_len = std::min(my_strlen(base::g_linux_distro), kDistroSize); + memcpy(distro, base::g_linux_distro, distro_len); + if (distro_len_param) + *distro_len_param = distro_len; +} + +bool CrashDone(const MinidumpDescriptor& minidump, + 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) { + 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(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 + // TODO(zcbenz): Set the correct process_type here. + 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 = true; + 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); + return true; +} + +#if defined(ADDRESS_SANITIZER) +extern "C" +void __asan_set_error_report_callback(void (*cb)(const char*)); + +extern "C" +void AsanLinuxBreakpadCallback(const char* report) { + // Send minidump here. + g_breakpad->SimulateSignalDelivery(SIGKILL); +} +#endif + +} // namespace + CrashReporterLinux::CrashReporterLinux() { + SetProcessStartTime(); + g_pid = getpid(); + + // Make base::g_linux_distro work. + base::SetLinuxDistro(base::GetLinuxDistro()); } CrashReporterLinux::~CrashReporterLinux() { } void CrashReporterLinux::InitBreakpad(const std::string& product_name, - const std::string& version, - const std::string& company_name, - const std::string& submit_url, - bool auto_submit, - bool skip_system_crash_handler) { + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) { + EnableCrashDumping(); + +#if defined(ADDRESS_SANITIZER) + // Register the callback for AddressSanitizer error reporting. + __asan_set_error_report_callback(AsanLinuxBreakpadCallback); +#endif } void CrashReporterLinux::SetUploadParameters() { + upload_parameters_["platform"] = "linux"; + + // TODO(zcbenz): Convert upload parameters to crash keys here. + g_crash_keys = new CrashKeyStorage; +} + +void CrashReporterLinux::EnableCrashDumping() { + base::FilePath tmp_path("/tmp"); + PathService::Get(base::DIR_TEMP, &tmp_path); + + base::FilePath dumps_path(tmp_path); + DCHECK(!g_breakpad); + MinidumpDescriptor minidump_descriptor(dumps_path.value()); + minidump_descriptor.set_size_limit(kMaxMinidumpFileSize); + + g_breakpad = new ExceptionHandler( + minidump_descriptor, + NULL, + CrashDone, + this, + true, // Install handlers. + -1); // Server file descriptor. -1 for in-process. } // static diff --git a/common/crash_reporter/crash_reporter_linux.h b/common/crash_reporter/crash_reporter_linux.h index a968a3946591..9ecd7cb42a6b 100644 --- a/common/crash_reporter/crash_reporter_linux.h +++ b/common/crash_reporter/crash_reporter_linux.h @@ -1,4 +1,4 @@ -// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Copyright (c) 2014 GitHub, Inc. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -30,9 +30,10 @@ class CrashReporterLinux : public CrashReporter { CrashReporterLinux(); virtual ~CrashReporterLinux(); + void EnableCrashDumping(); + DISALLOW_COPY_AND_ASSIGN(CrashReporterLinux); }; - } // namespace crash_reporter #endif // ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_LINUX_H_ diff --git a/common/crash_reporter/linux/crash_dump_handler.cc b/common/crash_reporter/linux/crash_dump_handler.cc new file mode 100644 index 000000000000..c63d9a15b6a2 --- /dev/null +++ b/common/crash_reporter/linux/crash_dump_handler.cc @@ -0,0 +1,707 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Copyright (c) 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 "common/crash_reporter/linux/crash_dump_handler.h" + +#include + +#include + +#include "base/posix/eintr_wrapper.h" +#include "vendor/breakpad/src/client/linux/minidump_writer/directory_reader.h" +#include "vendor/breakpad/src/common/linux/linux_libc_support.h" +#include "vendor/breakpad/src/common/memory.h" + +#include "third_party/lss/linux_syscall_support.h" + +#define STAT_STRUCT struct kernel_stat +#define FSTAT_FUNC sys_fstat + +// 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) + +namespace crash_reporter { + +namespace { + +const char kUploadURL[] = "https://clients2.google.com/cr/report"; + +// String buffer size to use to convert a uint64_t to string. +const size_t kUint64StringSize = 21; + +// 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; + } +} + +// 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); +} + +// 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; +} + +size_t LengthWithoutTrailingSpaces(const char* str, size_t len) { + while (len > 0 && str[len - 1] == ' ') { + len--; + } + return len; +} + +// MIME substrings. +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_; + + 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(base); + iov_[iov_index_].iov_len = size; + ++iov_index_; +} + +void MimeWriter::AddItemWithoutTrailingSpaces(const void* base, size_t size) { + AddItem(base, LengthWithoutTrailingSpaces(static_cast(base), + size)); +} + + + +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(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) { + // The --header argument to wget looks like: + // --header=Content-Type: multipart/form-data; boundary=XYZ + // where the boundary has two fewer leading '-' chars + static const char header_msg[] = + "--header=Content-Type: multipart/form-data; boundary="; + char* const header = reinterpret_cast(allocator->Alloc( + sizeof(header_msg) - 1 + strlen(mime_boundary) - 2 + 1)); + memcpy(header, header_msg, sizeof(header_msg) - 1); + memcpy(header + sizeof(header_msg) - 1, mime_boundary + 2, + strlen(mime_boundary) - 2); + // We grab the NUL byte from the end of |mime_boundary|. + + // The --post-file argument to wget looks like: + // --post-file=/tmp/... + static const char post_file_msg[] = "--post-file="; + char* const post_file = reinterpret_cast(allocator->Alloc( + sizeof(post_file_msg) - 1 + strlen(dumpfile) + 1)); + memcpy(post_file, post_file_msg, sizeof(post_file_msg) - 1); + memcpy(post_file + sizeof(post_file_msg) - 1, dumpfile, strlen(dumpfile)); + + static const char kWgetBinary[] = "/usr/bin/wget"; + const char* args[] = { + kWgetBinary, + header, + post_file, + // TODO(zcbenz): Enabling custom upload url. + 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", + NULL, + }; + static const char msg[] = "Cannot upload crash dump: cannot exec " + "/usr/bin/wget\n"; + execve(args[0], const_cast(args), environ); + WriteLog(msg, sizeof(msg) - 1); + sys__exit(1); +} + +} // namespace + +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 = NULL; + + 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) { + 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 + // + // \r\n BOUNDARY -- \r\n + + MimeWriter writer(temp_file_fd, mime_boundary); + { + // TODO(zcbenz): Set version and product_name from JS API. + std::string product_name("Atom-Shell"); + std::string version("0.1.0"); + + writer.AddBoundary(); + writer.AddPairString("prod", product_name.c_str()); + writer.AddBoundary(); + writer.AddPairString("ver", version.c_str()); + 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(); + } + writer.Flush(); + } + + if (info.process_start_time > 0) { + struct kernel_timeval tv; + if (!sys_gettimeofday(&tv, NULL)) { + 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 (!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])); + char id_buf[17]; // Crash report IDs are expected to be 16 chars. + ssize_t len = -1; + // 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 = fds[0]; + 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. + len = HANDLE_EINTR(sys_read(fds[0], id_buf, sizeof(id_buf) - 1)); + break; + } + // ret == 0 -> timed out, continue waiting. + } + if (len > 0) { + // Write crash dump id to stderr. + id_buf[len] = 0; + static const char msg[] = "\nCrash dump id: "; + WriteLog(msg, sizeof(msg) - 1); + WriteLog(id_buf, my_strlen(id_buf)); + WriteLog("\n", 1); + } + if (sys_waitpid(upload_child, NULL, 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, NULL, 0)); +} + +size_t WriteLog(const char* buf, size_t nbytes) { + return sys_write(2, buf, nbytes); +} + +} // namespace crash_reporter diff --git a/common/crash_reporter/linux/crash_dump_handler.h b/common/crash_reporter/linux/crash_dump_handler.h new file mode 100644 index 000000000000..3ff5d7964a1b --- /dev/null +++ b/common/crash_reporter/linux/crash_dump_handler.h @@ -0,0 +1,44 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Copyright (c) 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 ATOM_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ +#define ATOM_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ + +#include "base/basictypes.h" +#include "vendor/breakpad/src/common/simple_string_dictionary.h" + +namespace crash_reporter { + +typedef google_breakpad::NonAllocatingMap<256, 256, 64> CrashKeyStorage; + +// 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; +}; + +void HandleCrashDump(const BreakpadInfo& info); + +size_t WriteLog(const char* buf, size_t nbytes); + +} // namespace crash_reporter + +#endif // ATOM_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_