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 */); |
+} |