diff options
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/app/breakpad_linux.cc | 392 | ||||
-rw-r--r-- | chrome/app/breakpad_linux.h | 12 | ||||
-rw-r--r-- | chrome/app/breakpad_linux_stub.cc | 9 | ||||
-rw-r--r-- | chrome/browser/browser_main.cc | 8 | ||||
-rw-r--r-- | chrome/browser/renderer_host/browser_render_process_host.cc | 9 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_crash_handler_host_linux.cc | 187 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_crash_handler_host_linux.h | 47 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_crash_handler_host_linux_stub.cc | 25 | ||||
-rw-r--r-- | chrome/chrome.gyp | 61 | ||||
-rw-r--r-- | chrome/renderer/render_crash_handler_linux.cc | 85 | ||||
-rw-r--r-- | chrome/renderer/render_crash_handler_linux.h | 16 | ||||
-rw-r--r-- | chrome/renderer/render_crash_handler_linux_stub.cc | 9 | ||||
-rw-r--r-- | chrome/renderer/renderer_logging_linux.cc | 8 | ||||
-rw-r--r-- | chrome/renderer/renderer_main.cc | 7 |
14 files changed, 871 insertions, 4 deletions
diff --git a/chrome/app/breakpad_linux.cc b/chrome/app/breakpad_linux.cc new file mode 100644 index 0000000..7827231 --- /dev/null +++ b/chrome/app/breakpad_linux.cc @@ -0,0 +1,392 @@ +// Copyright (c) 2009 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 <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include "base/eintr_wrapper.h" +#include "base/rand_util.h" +#include "base/file_version_info_linux.h" +#include "breakpad/linux/directory_reader.h" +#include "breakpad/linux/exception_handler.h" +#include "breakpad/linux/linux_libc_support.h" +#include "breakpad/linux/linux_syscall_support.h" +#include "breakpad/linux/memory.h" + +static const char kUploadURL[] = + "https://clients2.google.com/cr/report"; + +// Writes the value |v| as 16 hex characters to the memory pointed at by +// |output|. +static 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; + } +} + +pid_t UploadCrashDump(const char* filename, const char* crash_url, + unsigned crash_url_length) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + + const int dumpfd = sys_open(filename, O_RDONLY, 0); + if (dumpfd < 0) { + static const char msg[] = "Cannot upload crash dump: failed to open\n"; + sys_write(2, msg, sizeof(msg)); + return -1; + } + struct kernel_stat st; + if (sys_fstat(dumpfd, &st) != 0) { + static const char msg[] = "Cannot upload crash dump: stat failed\n"; + sys_write(2, msg, sizeof(msg)); + sys_close(dumpfd); + return -1; + } + + google_breakpad::PageAllocator allocator; + + uint8_t* dump_data = reinterpret_cast<uint8_t*>(allocator.Alloc(st.st_size)); + if (!dump_data) { + static const char msg[] = "Cannot upload crash dump: cannot alloc\n"; + sys_write(2, msg, sizeof(msg)); + sys_close(dumpfd); + return -1; + } + + sys_read(dumpfd, dump_data, st.st_size); + sys_close(dumpfd); + + // 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"; + sys_write(2, msg, sizeof(msg) - 1); + return -1; + } + + static const char temp_file_template[] = + "/tmp/chromium-upload-XXXXXXXXXXXXXXXX"; + char buf[sizeof(temp_file_template)]; + memcpy(buf, temp_file_template, sizeof(temp_file_template)); + + int fd = -1; + for (unsigned i = 0; i < 10; ++i) { + uint64_t t; + read(ufd, &t, sizeof(t)); + write_uint64_hex(buf + sizeof(buf) - (16 + 1), t); + + fd = sys_open(buf, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd >= 0) + break; + } + + if (fd == -1) { + static const char msg[] = "Failed to create temporary file in /tmp: cannot " + "upload crash dump\n"; + sys_write(2, msg, sizeof(msg) - 1); + sys_close(ufd); + return -1; + } + + // The MIME boundary is 28 hypens, 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; + sys_close(ufd); + + // The define for the product version is a wide string, so we need to + // downconvert it. + static const wchar_t version[] = PRODUCT_VERSION; + static const unsigned version_len = sizeof(version) / sizeof(wchar_t); + char version_msg[version_len]; + for (unsigned i = 0; i < version_len; ++i) + version_msg[i] = static_cast<char>(version[i]); + + // The MIME block looks like this: + // BOUNDARY \r\n (0, 1) + // Content-Disposition: form-data; name="prod" \r\n \r\n (2..6) + // Chrome_Linux \r\n (7, 8) + // BOUNDARY \r\n (9, 10) + // Content-Disposition: form-data; name="ver" \r\n \r\n (11..15) + // 1.2.3.4 \r\n (16, 17) + // BOUNDARY \r\n (18, 19) + // + // zero or more: + // Content-Disposition: form-data; name="url-chunk-1" \r\n \r\n (0..5) + // abcdef \r\n (6, 7) + // BOUNDARY \r\n (8, 9) + // + // Content-Disposition: form-data; name="dump"; filename="dump" \r\n (0,1,2) + // Content-Type: application/octet-stream \r\n \r\n (3,4,5) + // <dump contents> (6) + // \r\n BOUNDARY -- \r\n (7,8,9,10) + + static const char rn[] = {'\r', '\n'}; + static const char form_data_msg[] = "Content-Disposition: form-data; name=\""; + static const char prod_msg[] = "prod"; + static const char quote_msg[] = {'"'}; + static const char chrome_linux_msg[] = "Chrome_Linux"; + static const char ver_msg[] = "ver"; + static const char dashdash_msg[] = {'-', '-'}; + static const char dump_msg[] = "upload_file_minidump\"; filename=\"dump\""; + static const char content_type_msg[] = + "Content-Type: application/octet-stream"; + static const char url_chunk_msg[] = "url-chunk-"; + + struct kernel_iovec iov[20]; + iov[0].iov_base = mime_boundary; + iov[0].iov_len = sizeof(mime_boundary) - 1; + iov[1].iov_base = const_cast<char*>(rn); + iov[1].iov_len = sizeof(rn); + + iov[2].iov_base = const_cast<char*>(form_data_msg); + iov[2].iov_len = sizeof(form_data_msg) - 1; + iov[3].iov_base = const_cast<char*>(prod_msg); + iov[3].iov_len = sizeof(prod_msg) - 1; + iov[4].iov_base = const_cast<char*>(quote_msg); + iov[4].iov_len = sizeof(quote_msg); + iov[5].iov_base = const_cast<char*>(rn); + iov[5].iov_len = sizeof(rn); + iov[6].iov_base = const_cast<char*>(rn); + iov[6].iov_len = sizeof(rn); + + iov[7].iov_base = const_cast<char*>(chrome_linux_msg); + iov[7].iov_len = sizeof(chrome_linux_msg) - 1; + iov[8].iov_base = const_cast<char*>(rn); + iov[8].iov_len = sizeof(rn); + + iov[9].iov_base = mime_boundary; + iov[9].iov_len = sizeof(mime_boundary) - 1; + iov[10].iov_base = const_cast<char*>(rn); + iov[10].iov_len = sizeof(rn); + + iov[11].iov_base = const_cast<char*>(form_data_msg); + iov[11].iov_len = sizeof(form_data_msg) - 1; + iov[12].iov_base = const_cast<char*>(ver_msg); + iov[12].iov_len = sizeof(ver_msg) - 1; + iov[13].iov_base = const_cast<char*>(quote_msg); + iov[13].iov_len = sizeof(quote_msg); + iov[14].iov_base = const_cast<char*>(rn); + iov[14].iov_len = sizeof(rn); + iov[15].iov_base = const_cast<char*>(rn); + iov[15].iov_len = sizeof(rn); + + iov[16].iov_base = const_cast<char*>(version_msg); + iov[16].iov_len = sizeof(version_msg) - 1; + iov[17].iov_base = const_cast<char*>(rn); + iov[17].iov_len = sizeof(rn); + + iov[18].iov_base = mime_boundary; + iov[18].iov_len = sizeof(mime_boundary) - 1; + iov[19].iov_base = const_cast<char*>(rn); + iov[19].iov_len = sizeof(rn); + + sys_writev(fd, iov, 20); + + if (crash_url_length) { + unsigned i = 0, done = 0; + static const unsigned kMaxCrashChunkSize = 64; + + while (crash_url_length) { + char num[16]; + const unsigned num_len = my_int_len(++i); + my_itos(num, i, num_len); + + iov[0].iov_base = const_cast<char*>(form_data_msg); + iov[0].iov_len = sizeof(form_data_msg) - 1; + iov[1].iov_base = const_cast<char*>(url_chunk_msg); + iov[1].iov_len = sizeof(url_chunk_msg) - 1; + iov[2].iov_base = num; + iov[2].iov_len = num_len; + iov[3].iov_base = const_cast<char*>(quote_msg); + iov[3].iov_len = sizeof(quote_msg); + iov[4].iov_base = const_cast<char*>(rn); + iov[4].iov_len = sizeof(rn); + iov[5].iov_base = const_cast<char*>(rn); + iov[5].iov_len = sizeof(rn); + + const unsigned len = crash_url_length > kMaxCrashChunkSize ? + kMaxCrashChunkSize : crash_url_length; + iov[6].iov_base = const_cast<char*>(crash_url + done); + iov[6].iov_len = len; + iov[7].iov_base = const_cast<char*>(rn); + iov[7].iov_len = sizeof(rn); + iov[8].iov_base = mime_boundary; + iov[8].iov_len = sizeof(mime_boundary) - 1; + iov[9].iov_base = const_cast<char*>(rn); + iov[9].iov_len = sizeof(rn); + + sys_writev(fd, iov, 10); + + done += len; + crash_url_length -= len; + } + } + + iov[0].iov_base = const_cast<char*>(form_data_msg); + iov[0].iov_len = sizeof(form_data_msg) - 1; + iov[1].iov_base = const_cast<char*>(dump_msg); + iov[1].iov_len = sizeof(dump_msg) - 1; + iov[2].iov_base = const_cast<char*>(rn); + iov[2].iov_len = sizeof(rn); + + iov[3].iov_base = const_cast<char*>(content_type_msg); + iov[3].iov_len = sizeof(content_type_msg) - 1; + iov[4].iov_base = const_cast<char*>(rn); + iov[4].iov_len = sizeof(rn); + iov[5].iov_base = const_cast<char*>(rn); + iov[5].iov_len = sizeof(rn); + + iov[6].iov_base = dump_data; + iov[6].iov_len = st.st_size; + + iov[7].iov_base = const_cast<char*>(rn); + iov[7].iov_len = sizeof(rn); + iov[8].iov_base = mime_boundary; + iov[8].iov_len = sizeof(mime_boundary) - 1; + iov[9].iov_base = const_cast<char*>(dashdash_msg); + iov[9].iov_len = sizeof(dashdash_msg); + iov[10].iov_base = const_cast<char*>(rn); + iov[10].iov_len = sizeof(rn); + + sys_writev(fd, iov, 11); + + sys_close(fd); + + // 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<char*>(allocator.Alloc( + sizeof(header_msg) - 1 + sizeof(mime_boundary) - 2)); + memcpy(header, header_msg, sizeof(header_msg) - 1); + memcpy(header + sizeof(header_msg) - 1, mime_boundary + 2, + sizeof(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<char*>(allocator.Alloc( + sizeof(post_file_msg) - 1 + sizeof(buf))); + memcpy(post_file, post_file_msg, sizeof(post_file_msg) - 1); + memcpy(post_file + sizeof(post_file_msg) - 1, buf, sizeof(buf)); + + const pid_t child = sys_fork(); + if (!child) { + // This code is called both when a browser is crashing (in which case, + // nothing really matters any more) and when a renderer 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) + 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) + sys_close(fd); + reader.PopEntry(); + } + + sys_close(fd); + } + + sys_setsid(); + + // Leave one end of a pipe in the wget process and watch for it getting + // closed by the wget process exiting. + int fds[2]; + sys_pipe(fds); + + const pid_t child = sys_fork(); + if (child) { + sys_close(fds[1]); + char buf[32]; + HANDLE_EINTR(read(fds[0], buf, sizeof(buf) - 1)); + buf[sizeof(buf) - 1] = 0; + static const char msg[] = "\nCrash dump id: "; + sys_write(2, msg, sizeof(msg) - 1); + sys_write(2, buf, my_strlen(buf)); + sys_write(2, "\n", 1); + sys_unlink(filename); + sys_unlink(buf); + sys__exit(0); + } + + sys_close(fds[0]); + sys_dup2(fds[1], 3); + static const char* const kWgetBinary = "/usr/bin/wget"; + const char* args[] = { + kWgetBinary, + header, + post_file, + kUploadURL, + "-O", // output reply to fd 3 + "/dev/fd/3", + NULL, + }; + + execv("/usr/bin/wget", const_cast<char**>(args)); + static const char msg[] = "Cannot update crash dump: cannot exec " + "/usr/bin/wget\n"; + sys_write(2, msg, sizeof(msg) - 1); + sys__exit(1); + } + + return child; +} + +static bool CrashDone(const char* dump_path, + const char* minidump_id, + void* context, + bool succeeded) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + if (!succeeded) + return false; + + google_breakpad::PageAllocator allocator; + const unsigned dump_path_len = my_strlen(dump_path); + const unsigned minidump_id_len = my_strlen(minidump_id); + char *const path = reinterpret_cast<char*>(allocator.Alloc( + dump_path_len + 1 /* '/' */ + minidump_id_len + + 4 /* ".dmp" */ + 1 /* NUL */)); + memcpy(path, dump_path, dump_path_len); + path[dump_path_len] = '/'; + memcpy(path + dump_path_len + 1, minidump_id, minidump_id_len); + memcpy(path + dump_path_len + 1 + minidump_id_len, ".dmp", 4); + path[dump_path_len + 1 + minidump_id_len + 4] = 0; + + UploadCrashDump(path, NULL, 0); + + return true; +} + +void EnableCrashDumping() { + // We leak this object. + + new google_breakpad::ExceptionHandler("/tmp", NULL, CrashDone, NULL, + true /* install handlers */); +} diff --git a/chrome/app/breakpad_linux.h b/chrome/app/breakpad_linux.h new file mode 100644 index 0000000..49a778d --- /dev/null +++ b/chrome/app/breakpad_linux.h @@ -0,0 +1,12 @@ +// Copyright (c) 2009 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 CHROME_APP_BREAKPAD_LINUX_H_ +#define CHROME_APP_BREAKPAD_LINUX_H_ + +extern void EnableCrashDumping(); +extern int UploadCrashDump(const char* filename, const char* crash_url, + unsigned crash_url_length); + +#endif // CHROME_APP_BREAKPAD_LINUX_H_ diff --git a/chrome/app/breakpad_linux_stub.cc b/chrome/app/breakpad_linux_stub.cc new file mode 100644 index 0000000..ee99606 --- /dev/null +++ b/chrome/app/breakpad_linux_stub.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2009 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. + +// This is a stub file which is compiled in when we are building without +// breakpad support. + +void EnableCrashDumping() { +} diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc index 78ae72d..af905c4 100644 --- a/chrome/browser/browser_main.cc +++ b/chrome/browser/browser_main.cc @@ -65,6 +65,10 @@ #include <signal.h> #endif +#if defined(OS_LINUX) +#include "chrome/app/breakpad_linux.h" +#endif + // TODO(port): several win-only methods have been pulled out of this, but // BrowserMain() as a whole needs to be broken apart so that it's usable by // other platforms. For now, it's just a stub. This is a serious work in @@ -248,6 +252,10 @@ int BrowserMain(const MainFunctionParams& parameters) { CHECK(sigaction(SIGCHLD, &action, NULL) == 0); #endif +#if defined(OS_LINUX) + EnableCrashDumping(); +#endif + // Do platform-specific things (such as finishing initializing Cocoa) // prior to instantiating the message loop. This could be turned into a // broadcast notification. diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc index 2061e25..4624ff6 100644 --- a/chrome/browser/renderer_host/browser_render_process_host.cc +++ b/chrome/browser/renderer_host/browser_render_process_host.cc @@ -34,6 +34,9 @@ #include "chrome/browser/history/history.h" #include "chrome/browser/plugin_service.h" #include "chrome/browser/profile.h" +#if defined(OS_LINUX) +#include "chrome/browser/renderer_host/render_crash_handler_host_linux.h" +#endif #include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/browser/renderer_host/render_widget_helper.h" #include "chrome/browser/renderer_host/render_widget_host.h" @@ -337,6 +340,12 @@ bool BrowserRenderProcessHost::Init() { channel_->GetClientFileDescriptorMapping(&src_fd, &dest_fd); if (src_fd > -1) fds_to_map.push_back(std::pair<int, int>(src_fd, dest_fd)); +#if defined(OS_LINUX) + const int crash_signal_fd = + Singleton<RenderCrashHandlerHostLinux>()->GetDeathSignalSocket(); + if (crash_signal_fd >= 0) + fds_to_map.push_back(std::make_pair(crash_signal_fd, 4)); +#endif base::LaunchApp(cmd_line.argv(), fds_to_map, false, &process); #endif if (!process) { diff --git a/chrome/browser/renderer_host/render_crash_handler_host_linux.cc b/chrome/browser/renderer_host/render_crash_handler_host_linux.cc new file mode 100644 index 0000000..a7342c5 --- /dev/null +++ b/chrome/browser/renderer_host/render_crash_handler_host_linux.cc @@ -0,0 +1,187 @@ +// Copyright (c) 2009 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 "chrome/browser/renderer_host/render_crash_handler_host_linux.h" + +#define __STDC_FORMAT_MACROS +#include <stdint.h> +#include <inttypes.h> + +#include <unistd.h> +#include <sys/uio.h> +#include <sys/socket.h> + +#include "base/eintr_wrapper.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/rand_util.h" +#include "base/string_util.h" +#include "breakpad/linux/exception_handler.h" +#include "breakpad/linux/linux_dumper.h" +#include "breakpad/linux/minidump_writer.h" +#include "chrome/app/breakpad_linux.h" +#include "chrome/browser/chrome_thread.h" + +// Since RenderCrashHandlerHostLinux is a singleton, it's only destroyed at the +// end of the processes lifetime, which is greater in span then the lifetime of +// the IO message loop. +template<> struct RunnableMethodTraits<RenderCrashHandlerHostLinux> { + static void RetainCallee(RenderCrashHandlerHostLinux*) { } + static void ReleaseCallee(RenderCrashHandlerHostLinux*) { } +}; + +RenderCrashHandlerHostLinux::RenderCrashHandlerHostLinux() + : renderer_socket_(-1), + browser_socket_(-1) { + int fds[2]; + CHECK(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == 0); + static const int on = 1; + + // Enable passcred on the server end of the socket + CHECK(setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) == 0); + + renderer_socket_ = fds[0]; + browser_socket_ = fds[1]; + + MessageLoop* ml = ChromeThread::GetMessageLoop(ChromeThread::IO); + ml->PostTask(FROM_HERE, NewRunnableMethod( + this, &RenderCrashHandlerHostLinux::Init)); +} + +RenderCrashHandlerHostLinux::~RenderCrashHandlerHostLinux() { + HANDLE_EINTR(close(renderer_socket_)); + HANDLE_EINTR(close(browser_socket_)); +} + +void RenderCrashHandlerHostLinux::Init() { + MessageLoopForIO* ml = MessageLoopForIO::current(); + CHECK(ml->WatchFileDescriptor( + browser_socket_, true /* persistent */, + MessageLoopForIO::WATCH_READ, + &file_descriptor_watcher_, this)); + ml->AddDestructionObserver(this); +} + +void RenderCrashHandlerHostLinux::OnFileCanWriteWithoutBlocking(int fd) { + DCHECK(false); +} + +void RenderCrashHandlerHostLinux::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_EQ(fd, browser_socket_); + + // A renderer 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 length of the control message: + static const unsigned kControlMsgSize = + CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); + // The length of the regular payload: + static const unsigned kCrashContextSize = + sizeof(google_breakpad::ExceptionHandler::CrashContext); + static const unsigned kMaxActiveURLSize = 1024; + + struct msghdr msg = {0}; + struct iovec iov; + char context[kCrashContextSize + kMaxActiveURLSize]; + char control[kControlMsgSize]; + iov.iov_base = context; + iov.iov_len = sizeof(context); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = kControlMsgSize; + + const ssize_t n = HANDLE_EINTR(recvmsg(browser_socket_, &msg, 0)); + if (n < 1) { + LOG(ERROR) << "Error reading from death signal socket. Crash dumping" + << " is disabled." + << " n:" << n + << " errno:" << errno; + file_descriptor_watcher_.StopWatchingFileDescriptor(); + return; + } + + if (n < static_cast<ssize_t>(kCrashContextSize) || + msg.msg_controllen != kControlMsgSize || + msg.msg_flags & ~MSG_TRUNC) { + LOG(ERROR) << "Received death signal message with the wrong size;" + << " n:" << n + << " msg.msg_controllen:" << msg.msg_controllen + << " msg.msg_flags:" << msg.msg_flags + << " kCrashContextSize:" << kCrashContextSize + << " kControlMsgSize:" << kControlMsgSize; + return; + } + + // Anything in the message contents after the crash context is the crashing + // URL. + const char* const crash_url = &context[kCrashContextSize]; + const unsigned crash_url_len = n - kCrashContextSize; + + // Walk the control payload an extract the file descriptor and validated pid. + pid_t crashing_pid = -1; + int signal_fd = -1; + 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 unsigned len = hdr->cmsg_len - + (((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); + DCHECK(len % sizeof(int) == 0); + const unsigned num_fds = len / sizeof(int); + if (num_fds > 1 || num_fds == 0) { + // A nasty renderer could try and send us too many descriptors and + // force a leak. + LOG(ERROR) << "Death signal contained too many descriptors;" + << " num_fds:" << num_fds; + for (unsigned i = 0; i < num_fds; ++i) + HANDLE_EINTR(close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i])); + return; + } else { + signal_fd = reinterpret_cast<int*>(CMSG_DATA(hdr))[0]; + } + } else if (hdr->cmsg_type == SCM_CREDENTIALS) { + const struct ucred *cred = + reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + crashing_pid = cred->pid; + } + } + + if (crashing_pid == -1 || signal_fd == -1) { + LOG(ERROR) << "Death signal message didn't contain all expected control" + << " messages"; + if (signal_fd) + HANDLE_EINTR(close(signal_fd)); + return; + } + + const uint64 rand = base::RandUint64(); + const std::string minidump_filename = + StringPrintf("/tmp/chromium-renderer-minidump-%016" PRIx64 ".dmp", rand); + if (!google_breakpad::WriteMinidump(minidump_filename.c_str(), + crashing_pid, context, + kCrashContextSize)) { + LOG(ERROR) << "Failed to write crash dump for pid " << crashing_pid; + HANDLE_EINTR(close(signal_fd)); + } + + // Send the done signal to the renderer: it can exit now. + memset(&msg, 0, sizeof(msg)); + iov.iov_base = const_cast<char*>("\x42"); + iov.iov_len = 1; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + HANDLE_EINTR(sendmsg(signal_fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL)); + HANDLE_EINTR(close(signal_fd)); + + UploadCrashDump(minidump_filename.c_str(), crash_url, crash_url_len); +} + +void RenderCrashHandlerHostLinux::WillDestroyCurrentMessageLoop() { + file_descriptor_watcher_.StopWatchingFileDescriptor(); +} diff --git a/chrome/browser/renderer_host/render_crash_handler_host_linux.h b/chrome/browser/renderer_host/render_crash_handler_host_linux.h new file mode 100644 index 0000000..78a97ba --- /dev/null +++ b/chrome/browser/renderer_host/render_crash_handler_host_linux.h @@ -0,0 +1,47 @@ +// Copyright (c) 2009 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 CHROME_BROWSER_RENDERER_HOST_RENDER_CRASH_HANDLER_HOST_LINUX_H_ +#define CHROME_BROWSER_RENDERER_HOST_RENDER_CRASH_HANDLER_HOST_LINUX_H_ + +#include "base/singleton.h" +#include "base/message_loop.h" + +// This is a singleton object which crash dumps renderers on Linux. We perform +// the crash dump from the browser because it allows us to be outside the +// sandbox. +// +// Renderers signal that they need to be dumped by sending a datagram over a +// UNIX domain socket. All renderers share the client end of this socket which +// is installed in their descriptor table before exec. +class RenderCrashHandlerHostLinux : public MessageLoopForIO::Watcher, + public MessageLoop::DestructionObserver { + public: + // Get the file descriptor which renderers should be given in order to signal + // crashes to the browser. + int GetDeathSignalSocket() const { + return renderer_socket_; + } + + // MessagePumbLibevent::Watcher impl: + virtual void OnFileCanWriteWithoutBlocking(int fd); + virtual void OnFileCanReadWithoutBlocking(int fd); + + // MessageLoop::DestructionObserver impl: + virtual void WillDestroyCurrentMessageLoop(); + + private: + friend struct DefaultSingletonTraits<RenderCrashHandlerHostLinux>; + RenderCrashHandlerHostLinux(); + ~RenderCrashHandlerHostLinux(); + void Init(); + + int renderer_socket_; + int browser_socket_; + MessageLoopForIO::FileDescriptorWatcher file_descriptor_watcher_; + + DISALLOW_EVIL_CONSTRUCTORS(RenderCrashHandlerHostLinux); +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_RENDER_CRASH_HANDLER_HOST_LINUX_H_ diff --git a/chrome/browser/renderer_host/render_crash_handler_host_linux_stub.cc b/chrome/browser/renderer_host/render_crash_handler_host_linux_stub.cc new file mode 100644 index 0000000..2410bc1 --- /dev/null +++ b/chrome/browser/renderer_host/render_crash_handler_host_linux_stub.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2009 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. + +// This is a stub file which is compiled in when we are building without +// breakpad support. + +#include "chrome/browser/renderer_host/render_crash_handler_host_linux.h" + +RenderCrashHandlerHostLinux::RenderCrashHandlerHostLinux() + : renderer_socket_(-1), + browser_socket_(-1) { +} + +RenderCrashHandlerHostLinux::~RenderCrashHandlerHostLinux() { +} + +void RenderCrashHandlerHostLinux::OnFileCanReadWithoutBlocking(int fd) { +} + +void RenderCrashHandlerHostLinux::OnFileCanWriteWithoutBlocking(int fd) { +} + +void RenderCrashHandlerHostLinux::WillDestroyCurrentMessageLoop() { +} diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 1fe98aa..1ac6f35 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1541,8 +1541,8 @@ '../build/linux/system.gyp:gtk', ], 'sources!': [ - # TODO(port): add this to the Linux build once a - # window_sizer_linux.cc is written + # TODO(port): add this to the Linux build once a + # window_sizer_linux.cc is written 'browser/window_sizer.cc', 'browser/debugger/debugger_shell_stubs.cc', # Windows-specific files. @@ -1550,6 +1550,17 @@ 'browser/password_manager/password_store_win.cc', 'browser/password_manager/password_store_win.h', ], + 'conditions': [ + ['linux_breakpad==1', { + 'sources': [ + 'browser/renderer_host/render_crash_handler_host_linux.cc', + ], + }, { + 'sources': [ + 'browser/renderer_host/render_crash_handler_host_linux_stub.cc', + ], + }], + ], }], ['OS=="linux" and toolkit_views==0', { 'sources!': [ @@ -1885,6 +1896,18 @@ 'dependencies': [ '../build/linux/system.gyp:gtk', ], + 'conditions': [ + ['linux_breakpad==1', { + 'sources': [ + 'renderer/render_crash_handler_linux.cc', + 'renderer/render_crash_handler_linux.h', + ], + }, { + 'sources': [ + 'renderer/render_crash_handler_linux_stub.cc', + ], + }] + ], }], # Windows-specific rules. ['OS=="win"', { @@ -2026,11 +2049,32 @@ 'files': ['<(INTERMEDIATE_DIR)/repack/default.pak'], }, ], + 'include_dirs': [ + # Needed in order to be able to read the raw + # file_version_info_linux.h file. + '<(SHARED_INTERMEDIATE_DIR)', + ], + 'conditions': [ + ['linux_breakpad==1', { + 'sources': [ + 'app/breakpad_linux.cc', + 'app/breakpad_linux.h', + ], + 'dependencies': [ + '../breakpad/breakpad.gyp:breakpad_client', + ], + }, { + 'sources': [ + 'app/breakpad_linux_stub.cc', + 'app/breakpad_linux.h', + ], + }], + ] }], ['OS=="linux" and toolkit_views==1', { 'dependencies': [ '../views/views.gyp:views', - ], + ], }], ['OS=="mac"', { # 'branding' is a variable defined in common.gypi @@ -3155,6 +3199,17 @@ } }], ['OS=="linux"', { + 'conditions': [ + ['branding=="Chrome"', { + 'variables': { + 'linux_breakpad%': 1, + }, + }, { + 'variables': { + 'linux_breakpad%': 0, + }, + }], + ], 'targets': [ { 'target_name': 'convert_dict', diff --git a/chrome/renderer/render_crash_handler_linux.cc b/chrome/renderer/render_crash_handler_linux.cc new file mode 100644 index 0000000..0594f17 --- /dev/null +++ b/chrome/renderer/render_crash_handler_linux.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2006-2008 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 <string> + +#include <unistd.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include "base/eintr_wrapper.h" +#include "breakpad/linux/exception_handler.h" +#include "breakpad/linux/linux_libc_support.h" +#include "breakpad/linux/linux_syscall_support.h" + +// This is defined in chrome/renderer/renderer_logging_linux.cc, it's the +// static string containing the current active URL. We send this in the crash +// report. +namespace renderer_logging { +extern std::string active_url; +} + +static bool +CrashHandler(const void* crash_context, size_t crash_context_size, + void* context) { + const int fd = (int) context; + int fds[2]; + pipe(fds); + + // The length of the control message: + static const unsigned kControlMsgSize = + CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); + + union { + struct kernel_msghdr msg; + struct msghdr sys_msg; + }; + my_memset(&msg, 0, sizeof(struct kernel_msghdr)); + struct kernel_iovec iov[2]; + iov[0].iov_base = const_cast<void*>(crash_context); + iov[0].iov_len = crash_context_size; + iov[1].iov_base = const_cast<char*>(renderer_logging::active_url.data()); + iov[1].iov_len = renderer_logging::active_url.size(); + + msg.msg_iov = iov; + msg.msg_iovlen = 2; + char cmsg[kControlMsgSize]; + memset(cmsg, 0, kControlMsgSize); + 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 = CMSG_LEN(sizeof(int)); + *((int*) CMSG_DATA(hdr)) = fds[1]; + hdr = CMSG_NXTHDR(&sys_msg, hdr); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_CREDENTIALS; + hdr->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + struct ucred *cred = reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + cred->uid = getuid(); + cred->gid = getgid(); + cred->pid = getpid(); + + HANDLE_EINTR(sys_sendmsg(fd, &msg, 0)); + sys_close(fds[1]); + + char b; + HANDLE_EINTR(sys_read(fds[0], &b, 1)); + + return true; +} + +void EnableRendererCrashDumping() { + // When the browser forks off our process, it installs the crash signal file + // descriptor in this slot: + static const int kMagicCrashSignalFd = 4; + + // We deliberately leak this object. + google_breakpad::ExceptionHandler* handler = + new google_breakpad::ExceptionHandler("" /* unused */, NULL, NULL, + (void*) kMagicCrashSignalFd, true); + handler->set_crash_handler(CrashHandler); +} diff --git a/chrome/renderer/render_crash_handler_linux.h b/chrome/renderer/render_crash_handler_linux.h new file mode 100644 index 0000000..7a3fa85 --- /dev/null +++ b/chrome/renderer/render_crash_handler_linux.h @@ -0,0 +1,16 @@ +// Copyright (c) 2006-2008 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 CHROME_RENDERER_CRASH_HANDLER_LINUX_H_ +#define CHROME_RENDERER_CRASH_HANDLER_LINUX_H_ + +#include "build/build_config.h" + +#if defined(OS_LINUX) + +extern void EnableRendererCrashDumping(); + +#endif // OS_LINUX + +#endif // CHROME_RENDERER_CRASH_HANDLER_LINUX_H_ diff --git a/chrome/renderer/render_crash_handler_linux_stub.cc b/chrome/renderer/render_crash_handler_linux_stub.cc new file mode 100644 index 0000000..2da4635 --- /dev/null +++ b/chrome/renderer/render_crash_handler_linux_stub.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2009 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. + +// This is a stub file which is compiled in when we are building without +// breakpad support. + +void EnableRendererCrashDumping() { +} diff --git a/chrome/renderer/renderer_logging_linux.cc b/chrome/renderer/renderer_logging_linux.cc index 9322db1c..7812da1 100644 --- a/chrome/renderer/renderer_logging_linux.cc +++ b/chrome/renderer/renderer_logging_linux.cc @@ -4,15 +4,21 @@ #include "chrome/renderer/renderer_logging.h" +#include <string> + #include "base/logging.h" #include "googleurl/src/gurl.h" namespace renderer_logging { +// We use a static string to hold the most recent active url. If we crash, the +// crash handler code will send the contents of this string to the browser. +std::string active_url; + // Sets the URL that is logged if the renderer crashes. Use GURL() to clear // the URL. void SetActiveRendererURL(const GURL& url) { - // crbug.com/9646 + active_url = url.possibly_invalid_spec(); } } // namespace renderer_logging diff --git a/chrome/renderer/renderer_main.cc b/chrome/renderer/renderer_main.cc index fc4d291..97d633c 100644 --- a/chrome/renderer/renderer_main.cc +++ b/chrome/renderer/renderer_main.cc @@ -21,6 +21,9 @@ #include "chrome/common/logging_chrome.h" #include "chrome/common/main_function_params.h" #include "chrome/renderer/renderer_main_platform_delegate.h" +#if defined(OS_LINUX) +#include "chrome/renderer/render_crash_handler_linux.h" +#endif #include "chrome/renderer/render_process.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" @@ -70,6 +73,10 @@ int RendererMain(const MainFunctionParams& parameters) { // whatever occurs before it. HandleRendererErrorTestParameters(parsed_command_line); +#if defined(OS_LINUX) + EnableRendererCrashDumping(); +#endif + RendererMainPlatformDelegate platform(parameters); StatsScope<StatsCounterTimer> |