Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(983)

Unified Diff: chrome/app/breakpad_linux.cc

Issue 115526: Linux: Add Breakpad client. (Closed)
Patch Set: ... Created 11 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/app/breakpad_linux.h ('k') | chrome/app/breakpad_linux_stub.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/app/breakpad_linux.cc
diff --git a/chrome/app/breakpad_linux.cc b/chrome/app/breakpad_linux.cc
new file mode 100644
index 0000000000000000000000000000000000000000..782723107e390b55939593ee3a279a636ed79cb6
--- /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 */);
+}
« no previous file with comments | « chrome/app/breakpad_linux.h ('k') | chrome/app/breakpad_linux_stub.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698