summaryrefslogtreecommitdiffstats
path: root/chrome/app
diff options
context:
space:
mode:
authoragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-22 03:37:45 +0000
committeragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-22 03:37:45 +0000
commit9a5d2a52a52bc167014d1e375208870278f451e0 (patch)
treef5b64315fc48c8b6e27089445d95c6f0ee189c50 /chrome/app
parent597725dc3391c9728461a2f93e1420bc8d386923 (diff)
downloadchromium_src-9a5d2a52a52bc167014d1e375208870278f451e0.zip
chromium_src-9a5d2a52a52bc167014d1e375208870278f451e0.tar.gz
chromium_src-9a5d2a52a52bc167014d1e375208870278f451e0.tar.bz2
Linux: add Breakpad support
This commits a rewrite of the Breakpad Linux client. The old code: * Had a number of plain bugs in it, but those could just have been fixed. * Allocated memory from the heap, which is a no go. * Made libc calls which can enter the dynamic linker - another source of crashes. * Didn't understand some of the tricks needed, like clone() via libc will write to random areas of memory because it assumes that it's only called from libpthread Additionally, we had one more requirement which meant changing the interface: * We need to be able to crash dump the renderers from the browser process. And that last one really needed a rewrite. We intend to try and upstream this new code into Breakpad. The new Breakpad design works like this: When a renderer crashes, a signal handler runs on an alternative stack and collects information about the registers of the thread before the crash. Then we enter Chromium specific code an send a datagram message to a magic file descriptor (4) containing: * the registers and tid of the crashing thread * the active URL * a file descriptor to a socket * a CREDENTIALS structure giving the PID of the renderer. On the other end of the socket is an object on the IO thread (render_crash_handler_host_linux.cc) which reads and parses the datagram. The CREDENTIALS structure is validated by the kernel, so the renderer can't lie about it's PID and try and get the browser to crash dump the wrong process. The browser then ptraces the renderer and extracts all the needed information to write a minidump to a temp file. Then we write a byte to the file descriptor which the renderer gave the browser in the datagram and that's the signal to the renderer to finish dying. It dies by sending itself the same signal which trigger the crash dump in the first place, so it will appear to crash as normal as far as kernel core dumps and waitpid are concerned. The browser then constucts a MIME message in a temp file for upload to the crash service. We then fork out to /usr/bin/wget to actually do the upload (since Debian numbers suggest that 99.8% of users have wget installed.) A second forked child unlinks the temp files once wget has completed. For a browser crash, everything works pretty much the same except that the datagram step is omitted and we clone() off a process to ptrace ourselves and write the minidump. This code is only enabled in Chrome branded builds. Stub source files are substituted in the case of a Chromium build. http://codereview.chromium.org/115526 BUG=9646,10772 TEST=Build a Chrome branded binary. Send SEGV to a renderer and verify that wget output appears on stderr. Send a SEGV to the main binary and verify the same. git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16719 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/app')
-rw-r--r--chrome/app/breakpad_linux.cc392
-rw-r--r--chrome/app/breakpad_linux.h12
-rw-r--r--chrome/app/breakpad_linux_stub.cc9
3 files changed, 413 insertions, 0 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() {
+}