summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/breakpad-client/linux
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/breakpad-client/linux')
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/crash_generation/client_info.h68
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_client.cc105
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_client.h65
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc383
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h135
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/data/linux-gate-amd.sym3
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/data/linux-gate-intel.sym3
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/dump_writer_common/mapping_info.h75
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/dump_writer_common/raw_context_cpu.h53
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/dump_writer_common/thread_info.cc305
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/dump_writer_common/thread_info.h91
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/dump_writer_common/ucontext_reader.cc259
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/dump_writer_common/ucontext_reader.h65
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc876
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.h289
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/handler/exception_handler_unittest.cc1290
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.cc108
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.h48
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/handler/microdump_extra_info.h52
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.cc96
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.h199
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/log/log.cc84
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/log/log.h55
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc664
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.h68
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer_unittest.cc421
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set.h144
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set_unittest.cc164
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader.h106
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader_unittest.cc78
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader.h131
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader_unittest.cc169
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.cc308
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.h130
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper_unittest.cc192
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc999
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h333
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper_unittest_helper.cc95
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.cc403
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.h106
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc580
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc1562
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.h143
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest.cc934
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.cc66
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.h49
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader.h130
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc199
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/moz.build47
49 files changed, 12928 insertions, 0 deletions
diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/client_info.h b/toolkit/crashreporter/breakpad-client/linux/crash_generation/client_info.h
new file mode 100644
index 0000000000..3de2606b7b
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/client_info.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_
+#define CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_
+
+namespace google_breakpad {
+
+class CrashGenerationServer;
+
+class ClientInfo {
+ public:
+ ClientInfo(pid_t pid, CrashGenerationServer* crash_server)
+ : crash_server_(crash_server),
+ pid_(pid) {}
+
+ CrashGenerationServer* crash_server() const { return crash_server_; }
+ pid_t pid() const { return pid_; }
+ void set_error_msg(nsCString &error_msg) {
+ had_error_ = true;
+ error_msg_ = error_msg;
+ }
+
+ const nsCString* error_msg() const {
+ return &error_msg_;
+ }
+
+ bool had_error() const {
+ return had_error_;
+ }
+
+ private:
+ CrashGenerationServer* crash_server_;
+ pid_t pid_;
+ bool had_error_ = false;
+ nsCString error_msg_; // Possible error message of the minidumper in
+ // case there was an error during dumping
+};
+
+}
+
+#endif // CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_client.cc b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_client.cc
new file mode 100644
index 0000000000..02dc66f355
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_client.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "linux/crash_generation/crash_generation_client.h"
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <algorithm>
+
+#include "common/linux/eintr_wrapper.h"
+#include "common/linux/ignore_ret.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+namespace {
+
+class CrashGenerationClientImpl : public CrashGenerationClient {
+ public:
+ explicit CrashGenerationClientImpl(int server_fd) : server_fd_(server_fd) {}
+ virtual ~CrashGenerationClientImpl() {}
+
+ virtual bool RequestDump(const void* blob, size_t blob_size) {
+ int fds[2];
+ if (sys_pipe(fds) < 0)
+ return false;
+ static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int));
+
+ struct kernel_iovec iov;
+ iov.iov_base = const_cast<void*>(blob);
+ iov.iov_len = blob_size;
+
+ struct kernel_msghdr msg = { 0 };
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ char cmsg[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* p = reinterpret_cast<int*>(CMSG_DATA(hdr));
+ *p = fds[1];
+
+ ssize_t ret = HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0));
+ sys_close(fds[1]);
+ if (ret < 0) {
+ sys_close(fds[0]);
+ return false;
+ }
+
+ // Wait for an ACK from the server.
+ char b;
+ IGNORE_RET(HANDLE_EINTR(sys_read(fds[0], &b, 1)));
+ sys_close(fds[0]);
+
+ return true;
+ }
+
+ private:
+ int server_fd_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrashGenerationClientImpl);
+};
+
+} // namespace
+
+// static
+CrashGenerationClient* CrashGenerationClient::TryCreate(int server_fd) {
+ if (server_fd < 0)
+ return NULL;
+ return new CrashGenerationClientImpl(server_fd);
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_client.h b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_client.h
new file mode 100644
index 0000000000..4e68424ae8
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_client.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
+#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
+
+#include "common/basictypes.h"
+
+#include <stddef.h>
+
+namespace google_breakpad {
+
+// CrashGenerationClient is an interface for implementing out-of-process crash
+// dumping. The default implementation, accessed via the TryCreate() factory,
+// works in conjunction with the CrashGenerationServer to generate a minidump
+// via a remote process.
+class CrashGenerationClient {
+ public:
+ CrashGenerationClient() {}
+ virtual ~CrashGenerationClient() {}
+
+ // Request the crash server to generate a dump. |blob| is an opaque
+ // CrashContext pointer from exception_handler.h.
+ // Returns true if the dump was successful; false otherwise.
+ virtual bool RequestDump(const void* blob, size_t blob_size) = 0;
+
+ // Returns a new CrashGenerationClient if |server_fd| is valid and
+ // connects to a CrashGenerationServer. Otherwise, return NULL.
+ // The returned CrashGenerationClient* is owned by the caller of
+ // this function.
+ static CrashGenerationClient* TryCreate(int server_fd);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CrashGenerationClient);
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc
new file mode 100644
index 0000000000..58baa31d10
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc
@@ -0,0 +1,383 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <assert.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "nsThreadUtils.h"
+
+#include "linux/crash_generation/crash_generation_server.h"
+#include "linux/crash_generation/client_info.h"
+#include "linux/handler/exception_handler.h"
+#include "linux/handler/guid_generator.h"
+#include "linux/minidump_writer/minidump_writer.h"
+#include "common/linux/eintr_wrapper.h"
+#include "common/linux/safe_readlink.h"
+
+#if defined(MOZ_OXIDIZED_BREAKPAD)
+# include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h"
+# include <sys/signalfd.h>
+# include "nsString.h"
+#endif
+
+static const char kCommandQuit = 'x';
+
+namespace google_breakpad {
+
+CrashGenerationServer::CrashGenerationServer(
+ const int listen_fd,
+ OnClientDumpRequestCallback dump_callback,
+ void* dump_context,
+ OnClientExitingCallback exit_callback,
+ void* exit_context,
+ bool generate_dumps,
+ const string* dump_path) :
+ server_fd_(listen_fd),
+ dump_callback_(dump_callback),
+ dump_context_(dump_context),
+ exit_callback_(exit_callback),
+ exit_context_(exit_context),
+ generate_dumps_(generate_dumps),
+ started_(false)
+{
+ if (dump_path)
+ dump_dir_ = *dump_path;
+ else
+ dump_dir_ = "/tmp";
+}
+
+CrashGenerationServer::~CrashGenerationServer()
+{
+ if (started_)
+ Stop();
+}
+
+bool
+CrashGenerationServer::Start()
+{
+ if (started_ || 0 > server_fd_)
+ return false;
+
+ int control_pipe[2];
+ if (pipe(control_pipe))
+ return false;
+
+ if (fcntl(control_pipe[0], F_SETFD, FD_CLOEXEC))
+ return false;
+ if (fcntl(control_pipe[1], F_SETFD, FD_CLOEXEC))
+ return false;
+
+ if (fcntl(control_pipe[0], F_SETFL, O_NONBLOCK))
+ return false;
+
+ control_pipe_in_ = control_pipe[0];
+ control_pipe_out_ = control_pipe[1];
+
+ if (pthread_create(&thread_, NULL,
+ ThreadMain, reinterpret_cast<void*>(this)))
+ return false;
+
+ started_ = true;
+ return true;
+}
+
+void
+CrashGenerationServer::Stop()
+{
+ assert(pthread_self() != thread_);
+
+ if (!started_)
+ return;
+
+ HANDLE_EINTR(write(control_pipe_out_, &kCommandQuit, 1));
+
+ void* dummy;
+ pthread_join(thread_, &dummy);
+
+ close(control_pipe_in_);
+ close(control_pipe_out_);
+
+ started_ = false;
+}
+
+//static
+bool
+CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd)
+{
+ int fds[2];
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds))
+ return false;
+
+ static const int on = 1;
+ // Enable passcred on the server end of the socket
+ if (setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)))
+ return false;
+
+ if (fcntl(fds[1], F_SETFL, O_NONBLOCK))
+ return false;
+ if (fcntl(fds[1], F_SETFD, FD_CLOEXEC))
+ return false;
+
+ *client_fd = fds[0];
+ *server_fd = fds[1];
+ return true;
+}
+
+// The following methods/functions execute on the server thread
+
+void
+CrashGenerationServer::Run()
+{
+ struct pollfd pollfds[2];
+ memset(&pollfds, 0, sizeof(pollfds));
+
+ pollfds[0].fd = server_fd_;
+ pollfds[0].events = POLLIN;
+
+ pollfds[1].fd = control_pipe_in_;
+ pollfds[1].events = POLLIN;
+
+ while (true) {
+ // infinite timeout
+ int nevents = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), -1);
+ if (-1 == nevents) {
+ if (EINTR == errno) {
+ continue;
+ } else {
+ return;
+ }
+ }
+
+ if (pollfds[0].revents && !ClientEvent(pollfds[0].revents))
+ return;
+
+ if (pollfds[1].revents && !ControlEvent(pollfds[1].revents))
+ return;
+ }
+}
+
+bool
+CrashGenerationServer::ClientEvent(short revents)
+{
+ if (POLLHUP & revents)
+ return false;
+ assert(POLLIN & revents);
+
+ // A 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);
+
+ struct msghdr msg = {0};
+ struct iovec iov[1];
+ char crash_context[kCrashContextSize];
+ char control[kControlMsgSize];
+ const ssize_t expected_msg_size = sizeof(crash_context);
+
+ iov[0].iov_base = crash_context;
+ iov[0].iov_len = sizeof(crash_context);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]);
+ msg.msg_control = control;
+ msg.msg_controllen = kControlMsgSize;
+
+ const ssize_t msg_size = HANDLE_EINTR(recvmsg(server_fd_, &msg, 0));
+ if (msg_size != expected_msg_size)
+ return true;
+
+ if (msg.msg_controllen != kControlMsgSize ||
+ msg.msg_flags & ~MSG_TRUNC)
+ return true;
+
+ // Walk the control payload and 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);
+ assert(len % sizeof(int) == 0u);
+ const unsigned num_fds = len / sizeof(int);
+ if (num_fds > 1 || num_fds == 0) {
+ // A nasty process could try and send us too many descriptors and
+ // force a leak.
+ for (unsigned i = 0; i < num_fds; ++i)
+ close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i]);
+ return true;
+ } 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) {
+ if (signal_fd != -1)
+ close(signal_fd);
+ return true;
+ }
+
+ string minidump_filename;
+ if (!MakeMinidumpFilename(minidump_filename))
+ return true;
+
+#if defined(MOZ_OXIDIZED_BREAKPAD)
+ ExceptionHandler::CrashContext* breakpad_cc =
+ reinterpret_cast<ExceptionHandler::CrashContext*>(crash_context);
+ nsCString error_msg;
+ siginfo_t& si = breakpad_cc->siginfo;
+ signalfd_siginfo signalfd_si = {};
+ signalfd_si.ssi_signo = si.si_signo;
+ signalfd_si.ssi_errno = si.si_errno;
+ signalfd_si.ssi_code = si.si_code;
+
+ switch (si.si_signo) {
+ case SIGILL:
+ case SIGFPE:
+ case SIGSEGV:
+ case SIGBUS:
+ case SIGSYS:
+ signalfd_si.ssi_addr = reinterpret_cast<size_t>(si.si_addr);
+ break;
+ }
+
+ // Ignoring the return-value here for now.
+ // The function always creates an empty minidump file even in case of an
+ // error. So we'll report that as well via the callback-functions.
+ bool res = write_minidump_linux_with_context(
+ minidump_filename.c_str(), crashing_pid, &breakpad_cc->context,
+# ifndef __arm__
+ reinterpret_cast<const fpregset_t *>(&breakpad_cc->float_state),
+# else
+ nullptr,
+# endif // __arm__
+ &signalfd_si, breakpad_cc->tid, &error_msg);
+#else
+ if (!google_breakpad::WriteMinidump(minidump_filename.c_str(),
+ crashing_pid, crash_context,
+ kCrashContextSize)) {
+ close(signal_fd);
+ return true;
+ }
+#endif
+
+ ClientInfo info(crashing_pid, this);
+#if defined(MOZ_OXIDIZED_BREAKPAD)
+ if (!res) {
+ info.set_error_msg(error_msg);
+ }
+#endif
+ if (dump_callback_) {
+ dump_callback_(dump_context_, info, minidump_filename);
+ }
+
+ // Send the done signal to the process: it can exit now.
+ // (Closing this will make the child's sys_read unblock and return 0.)
+ close(signal_fd);
+
+ if (exit_callback_) {
+ exit_callback_(exit_context_, info);
+ }
+
+ return true;
+}
+
+bool
+CrashGenerationServer::ControlEvent(short revents)
+{
+ if (POLLHUP & revents)
+ return false;
+ assert(POLLIN & revents);
+
+ char command;
+ if (read(control_pipe_in_, &command, 1))
+ return false;
+
+ switch (command) {
+ case kCommandQuit:
+ return false;
+ default:
+ assert(0);
+ }
+
+ return true;
+}
+
+bool
+CrashGenerationServer::MakeMinidumpFilename(string& outFilename)
+{
+ GUID guid;
+ char guidString[kGUIDStringLength+1];
+
+ if (!(CreateGUID(&guid)
+ && GUIDToString(&guid, guidString, sizeof(guidString))))
+ return false;
+
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "%s/%s.dmp", dump_dir_.c_str(), guidString);
+
+ outFilename = path;
+ return true;
+}
+
+// static
+void*
+CrashGenerationServer::ThreadMain(void *arg)
+{
+ NS_SetCurrentThreadName("Breakpad Server");
+ reinterpret_cast<CrashGenerationServer*>(arg)->Run();
+ return NULL;
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h
new file mode 100644
index 0000000000..83b626ab1e
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
+#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
+
+#include <pthread.h>
+
+#include <string>
+
+#include "common/using_std_string.h"
+
+namespace google_breakpad {
+
+class ClientInfo;
+
+class CrashGenerationServer {
+public:
+ // WARNING: callbacks may be invoked on a different thread
+ // than that which creates the CrashGenerationServer. They must
+ // be thread safe.
+ typedef void (*OnClientDumpRequestCallback)(void* context,
+ const ClientInfo& client_info,
+ const string& file_path);
+
+ typedef void (*OnClientExitingCallback)(void* context,
+ const ClientInfo& client_info);
+
+ // Create an instance with the given parameters.
+ //
+ // Parameter listen_fd: The server fd created by CreateReportChannel().
+ // Parameter dump_callback: Callback for a client crash dump request.
+ // Parameter dump_context: Context for client crash dump request callback.
+ // Parameter exit_callback: Callback for client process exit.
+ // Parameter exit_context: Context for client exit callback.
+ // Parameter generate_dumps: Whether to automatically generate dumps.
+ // Client code of this class might want to generate dumps explicitly
+ // in the crash dump request callback. In that case, false can be
+ // passed for this parameter.
+ // Parameter dump_path: Path for generating dumps; required only if true is
+ // passed for generateDumps parameter; NULL can be passed otherwise.
+ CrashGenerationServer(const int listen_fd,
+ OnClientDumpRequestCallback dump_callback,
+ void* dump_context,
+ OnClientExitingCallback exit_callback,
+ void* exit_context,
+ bool generate_dumps,
+ const string* dump_path);
+
+ ~CrashGenerationServer();
+
+ // Perform initialization steps needed to start listening to clients.
+ //
+ // Return true if initialization is successful; false otherwise.
+ bool Start();
+
+ // Stop the server.
+ void Stop();
+
+ // Create a "channel" that can be used by clients to report crashes
+ // to a CrashGenerationServer. |*server_fd| should be passed to
+ // this class's constructor, and |*client_fd| should be passed to
+ // the ExceptionHandler constructor in the client process.
+ static bool CreateReportChannel(int* server_fd, int* client_fd);
+
+private:
+ // Run the server's event loop
+ void Run();
+
+ // Invoked when an child process (client) event occurs
+ // Returning true => "keep running", false => "exit loop"
+ bool ClientEvent(short revents);
+
+ // Invoked when the controlling thread (main) event occurs
+ // Returning true => "keep running", false => "exit loop"
+ bool ControlEvent(short revents);
+
+ // Return a unique filename at which a minidump can be written
+ bool MakeMinidumpFilename(string& outFilename);
+
+ // Trampoline to |Run()|
+ static void* ThreadMain(void* arg);
+
+ int server_fd_;
+
+ OnClientDumpRequestCallback dump_callback_;
+ void* dump_context_;
+
+ OnClientExitingCallback exit_callback_;
+ void* exit_context_;
+
+ bool generate_dumps_;
+
+ string dump_dir_;
+
+ bool started_;
+
+ pthread_t thread_;
+ int control_pipe_in_;
+ int control_pipe_out_;
+
+ // disable these
+ CrashGenerationServer(const CrashGenerationServer&);
+ CrashGenerationServer& operator=(const CrashGenerationServer&);
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/data/linux-gate-amd.sym b/toolkit/crashreporter/breakpad-client/linux/data/linux-gate-amd.sym
new file mode 100644
index 0000000000..e042a5ec42
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/data/linux-gate-amd.sym
@@ -0,0 +1,3 @@
+MODULE Linux x86 B8CFDE93002D54DA1900A40AA1BD67690 linux-gate.so
+PUBLIC 400 0 __kernel_vsyscall
+STACK WIN 4 400 100 1 1 0 0 0 0 0 1
diff --git a/toolkit/crashreporter/breakpad-client/linux/data/linux-gate-intel.sym b/toolkit/crashreporter/breakpad-client/linux/data/linux-gate-intel.sym
new file mode 100644
index 0000000000..c20facaf08
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/data/linux-gate-intel.sym
@@ -0,0 +1,3 @@
+MODULE Linux x86 4FBDA58B5A1DF5A379E3CF19A235EA090 linux-gate.so
+PUBLIC 400 0 __kernel_vsyscall
+STACK WIN 4 400 200 3 3 0 0 0 0 0 1
diff --git a/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/mapping_info.h b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/mapping_info.h
new file mode 100644
index 0000000000..c358539f58
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/mapping_info.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2014, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_
+#define CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_
+
+#include <limits.h>
+#include <list>
+#include <stdint.h>
+#include <vector>
+
+#include "google_breakpad/common/minidump_format.h"
+
+namespace google_breakpad {
+
+// One of these is produced for each mapping in the process (i.e. line in
+// /proc/$x/maps).
+struct MappingInfo {
+ // On Android, relocation packing can mean that the reported start
+ // address of the mapping must be adjusted by a bias in order to
+ // compensate for the compression of the relocation section. The
+ // following two members hold (after LateInit) the adjusted mapping
+ // range. See crbug.com/606972 for more information.
+ uintptr_t start_addr;
+ size_t size;
+ // When Android relocation packing causes |start_addr| and |size| to
+ // be modified with a load bias, we need to remember the unbiased
+ // address range. The following structure holds the original mapping
+ // address range as reported by the operating system.
+ struct {
+ uintptr_t start_addr;
+ uintptr_t end_addr;
+ } system_mapping_info;
+ size_t offset; // offset into the backed file.
+ bool exec; // true if the mapping has the execute bit set.
+ char name[NAME_MAX];
+};
+
+struct MappingEntry {
+ MappingInfo first;
+ std::vector<uint8_t> second;
+};
+
+// A list of <MappingInfo, GUID>
+typedef std::list<MappingEntry> MappingList;
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/raw_context_cpu.h b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/raw_context_cpu.h
new file mode 100644
index 0000000000..07d9171a0a
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/raw_context_cpu.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2014, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H
+#define CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H
+
+#include "google_breakpad/common/minidump_format.h"
+
+namespace google_breakpad {
+
+#if defined(__i386__)
+typedef MDRawContextX86 RawContextCPU;
+#elif defined(__x86_64)
+typedef MDRawContextAMD64 RawContextCPU;
+#elif defined(__ARM_EABI__)
+typedef MDRawContextARM RawContextCPU;
+#elif defined(__aarch64__)
+typedef MDRawContextARM64_Old RawContextCPU;
+#elif defined(__mips__)
+typedef MDRawContextMIPS RawContextCPU;
+#else
+#error "This code has not been ported to your platform yet."
+#endif
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H
diff --git a/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/thread_info.cc b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/thread_info.cc
new file mode 100644
index 0000000000..5d9708c70c
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/thread_info.cc
@@ -0,0 +1,305 @@
+// Copyright (c) 2014, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "linux/dump_writer_common/thread_info.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "common/linux/linux_libc_support.h"
+#include "google_breakpad/common/minidump_format.h"
+
+namespace {
+
+#if defined(__i386__)
+// Write a uint16_t to memory
+// out: memory location to write to
+// v: value to write.
+void U16(void* out, uint16_t v) {
+ my_memcpy(out, &v, sizeof(v));
+}
+
+// Write a uint32_t to memory
+// out: memory location to write to
+// v: value to write.
+void U32(void* out, uint32_t v) {
+ my_memcpy(out, &v, sizeof(v));
+}
+#endif
+
+}
+
+namespace google_breakpad {
+
+#if defined(__i386__)
+
+uintptr_t ThreadInfo::GetInstructionPointer() const {
+ return regs.eip;
+}
+
+void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
+ out->context_flags = MD_CONTEXT_X86_ALL;
+
+ out->dr0 = dregs[0];
+ out->dr1 = dregs[1];
+ out->dr2 = dregs[2];
+ out->dr3 = dregs[3];
+ // 4 and 5 deliberatly omitted because they aren't included in the minidump
+ // format.
+ out->dr6 = dregs[6];
+ out->dr7 = dregs[7];
+
+ out->gs = regs.xgs;
+ out->fs = regs.xfs;
+ out->es = regs.xes;
+ out->ds = regs.xds;
+
+ out->edi = regs.edi;
+ out->esi = regs.esi;
+ out->ebx = regs.ebx;
+ out->edx = regs.edx;
+ out->ecx = regs.ecx;
+ out->eax = regs.eax;
+
+ out->ebp = regs.ebp;
+ out->eip = regs.eip;
+ out->cs = regs.xcs;
+ out->eflags = regs.eflags;
+ out->esp = regs.esp;
+ out->ss = regs.xss;
+
+ out->float_save.control_word = fpregs.cwd;
+ out->float_save.status_word = fpregs.swd;
+ out->float_save.tag_word = fpregs.twd;
+ out->float_save.error_offset = fpregs.fip;
+ out->float_save.error_selector = fpregs.fcs;
+ out->float_save.data_offset = fpregs.foo;
+ out->float_save.data_selector = fpregs.fos;
+
+ // 8 registers * 10 bytes per register.
+ my_memcpy(out->float_save.register_area, fpregs.st_space, 10 * 8);
+
+ // This matches the Intel fpsave format.
+ U16(out->extended_registers + 0, fpregs.cwd);
+ U16(out->extended_registers + 2, fpregs.swd);
+ U16(out->extended_registers + 4, fpregs.twd);
+ U16(out->extended_registers + 6, fpxregs.fop);
+ U32(out->extended_registers + 8, fpxregs.fip);
+ U16(out->extended_registers + 12, fpxregs.fcs);
+ U32(out->extended_registers + 16, fpregs.foo);
+ U16(out->extended_registers + 20, fpregs.fos);
+ U32(out->extended_registers + 24, fpxregs.mxcsr);
+
+ my_memcpy(out->extended_registers + 32, &fpxregs.st_space, 128);
+ my_memcpy(out->extended_registers + 160, &fpxregs.xmm_space, 128);
+}
+
+#elif defined(__x86_64)
+
+uintptr_t ThreadInfo::GetInstructionPointer() const {
+ return regs.rip;
+}
+
+void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
+ out->context_flags = MD_CONTEXT_AMD64_FULL |
+ MD_CONTEXT_AMD64_SEGMENTS;
+
+ out->cs = regs.cs;
+
+ out->ds = regs.ds;
+ out->es = regs.es;
+ out->fs = regs.fs;
+ out->gs = regs.gs;
+
+ out->ss = regs.ss;
+ out->eflags = regs.eflags;
+
+ out->dr0 = dregs[0];
+ out->dr1 = dregs[1];
+ out->dr2 = dregs[2];
+ out->dr3 = dregs[3];
+ // 4 and 5 deliberatly omitted because they aren't included in the minidump
+ // format.
+ out->dr6 = dregs[6];
+ out->dr7 = dregs[7];
+
+ out->rax = regs.rax;
+ out->rcx = regs.rcx;
+ out->rdx = regs.rdx;
+ out->rbx = regs.rbx;
+
+ out->rsp = regs.rsp;
+
+ out->rbp = regs.rbp;
+ out->rsi = regs.rsi;
+ out->rdi = regs.rdi;
+ out->r8 = regs.r8;
+ out->r9 = regs.r9;
+ out->r10 = regs.r10;
+ out->r11 = regs.r11;
+ out->r12 = regs.r12;
+ out->r13 = regs.r13;
+ out->r14 = regs.r14;
+ out->r15 = regs.r15;
+
+ out->rip = regs.rip;
+
+ out->flt_save.control_word = fpregs.cwd;
+ out->flt_save.status_word = fpregs.swd;
+ out->flt_save.tag_word = fpregs.ftw;
+ out->flt_save.error_opcode = fpregs.fop;
+ out->flt_save.error_offset = fpregs.rip;
+ out->flt_save.error_selector = 0; // We don't have this.
+ out->flt_save.data_offset = fpregs.rdp;
+ out->flt_save.data_selector = 0; // We don't have this.
+ out->flt_save.mx_csr = fpregs.mxcsr;
+ out->flt_save.mx_csr_mask = fpregs.mxcr_mask;
+
+ my_memcpy(&out->flt_save.float_registers, &fpregs.st_space, 8 * 16);
+ my_memcpy(&out->flt_save.xmm_registers, &fpregs.xmm_space, 16 * 16);
+}
+
+#elif defined(__ARM_EABI__)
+
+uintptr_t ThreadInfo::GetInstructionPointer() const {
+ return regs.uregs[15];
+}
+
+void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
+ out->context_flags = MD_CONTEXT_ARM_FULL;
+
+ for (int i = 0; i < MD_CONTEXT_ARM_GPR_COUNT; ++i)
+ out->iregs[i] = regs.uregs[i];
+ // No CPSR register in ThreadInfo(it's not accessible via ptrace)
+ out->cpsr = 0;
+#if !defined(__ANDROID__)
+ out->float_save.fpscr = fpregs.fpsr |
+ (static_cast<uint64_t>(fpregs.fpcr) << 32);
+ // TODO: sort this out, actually collect floating point registers
+ my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs));
+ my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra));
+#endif
+}
+
+#elif defined(__aarch64__)
+
+uintptr_t ThreadInfo::GetInstructionPointer() const {
+ return regs.pc;
+}
+
+void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
+ out->context_flags = MD_CONTEXT_ARM64_FULL_OLD;
+
+ out->cpsr = static_cast<uint32_t>(regs.pstate);
+ for (int i = 0; i < MD_CONTEXT_ARM64_REG_SP; ++i)
+ out->iregs[i] = regs.regs[i];
+ out->iregs[MD_CONTEXT_ARM64_REG_SP] = regs.sp;
+ out->iregs[MD_CONTEXT_ARM64_REG_PC] = regs.pc;
+
+ out->float_save.fpsr = fpregs.fpsr;
+ out->float_save.fpcr = fpregs.fpcr;
+ my_memcpy(&out->float_save.regs, &fpregs.vregs,
+ MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16);
+}
+
+#elif defined(__mips__)
+
+uintptr_t ThreadInfo::GetInstructionPointer() const {
+ return mcontext.pc;
+}
+
+void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
+#if _MIPS_SIM == _ABI64
+ out->context_flags = MD_CONTEXT_MIPS64_FULL;
+#elif _MIPS_SIM == _ABIO32
+ out->context_flags = MD_CONTEXT_MIPS_FULL;
+#else
+# error "This mips ABI is currently not supported (n32)"
+#endif
+
+ for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i)
+ out->iregs[i] = mcontext.gregs[i];
+
+ out->mdhi = mcontext.mdhi;
+ out->mdlo = mcontext.mdlo;
+ out->dsp_control = mcontext.dsp;
+
+ out->hi[0] = mcontext.hi1;
+ out->lo[0] = mcontext.lo1;
+ out->hi[1] = mcontext.hi2;
+ out->lo[1] = mcontext.lo2;
+ out->hi[2] = mcontext.hi3;
+ out->lo[2] = mcontext.lo3;
+
+ out->epc = mcontext.pc;
+ out->badvaddr = 0; // Not stored in mcontext
+ out->status = 0; // Not stored in mcontext
+ out->cause = 0; // Not stored in mcontext
+
+ for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i)
+ out->float_save.regs[i] = mcontext.fpregs.fp_r.fp_fregs[i]._fp_fregs;
+
+ out->float_save.fpcsr = mcontext.fpc_csr;
+#if _MIPS_SIM == _ABIO32
+ out->float_save.fir = mcontext.fpc_eir;
+#endif
+}
+#endif // __mips__
+
+void ThreadInfo::GetGeneralPurposeRegisters(void** gp_regs, size_t* size) {
+ assert(gp_regs || size);
+#if defined(__mips__)
+ if (gp_regs)
+ *gp_regs = mcontext.gregs;
+ if (size)
+ *size = sizeof(mcontext.gregs);
+#else
+ if (gp_regs)
+ *gp_regs = &regs;
+ if (size)
+ *size = sizeof(regs);
+#endif
+}
+
+void ThreadInfo::GetFloatingPointRegisters(void** fp_regs, size_t* size) {
+ assert(fp_regs || size);
+#if defined(__mips__)
+ if (fp_regs)
+ *fp_regs = &mcontext.fpregs;
+ if (size)
+ *size = sizeof(mcontext.fpregs);
+#else
+ if (fp_regs)
+ *fp_regs = &fpregs;
+ if (size)
+ *size = sizeof(fpregs);
+#endif
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/thread_info.h b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/thread_info.h
new file mode 100644
index 0000000000..4173d239c4
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/thread_info.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2014, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_
+#define CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_
+
+#include <sys/ucontext.h>
+#include <sys/user.h>
+
+#include "linux/dump_writer_common/raw_context_cpu.h"
+#include "common/memory_allocator.h"
+#include "google_breakpad/common/minidump_format.h"
+
+namespace google_breakpad {
+
+#if defined(__i386) || defined(__x86_64)
+typedef __typeof__(((struct user*) 0)->u_debugreg[0]) debugreg_t;
+#endif
+
+// We produce one of these structures for each thread in the crashed process.
+struct ThreadInfo {
+ pid_t tgid; // thread group id
+ pid_t ppid; // parent process
+
+ uintptr_t stack_pointer; // thread stack pointer
+
+
+#if defined(__i386) || defined(__x86_64)
+ user_regs_struct regs;
+ user_fpregs_struct fpregs;
+ static const unsigned kNumDebugRegisters = 8;
+ debugreg_t dregs[8];
+#if defined(__i386)
+ user_fpxregs_struct fpxregs;
+#endif // defined(__i386)
+
+#elif defined(__ARM_EABI__)
+ // Mimicking how strace does this(see syscall.c, search for GETREGS)
+ struct user_regs regs;
+ struct user_fpregs fpregs;
+#elif defined(__aarch64__)
+ // Use the structures defined in <sys/user.h>
+ struct user_regs_struct regs;
+ struct user_fpsimd_struct fpregs;
+#elif defined(__mips__)
+ // Use the structure defined in <sys/ucontext.h>.
+ mcontext_t mcontext;
+#endif
+
+ // Returns the instruction pointer (platform-dependent impl.).
+ uintptr_t GetInstructionPointer() const;
+
+ // Fills a RawContextCPU using the context in the ThreadInfo object.
+ void FillCPUContext(RawContextCPU* out) const;
+
+ // Returns the pointer and size of general purpose register area.
+ void GetGeneralPurposeRegisters(void** gp_regs, size_t* size);
+
+ // Returns the pointer and size of float point register area.
+ void GetFloatingPointRegisters(void** fp_regs, size_t* size);
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/ucontext_reader.cc b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/ucontext_reader.cc
new file mode 100644
index 0000000000..5145ede3d8
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/ucontext_reader.cc
@@ -0,0 +1,259 @@
+// Copyright (c) 2014, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "linux/dump_writer_common/ucontext_reader.h"
+
+#include "common/linux/linux_libc_support.h"
+#include "google_breakpad/common/minidump_format.h"
+
+namespace google_breakpad {
+
+// Minidump defines register structures which are different from the raw
+// structures which we get from the kernel. These are platform specific
+// functions to juggle the ucontext_t and user structures into minidump format.
+
+#if defined(__i386__)
+
+uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.gregs[REG_ESP];
+}
+
+uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.gregs[REG_EIP];
+}
+
+void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc,
+ const fpstate_t* fp) {
+ const greg_t* regs = uc->uc_mcontext.gregs;
+
+ out->context_flags = MD_CONTEXT_X86_FULL |
+ MD_CONTEXT_X86_FLOATING_POINT;
+
+ out->gs = regs[REG_GS];
+ out->fs = regs[REG_FS];
+ out->es = regs[REG_ES];
+ out->ds = regs[REG_DS];
+
+ out->edi = regs[REG_EDI];
+ out->esi = regs[REG_ESI];
+ out->ebx = regs[REG_EBX];
+ out->edx = regs[REG_EDX];
+ out->ecx = regs[REG_ECX];
+ out->eax = regs[REG_EAX];
+
+ out->ebp = regs[REG_EBP];
+ out->eip = regs[REG_EIP];
+ out->cs = regs[REG_CS];
+ out->eflags = regs[REG_EFL];
+ out->esp = regs[REG_UESP];
+ out->ss = regs[REG_SS];
+
+ out->float_save.control_word = fp->cw;
+ out->float_save.status_word = fp->sw;
+ out->float_save.tag_word = fp->tag;
+ out->float_save.error_offset = fp->ipoff;
+ out->float_save.error_selector = fp->cssel;
+ out->float_save.data_offset = fp->dataoff;
+ out->float_save.data_selector = fp->datasel;
+
+ // 8 registers * 10 bytes per register.
+ my_memcpy(out->float_save.register_area, fp->_st, 10 * 8);
+}
+
+#elif defined(__x86_64)
+
+uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.gregs[REG_RSP];
+}
+
+uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.gregs[REG_RIP];
+}
+
+void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc,
+ const fpstate_t* fpregs) {
+ const greg_t* regs = uc->uc_mcontext.gregs;
+
+ out->context_flags = MD_CONTEXT_AMD64_FULL;
+
+ out->cs = regs[REG_CSGSFS] & 0xffff;
+
+ out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff;
+ out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff;
+
+ out->eflags = regs[REG_EFL];
+
+ out->rax = regs[REG_RAX];
+ out->rcx = regs[REG_RCX];
+ out->rdx = regs[REG_RDX];
+ out->rbx = regs[REG_RBX];
+
+ out->rsp = regs[REG_RSP];
+ out->rbp = regs[REG_RBP];
+ out->rsi = regs[REG_RSI];
+ out->rdi = regs[REG_RDI];
+ out->r8 = regs[REG_R8];
+ out->r9 = regs[REG_R9];
+ out->r10 = regs[REG_R10];
+ out->r11 = regs[REG_R11];
+ out->r12 = regs[REG_R12];
+ out->r13 = regs[REG_R13];
+ out->r14 = regs[REG_R14];
+ out->r15 = regs[REG_R15];
+
+ out->rip = regs[REG_RIP];
+
+ out->flt_save.control_word = fpregs->cwd;
+ out->flt_save.status_word = fpregs->swd;
+ out->flt_save.tag_word = fpregs->ftw;
+ out->flt_save.error_opcode = fpregs->fop;
+ out->flt_save.error_offset = fpregs->rip;
+ out->flt_save.data_offset = fpregs->rdp;
+ out->flt_save.error_selector = 0; // We don't have this.
+ out->flt_save.data_selector = 0; // We don't have this.
+ out->flt_save.mx_csr = fpregs->mxcsr;
+ out->flt_save.mx_csr_mask = fpregs->mxcr_mask;
+ my_memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16);
+ my_memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16);
+}
+
+#elif defined(__ARM_EABI__)
+
+uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.arm_sp;
+}
+
+uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.arm_pc;
+}
+
+void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc) {
+ out->context_flags = MD_CONTEXT_ARM_FULL;
+
+ out->iregs[0] = uc->uc_mcontext.arm_r0;
+ out->iregs[1] = uc->uc_mcontext.arm_r1;
+ out->iregs[2] = uc->uc_mcontext.arm_r2;
+ out->iregs[3] = uc->uc_mcontext.arm_r3;
+ out->iregs[4] = uc->uc_mcontext.arm_r4;
+ out->iregs[5] = uc->uc_mcontext.arm_r5;
+ out->iregs[6] = uc->uc_mcontext.arm_r6;
+ out->iregs[7] = uc->uc_mcontext.arm_r7;
+ out->iregs[8] = uc->uc_mcontext.arm_r8;
+ out->iregs[9] = uc->uc_mcontext.arm_r9;
+ out->iregs[10] = uc->uc_mcontext.arm_r10;
+
+ out->iregs[11] = uc->uc_mcontext.arm_fp;
+ out->iregs[12] = uc->uc_mcontext.arm_ip;
+ out->iregs[13] = uc->uc_mcontext.arm_sp;
+ out->iregs[14] = uc->uc_mcontext.arm_lr;
+ out->iregs[15] = uc->uc_mcontext.arm_pc;
+
+ out->cpsr = uc->uc_mcontext.arm_cpsr;
+
+ // TODO: fix this after fixing ExceptionHandler
+ out->float_save.fpscr = 0;
+ my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs));
+ my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra));
+}
+
+#elif defined(__aarch64__)
+
+uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.sp;
+}
+
+uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.pc;
+}
+
+void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc,
+ const struct fpsimd_context* fpregs) {
+ out->context_flags = MD_CONTEXT_ARM64_FULL_OLD;
+
+ out->cpsr = static_cast<uint32_t>(uc->uc_mcontext.pstate);
+ for (int i = 0; i < MD_CONTEXT_ARM64_REG_SP; ++i)
+ out->iregs[i] = uc->uc_mcontext.regs[i];
+ out->iregs[MD_CONTEXT_ARM64_REG_SP] = uc->uc_mcontext.sp;
+ out->iregs[MD_CONTEXT_ARM64_REG_PC] = uc->uc_mcontext.pc;
+
+ out->float_save.fpsr = fpregs->fpsr;
+ out->float_save.fpcr = fpregs->fpcr;
+ my_memcpy(&out->float_save.regs, &fpregs->vregs,
+ MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16);
+}
+
+#elif defined(__mips__)
+
+uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP];
+}
+
+uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
+ return uc->uc_mcontext.pc;
+}
+
+void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc) {
+#if _MIPS_SIM == _ABI64
+ out->context_flags = MD_CONTEXT_MIPS64_FULL;
+#elif _MIPS_SIM == _ABIO32
+ out->context_flags = MD_CONTEXT_MIPS_FULL;
+#else
+#error "This mips ABI is currently not supported (n32)"
+#endif
+
+ for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i)
+ out->iregs[i] = uc->uc_mcontext.gregs[i];
+
+ out->mdhi = uc->uc_mcontext.mdhi;
+ out->mdlo = uc->uc_mcontext.mdlo;
+
+ out->hi[0] = uc->uc_mcontext.hi1;
+ out->hi[1] = uc->uc_mcontext.hi2;
+ out->hi[2] = uc->uc_mcontext.hi3;
+ out->lo[0] = uc->uc_mcontext.lo1;
+ out->lo[1] = uc->uc_mcontext.lo2;
+ out->lo[2] = uc->uc_mcontext.lo3;
+ out->dsp_control = uc->uc_mcontext.dsp;
+
+ out->epc = uc->uc_mcontext.pc;
+ out->badvaddr = 0; // Not reported in signal context.
+ out->status = 0; // Not reported in signal context.
+ out->cause = 0; // Not reported in signal context.
+
+ for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i)
+ out->float_save.regs[i] = uc->uc_mcontext.fpregs.fp_r.fp_dregs[i];
+
+ out->float_save.fpcsr = uc->uc_mcontext.fpc_csr;
+#if _MIPS_SIM == _ABIO32
+ out->float_save.fir = uc->uc_mcontext.fpc_eir; // Unused.
+#endif
+}
+#endif
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/ucontext_reader.h b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/ucontext_reader.h
new file mode 100644
index 0000000000..390520be41
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/dump_writer_common/ucontext_reader.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2014, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H
+#define CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H
+
+#include <sys/ucontext.h>
+#include <sys/user.h>
+
+#include "linux/dump_writer_common/raw_context_cpu.h"
+#include "linux/minidump_writer/minidump_writer.h"
+#include "common/memory_allocator.h"
+#include "google_breakpad/common/minidump_format.h"
+
+namespace google_breakpad {
+
+// Wraps platform-dependent implementations of accessors to ucontext_t structs.
+struct UContextReader {
+ static uintptr_t GetStackPointer(const ucontext_t* uc);
+
+ static uintptr_t GetInstructionPointer(const ucontext_t* uc);
+
+ // Juggle a arch-specific ucontext into a minidump format
+ // out: the minidump structure
+ // info: the collection of register structures.
+#if defined(__i386__) || defined(__x86_64)
+ static void FillCPUContext(RawContextCPU *out, const ucontext_t *uc,
+ const fpstate_t* fp);
+#elif defined(__aarch64__)
+ static void FillCPUContext(RawContextCPU *out, const ucontext_t *uc,
+ const struct fpsimd_context* fpregs);
+#else
+ static void FillCPUContext(RawContextCPU *out, const ucontext_t *uc);
+#endif
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc
new file mode 100644
index 0000000000..92a0443d79
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc
@@ -0,0 +1,876 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// The ExceptionHandler object installs signal handlers for a number of
+// signals. We rely on the signal handler running on the thread which crashed
+// in order to identify it. This is true of the synchronous signals (SEGV etc),
+// but not true of ABRT. Thus, if you send ABRT to yourself in a program which
+// uses ExceptionHandler, you need to use tgkill to direct it to the current
+// thread.
+//
+// The signal flow looks like this:
+//
+// SignalHandler (uses a global stack of ExceptionHandler objects to find
+// | one to handle the signal. If the first rejects it, try
+// | the second etc...)
+// V
+// HandleSignal ----------------------------| (clones a new process which
+// | | shares an address space with
+// (wait for cloned | the crashed process. This
+// process) | allows us to ptrace the crashed
+// | | process)
+// V V
+// (set signal handler to ThreadEntry (static function to bounce
+// SIG_DFL and rethrow, | back into the object)
+// killing the crashed |
+// process) V
+// DoDump (writes minidump)
+// |
+// V
+// sys_exit
+//
+
+// This code is a little fragmented. Different functions of the ExceptionHandler
+// class run in a number of different contexts. Some of them run in a normal
+// context and are easy to code, others run in a compromised context and the
+// restrictions at the top of minidump_writer.cc apply: no libc and use the
+// alternative malloc. Each function should have comment above it detailing the
+// context which it runs in.
+
+#include "linux/handler/exception_handler.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <pthread.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <sys/ucontext.h>
+#include <sys/user.h>
+#include <ucontext.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "common/basictypes.h"
+#include "common/linux/breakpad_getcontext.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/memory_allocator.h"
+#include "linux/log/log.h"
+#include "linux/microdump_writer/microdump_writer.h"
+#include "linux/minidump_writer/linux_dumper.h"
+#include "linux/minidump_writer/minidump_writer.h"
+#include "common/linux/eintr_wrapper.h"
+#include "third_party/lss/linux_syscall_support.h"
+#if defined(MOZ_OXIDIZED_BREAKPAD)
+#include "nsString.h"
+#include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h"
+#endif
+
+#ifdef MOZ_PHC
+#include "PHC.h"
+#endif
+
+#if defined(__ANDROID__)
+#include "linux/sched.h"
+#endif
+
+#ifndef PR_SET_PTRACER
+#define PR_SET_PTRACER 0x59616d61
+#endif
+
+#define SKIP_SIGILL(sig) if (g_skip_sigill_ && (sig == SIGILL)) continue;
+
+namespace google_breakpad {
+
+namespace {
+// The list of signals which we consider to be crashes. The default action for
+// all these signals must be Core (see man 7 signal) because we rethrow the
+// signal after handling it and expect that it'll be fatal.
+const int kExceptionSignals[] = {
+ SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP
+};
+const int kNumHandledSignals =
+ sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]);
+struct sigaction old_handlers[kNumHandledSignals];
+bool handlers_installed = false;
+
+// InstallAlternateStackLocked will store the newly installed stack in new_stack
+// and (if it exists) the previously installed stack in old_stack.
+stack_t old_stack;
+stack_t new_stack;
+bool stack_installed = false;
+
+// Create an alternative stack to run the signal handlers on. This is done since
+// the signal might have been caused by a stack overflow.
+// Runs before crashing: normal context.
+void InstallAlternateStackLocked() {
+ if (stack_installed)
+ return;
+
+ memset(&old_stack, 0, sizeof(old_stack));
+ memset(&new_stack, 0, sizeof(new_stack));
+
+ // SIGSTKSZ may be too small to prevent the signal handlers from overrunning
+ // the alternative stack. Ensure that the size of the alternative stack is
+ // large enough.
+ static const size_t kSigStackSize = std::max(size_t(16384), size_t(SIGSTKSZ));
+
+ // Only set an alternative stack if there isn't already one, or if the current
+ // one is too small.
+ if (sys_sigaltstack(NULL, &old_stack) == -1 || !old_stack.ss_sp ||
+ old_stack.ss_size < kSigStackSize) {
+ new_stack.ss_sp = calloc(1, kSigStackSize);
+ new_stack.ss_size = kSigStackSize;
+
+ if (sys_sigaltstack(&new_stack, NULL) == -1) {
+ free(new_stack.ss_sp);
+ return;
+ }
+ stack_installed = true;
+ }
+}
+
+// Runs before crashing: normal context.
+void RestoreAlternateStackLocked() {
+ if (!stack_installed)
+ return;
+
+ stack_t current_stack;
+ if (sys_sigaltstack(NULL, &current_stack) == -1)
+ return;
+
+ // Only restore the old_stack if the current alternative stack is the one
+ // installed by the call to InstallAlternateStackLocked.
+ if (current_stack.ss_sp == new_stack.ss_sp) {
+ if (old_stack.ss_sp) {
+ if (sys_sigaltstack(&old_stack, NULL) == -1)
+ return;
+ } else {
+ stack_t disable_stack;
+ disable_stack.ss_flags = SS_DISABLE;
+ if (sys_sigaltstack(&disable_stack, NULL) == -1)
+ return;
+ }
+ }
+
+ free(new_stack.ss_sp);
+ stack_installed = false;
+}
+
+void InstallDefaultHandler(int sig) {
+#if defined(__ANDROID__)
+ // Android L+ expose signal and sigaction symbols that override the system
+ // ones. There is a bug in these functions where a request to set the handler
+ // to SIG_DFL is ignored. In that case, an infinite loop is entered as the
+ // signal is repeatedly sent to breakpad's signal handler.
+ // To work around this, directly call the system's sigaction.
+ struct kernel_sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sys_sigemptyset(&sa.sa_mask);
+ sa.sa_handler_ = SIG_DFL;
+ sa.sa_flags = SA_RESTART;
+ sys_rt_sigaction(sig, &sa, NULL, sizeof(kernel_sigset_t));
+#else
+ signal(sig, SIG_DFL);
+#endif
+}
+
+// The global exception handler stack. This is needed because there may exist
+// multiple ExceptionHandler instances in a process. Each will have itself
+// registered in this stack.
+std::vector<ExceptionHandler*>* g_handler_stack_ = NULL;
+pthread_mutex_t g_handler_stack_mutex_ = PTHREAD_MUTEX_INITIALIZER;
+
+// sizeof(CrashContext) can be too big w.r.t the size of alternatate stack
+// for SignalHandler(). Keep the crash context as a .bss field. Exception
+// handlers are serialized by the |g_handler_stack_mutex_| and at most one at a
+// time can use |g_crash_context_|.
+ExceptionHandler::CrashContext g_crash_context_;
+
+FirstChanceHandler g_first_chance_handler_ = nullptr;
+bool g_skip_sigill_ = false;
+} // namespace
+
+// Runs before crashing: normal context.
+ExceptionHandler::ExceptionHandler(const MinidumpDescriptor& descriptor,
+ FilterCallback filter,
+ MinidumpCallback callback,
+ void* callback_context,
+ bool install_handler,
+ const int server_fd)
+ : filter_(filter),
+ callback_(callback),
+ callback_context_(callback_context),
+ minidump_descriptor_(descriptor),
+ crash_handler_(NULL) {
+
+ g_skip_sigill_ = getenv("MOZ_DISABLE_EXCEPTION_HANDLER_SIGILL") ? true : false;
+ if (server_fd >= 0)
+ crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd));
+
+ if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() &&
+ !minidump_descriptor_.IsMicrodumpOnConsole())
+ minidump_descriptor_.UpdatePath();
+
+#if defined(__ANDROID__)
+ if (minidump_descriptor_.IsMicrodumpOnConsole())
+ logger::initializeCrashLogWriter();
+#endif
+
+ pthread_mutex_lock(&g_handler_stack_mutex_);
+
+ // Pre-fault the crash context struct. This is to avoid failing due to OOM
+ // if handling an exception when the process ran out of virtual memory.
+ memset(&g_crash_context_, 0, sizeof(g_crash_context_));
+
+ if (!g_handler_stack_)
+ g_handler_stack_ = new std::vector<ExceptionHandler*>;
+ if (install_handler) {
+ InstallAlternateStackLocked();
+ InstallHandlersLocked();
+ }
+ g_handler_stack_->push_back(this);
+ pthread_mutex_unlock(&g_handler_stack_mutex_);
+}
+
+// Runs before crashing: normal context.
+ExceptionHandler::~ExceptionHandler() {
+ pthread_mutex_lock(&g_handler_stack_mutex_);
+ std::vector<ExceptionHandler*>::iterator handler =
+ std::find(g_handler_stack_->begin(), g_handler_stack_->end(), this);
+ g_handler_stack_->erase(handler);
+ if (g_handler_stack_->empty()) {
+ delete g_handler_stack_;
+ g_handler_stack_ = NULL;
+ RestoreAlternateStackLocked();
+ RestoreHandlersLocked();
+ }
+ pthread_mutex_unlock(&g_handler_stack_mutex_);
+}
+
+// Runs before crashing: normal context.
+// static
+bool ExceptionHandler::InstallHandlersLocked() {
+ if (handlers_installed)
+ return false;
+
+ // Fail if unable to store all the old handlers.
+ for (int i = 0; i < kNumHandledSignals; ++i) {
+ SKIP_SIGILL(kExceptionSignals[i]);
+ if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1)
+ return false;
+ }
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+
+ // Mask all exception signals when we're handling one of them.
+ for (int i = 0; i < kNumHandledSignals; ++i) {
+ SKIP_SIGILL(kExceptionSignals[i]);
+ sigaddset(&sa.sa_mask, kExceptionSignals[i]);
+ }
+
+ sa.sa_sigaction = SignalHandler;
+ sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
+
+ for (int i = 0; i < kNumHandledSignals; ++i) {
+ SKIP_SIGILL(kExceptionSignals[i]);
+ if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) {
+ // At this point it is impractical to back out changes, and so failure to
+ // install a signal is intentionally ignored.
+ }
+ }
+ handlers_installed = true;
+ return true;
+}
+
+// This function runs in a compromised context: see the top of the file.
+// Runs on the crashing thread.
+// static
+void ExceptionHandler::RestoreHandlersLocked() {
+ if (!handlers_installed)
+ return;
+
+ for (int i = 0; i < kNumHandledSignals; ++i) {
+ SKIP_SIGILL(kExceptionSignals[i]);
+ if (sigaction(kExceptionSignals[i], &old_handlers[i], NULL) == -1) {
+ InstallDefaultHandler(kExceptionSignals[i]);
+ }
+ }
+ handlers_installed = false;
+}
+
+// void ExceptionHandler::set_crash_handler(HandlerCallback callback) {
+// crash_handler_ = callback;
+// }
+
+// This function runs in a compromised context: see the top of the file.
+// Runs on the crashing thread.
+// static
+void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
+
+ // Give the first chance handler a chance to recover from this signal
+ //
+ // This is primarily used by V8. V8 uses guard regions to guarantee memory
+ // safety in WebAssembly. This means some signals might be expected if they
+ // originate from Wasm code while accessing the guard region. We give V8 the
+ // chance to handle and recover from these signals first.
+ if (g_first_chance_handler_ != nullptr &&
+ g_first_chance_handler_(sig, info, uc)) {
+ return;
+ }
+
+ // All the exception signals are blocked at this point.
+ pthread_mutex_lock(&g_handler_stack_mutex_);
+
+ // Sometimes, Breakpad runs inside a process where some other buggy code
+ // saves and restores signal handlers temporarily with 'signal'
+ // instead of 'sigaction'. This loses the SA_SIGINFO flag associated
+ // with this function. As a consequence, the values of 'info' and 'uc'
+ // become totally bogus, generally inducing a crash.
+ //
+ // The following code tries to detect this case. When it does, it
+ // resets the signal handlers with sigaction + SA_SIGINFO and returns.
+ // This forces the signal to be thrown again, but this time the kernel
+ // will call the function with the right arguments.
+ struct sigaction cur_handler;
+ if (sigaction(sig, NULL, &cur_handler) == 0 &&
+ cur_handler.sa_sigaction == SignalHandler &&
+ (cur_handler.sa_flags & SA_SIGINFO) == 0) {
+ // Reset signal handler with the right flags.
+ sigemptyset(&cur_handler.sa_mask);
+ sigaddset(&cur_handler.sa_mask, sig);
+
+ cur_handler.sa_sigaction = SignalHandler;
+ cur_handler.sa_flags = SA_ONSTACK | SA_SIGINFO;
+
+ if (sigaction(sig, &cur_handler, NULL) == -1) {
+ // When resetting the handler fails, try to reset the
+ // default one to avoid an infinite loop here.
+ InstallDefaultHandler(sig);
+ }
+ pthread_mutex_unlock(&g_handler_stack_mutex_);
+ return;
+ }
+
+ bool handled = false;
+ for (int i = g_handler_stack_->size() - 1; !handled && i >= 0; --i) {
+ handled = (*g_handler_stack_)[i]->HandleSignal(sig, info, uc);
+ }
+
+ // Upon returning from this signal handler, sig will become unmasked and then
+ // it will be retriggered. If one of the ExceptionHandlers handled it
+ // successfully, restore the default handler. Otherwise, restore the
+ // previously installed handler. Then, when the signal is retriggered, it will
+ // be delivered to the appropriate handler.
+ if (handled) {
+ InstallDefaultHandler(sig);
+ } else {
+ RestoreHandlersLocked();
+ }
+
+ pthread_mutex_unlock(&g_handler_stack_mutex_);
+
+ // info->si_code <= 0 iff SI_FROMUSER (SI_FROMKERNEL otherwise).
+ if (info->si_code <= 0 || sig == SIGABRT) {
+ // This signal was triggered by somebody sending us the signal with kill().
+ // In order to retrigger it, we have to queue a new signal by calling
+ // kill() ourselves. The special case (si_pid == 0 && sig == SIGABRT) is
+ // due to the kernel sending a SIGABRT from a user request via SysRQ.
+ if (sys_tgkill(getpid(), syscall(__NR_gettid), sig) < 0) {
+ // If we failed to kill ourselves (e.g. because a sandbox disallows us
+ // to do so), we instead resort to terminating our process. This will
+ // result in an incorrect exit code.
+ _exit(1);
+ }
+ } else {
+ // This was a synchronous signal triggered by a hard fault (e.g. SIGSEGV).
+ // No need to reissue the signal. It will automatically trigger again,
+ // when we return from the signal handler.
+ }
+}
+
+struct ThreadArgument {
+ pid_t pid; // the crashing process
+ const MinidumpDescriptor* minidump_descriptor;
+ ExceptionHandler* handler;
+ const void* context; // a CrashContext structure
+ size_t context_size;
+};
+
+// This is the entry function for the cloned process. We are in a compromised
+// context here: see the top of the file.
+// static
+int ExceptionHandler::ThreadEntry(void *arg) {
+ const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg);
+
+ // Close the write end of the pipe. This allows us to fail if the parent dies
+ // while waiting for the continue signal.
+ sys_close(thread_arg->handler->fdes[1]);
+
+ // Block here until the crashing process unblocks us when
+ // we're allowed to use ptrace
+ thread_arg->handler->WaitForContinueSignal();
+ sys_close(thread_arg->handler->fdes[0]);
+
+ return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
+ thread_arg->context_size) == false;
+}
+
+#ifdef MOZ_PHC
+static void GetPHCAddrInfo(siginfo_t* siginfo,
+ mozilla::phc::AddrInfo* addr_info) {
+ // Is this a crash involving a PHC allocation?
+ if (siginfo->si_signo == SIGSEGV || siginfo->si_signo == SIGBUS) {
+ mozilla::phc::IsPHCAllocation(siginfo->si_addr, addr_info);
+ }
+}
+#endif
+
+// This function runs in a compromised context: see the top of the file.
+// Runs on the crashing thread.
+bool ExceptionHandler::HandleSignal(int /*sig*/, siginfo_t* info, void* uc) {
+ mozilla::phc::AddrInfo* addr_info = nullptr;
+#ifdef MOZ_PHC
+ addr_info = &mozilla::phc::gAddrInfo;
+ GetPHCAddrInfo(info, addr_info);
+#endif
+
+ if (filter_ && !filter_(callback_context_))
+ return false;
+
+ // Allow ourselves to be dumped if the signal is trusted.
+ bool signal_trusted = info->si_code > 0;
+ bool signal_pid_trusted = info->si_code == SI_USER ||
+ info->si_code == SI_TKILL;
+ if (signal_trusted || (signal_pid_trusted && info->si_pid == getpid())) {
+ sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+ }
+
+ // Fill in all the holes in the struct to make Valgrind happy.
+ memset(&g_crash_context_, 0, sizeof(g_crash_context_));
+ memcpy(&g_crash_context_.siginfo, info, sizeof(siginfo_t));
+ memcpy(&g_crash_context_.context, uc, sizeof(ucontext_t));
+#if defined(__aarch64__)
+ ucontext_t* uc_ptr = (ucontext_t*)uc;
+ struct fpsimd_context* fp_ptr =
+ (struct fpsimd_context*)&uc_ptr->uc_mcontext.__reserved;
+ if (fp_ptr->head.magic == FPSIMD_MAGIC) {
+ memcpy(&g_crash_context_.float_state, fp_ptr,
+ sizeof(g_crash_context_.float_state));
+ }
+#elif !defined(__ARM_EABI__) && !defined(__mips__)
+ // FP state is not part of user ABI on ARM Linux.
+ // In case of MIPS Linux FP state is already part of ucontext_t
+ // and 'float_state' is not a member of CrashContext.
+ ucontext_t* uc_ptr = (ucontext_t*)uc;
+ if (uc_ptr->uc_mcontext.fpregs) {
+ memcpy(&g_crash_context_.float_state, uc_ptr->uc_mcontext.fpregs,
+ sizeof(g_crash_context_.float_state));
+ }
+#endif
+ g_crash_context_.tid = syscall(__NR_gettid);
+ if (crash_handler_ != NULL) {
+ if (crash_handler_(&g_crash_context_, sizeof(g_crash_context_),
+ callback_context_)) {
+ return true;
+ }
+ }
+
+ return GenerateDump(&g_crash_context_, addr_info);
+}
+
+// This is a public interface to HandleSignal that allows the client to
+// generate a crash dump. This function may run in a compromised context.
+bool ExceptionHandler::SimulateSignalDelivery(int sig) {
+ siginfo_t siginfo = {};
+ // Mimic a trusted signal to allow tracing the process (see
+ // ExceptionHandler::HandleSignal().
+ siginfo.si_code = SI_USER;
+ siginfo.si_pid = getpid();
+ ucontext_t context;
+ getcontext(&context);
+ return HandleSignal(sig, &siginfo, &context);
+}
+
+// This function may run in a compromised context: see the top of the file.
+bool ExceptionHandler::GenerateDump(
+ CrashContext *context, const mozilla::phc::AddrInfo* addr_info) {
+ if (IsOutOfProcess()) {
+ bool success =
+ crash_generation_client_->RequestDump(context, sizeof(*context));
+
+ if (callback_) {
+ success =
+ callback_(minidump_descriptor_, callback_context_, addr_info, success);
+ }
+
+ return success;
+ }
+
+ // Allocating too much stack isn't a problem, and better to err on the side
+ // of caution than smash it into random locations.
+ static const unsigned kChildStackSize = 16000;
+ PageAllocator allocator;
+ uint8_t* stack = reinterpret_cast<uint8_t*>(allocator.Alloc(kChildStackSize));
+ if (!stack)
+ return false;
+ // clone() needs the top-most address. (scrub just to be safe)
+ stack += kChildStackSize;
+ my_memset(stack - 16, 0, 16);
+
+ ThreadArgument thread_arg;
+ thread_arg.handler = this;
+ thread_arg.minidump_descriptor = &minidump_descriptor_;
+ thread_arg.pid = getpid();
+ thread_arg.context = context;
+ thread_arg.context_size = sizeof(*context);
+
+ // We need to explicitly enable ptrace of parent processes on some
+ // kernels, but we need to know the PID of the cloned process before we
+ // can do this. Create a pipe here which we can use to block the
+ // cloned process after creating it, until we have explicitly enabled ptrace
+ if (sys_pipe(fdes) == -1) {
+ // Creating the pipe failed. We'll log an error but carry on anyway,
+ // as we'll probably still get a useful crash report. All that will happen
+ // is the write() and read() calls will fail with EBADF
+ static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump "
+ "sys_pipe failed:";
+ logger::write(no_pipe_msg, sizeof(no_pipe_msg) - 1);
+ logger::write(strerror(errno), strlen(strerror(errno)));
+ logger::write("\n", 1);
+
+ // Ensure fdes[0] and fdes[1] are invalid file descriptors.
+ fdes[0] = fdes[1] = -1;
+ }
+
+ const pid_t child = sys_clone(
+ ThreadEntry, stack, CLONE_FS | CLONE_UNTRACED, &thread_arg, NULL, NULL,
+ NULL);
+ if (child == -1) {
+ sys_close(fdes[0]);
+ sys_close(fdes[1]);
+ return false;
+ }
+
+ if (child != 0) {
+ static const char clonedMsg[] =
+ "ExceptionHandler::GenerateDump cloned child ";
+ char pidMsg[32] = {};
+
+ unsigned int pidLen = my_uint_len(child);
+ my_uitos(pidMsg, child, pidLen);
+
+ logger::write(clonedMsg, my_strlen(clonedMsg));
+ logger::write(pidMsg, pidLen);
+ logger::write("\n", 1);
+ } else {
+ static const char childMsg[] =
+ "ExceptionHandler::GenerateDump I'm the child\n";
+ logger::write(childMsg, my_strlen(childMsg));
+ }
+
+ // Close the read end of the pipe.
+ sys_close(fdes[0]);
+ // Allow the child to ptrace us
+ sys_prctl(PR_SET_PTRACER, child, 0, 0, 0);
+ SendContinueSignalToChild();
+ int status = 0;
+ const int r = HANDLE_EINTR(sys_waitpid(child, &status, __WALL));
+
+ sys_close(fdes[1]);
+
+ if (r == -1) {
+ static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:";
+ logger::write(msg, sizeof(msg) - 1);
+ logger::write(strerror(errno), strlen(strerror(errno)));
+ logger::write("\n", 1);
+ }
+
+ bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
+ if (callback_)
+ success =
+ callback_(minidump_descriptor_, callback_context_, addr_info, success);
+ return success;
+}
+
+// This function runs in a compromised context: see the top of the file.
+void ExceptionHandler::SendContinueSignalToChild() {
+ static const char okToContinueMessage = 'a';
+ int r;
+ r = HANDLE_EINTR(sys_write(fdes[1], &okToContinueMessage, sizeof(char)));
+ if (r == -1) {
+ static const char msg[] = "ExceptionHandler::SendContinueSignalToChild "
+ "sys_write failed:";
+ logger::write(msg, sizeof(msg) - 1);
+ logger::write(strerror(errno), strlen(strerror(errno)));
+ logger::write("\n", 1);
+ }
+
+ const char* msg = "ExceptionHandler::SendContinueSignalToChild sent continue signal to child\n";
+ logger::write(msg, my_strlen(msg));
+}
+
+// This function runs in a compromised context: see the top of the file.
+// Runs on the cloned process.
+void ExceptionHandler::WaitForContinueSignal() {
+ int r;
+ char receivedMessage;
+
+ const char* waitMsg = "ExceptionHandler::WaitForContinueSignal waiting for continue signal...\n";
+ logger::write(waitMsg, my_strlen(waitMsg));
+
+ r = HANDLE_EINTR(sys_read(fdes[0], &receivedMessage, sizeof(char)));
+ if (r == -1) {
+ static const char msg[] = "ExceptionHandler::WaitForContinueSignal "
+ "sys_read failed:";
+ logger::write(msg, sizeof(msg) - 1);
+ logger::write(strerror(errno), strlen(strerror(errno)));
+ logger::write("\n", 1);
+ }
+}
+
+// This function runs in a compromised context: see the top of the file.
+// Runs on the cloned process.
+bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
+ size_t context_size) {
+ const bool may_skip_dump =
+ minidump_descriptor_.skip_dump_if_principal_mapping_not_referenced();
+ const uintptr_t principal_mapping_address =
+ minidump_descriptor_.address_within_principal_mapping();
+ const bool sanitize_stacks = minidump_descriptor_.sanitize_stacks();
+ if (minidump_descriptor_.IsMicrodumpOnConsole()) {
+ return google_breakpad::WriteMicrodump(
+ crashing_process,
+ context,
+ context_size,
+ mapping_list_,
+ may_skip_dump,
+ principal_mapping_address,
+ sanitize_stacks,
+ *minidump_descriptor_.microdump_extra_info());
+ }
+ if (minidump_descriptor_.IsFD()) {
+ return google_breakpad::WriteMinidump(minidump_descriptor_.fd(),
+ minidump_descriptor_.size_limit(),
+ crashing_process,
+ context,
+ context_size,
+ mapping_list_,
+ app_memory_list_,
+ may_skip_dump,
+ principal_mapping_address,
+ sanitize_stacks);
+ }
+ return google_breakpad::WriteMinidump(minidump_descriptor_.path(),
+ minidump_descriptor_.size_limit(),
+ crashing_process,
+ context,
+ context_size,
+ mapping_list_,
+ app_memory_list_,
+ may_skip_dump,
+ principal_mapping_address,
+ sanitize_stacks);
+}
+
+// static
+bool ExceptionHandler::WriteMinidump(const string& dump_path,
+ MinidumpCallback callback,
+ void* callback_context) {
+ MinidumpDescriptor descriptor(dump_path);
+ ExceptionHandler eh(descriptor, NULL, callback, callback_context, false, -1);
+ return eh.WriteMinidump();
+}
+
+// In order to making using EBP to calculate the desired value for ESP
+// a valid operation, ensure that this function is compiled with a
+// frame pointer using the following attribute. This attribute
+// is supported on GCC but not on clang.
+#if defined(__i386__) && defined(__GNUC__) && !defined(__clang__)
+__attribute__((optimize("no-omit-frame-pointer")))
+#endif
+bool ExceptionHandler::WriteMinidump() {
+ if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() &&
+ !minidump_descriptor_.IsMicrodumpOnConsole()) {
+ // Update the path of the minidump so that this can be called multiple times
+ // and new files are created for each minidump. This is done before the
+ // generation happens, as clients may want to access the MinidumpDescriptor
+ // after this call to find the exact path to the minidump file.
+ minidump_descriptor_.UpdatePath();
+ } else if (minidump_descriptor_.IsFD()) {
+ // Reposition the FD to its beginning and resize it to get rid of the
+ // previous minidump info.
+ lseek(minidump_descriptor_.fd(), 0, SEEK_SET);
+ ignore_result(ftruncate(minidump_descriptor_.fd(), 0));
+ }
+
+ // Allow this process to be dumped.
+ sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+
+ CrashContext context;
+ int getcontext_result = getcontext(&context.context);
+ if (getcontext_result)
+ return false;
+
+#if defined(__i386__)
+ // In CPUFillFromUContext in minidumpwriter.cc the stack pointer is retrieved
+ // from REG_UESP instead of from REG_ESP. REG_UESP is the user stack pointer
+ // and it only makes sense when running in kernel mode with a different stack
+ // pointer. When WriteMiniDump is called during normal processing REG_UESP is
+ // zero which leads to bad minidump files.
+ if (!context.context.uc_mcontext.gregs[REG_UESP]) {
+ // If REG_UESP is set to REG_ESP then that includes the stack space for the
+ // CrashContext object in this function, which is about 128 KB. Since the
+ // Linux dumper only records 32 KB of stack this would mean that nothing
+ // useful would be recorded. A better option is to set REG_UESP to REG_EBP,
+ // perhaps with a small negative offset in case there is any code that
+ // objects to them being equal.
+ context.context.uc_mcontext.gregs[REG_UESP] =
+ context.context.uc_mcontext.gregs[REG_EBP] - 16;
+ // The stack saving is based off of REG_ESP so it must be set to match the
+ // new REG_UESP.
+ context.context.uc_mcontext.gregs[REG_ESP] =
+ context.context.uc_mcontext.gregs[REG_UESP];
+ }
+#endif
+
+#if !defined(__ARM_EABI__) && !defined(__aarch64__) && !defined(__mips__)
+ // FPU state is not part of ARM EABI ucontext_t.
+ memcpy(&context.float_state, context.context.uc_mcontext.fpregs,
+ sizeof(context.float_state));
+#endif
+ context.tid = sys_gettid();
+
+ // Add an exception stream to the minidump for better reporting.
+ memset(&context.siginfo, 0, sizeof(context.siginfo));
+ context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED;
+#if defined(__i386__)
+ context.siginfo.si_addr =
+ reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_EIP]);
+#elif defined(__x86_64__)
+ context.siginfo.si_addr =
+ reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_RIP]);
+#elif defined(__arm__)
+ context.siginfo.si_addr =
+ reinterpret_cast<void*>(context.context.uc_mcontext.arm_pc);
+#elif defined(__aarch64__)
+ context.siginfo.si_addr =
+ reinterpret_cast<void*>(context.context.uc_mcontext.pc);
+#elif defined(__mips__)
+ context.siginfo.si_addr =
+ reinterpret_cast<void*>(context.context.uc_mcontext.pc);
+#else
+#error "This code has not been ported to your platform yet."
+#endif
+
+ // nullptr here for phc::AddrInfo* is ok because this is not a crash.
+ return GenerateDump(&context, nullptr);
+}
+
+void ExceptionHandler::AddMappingInfo(const string& name,
+ const wasteful_vector<uint8_t>& identifier,
+ uintptr_t start_address,
+ size_t mapping_size,
+ size_t file_offset) {
+ MappingInfo info;
+ info.start_addr = start_address;
+ info.size = mapping_size;
+ info.offset = file_offset;
+ strncpy(info.name, name.c_str(), sizeof(info.name) - 1);
+ info.name[sizeof(info.name) - 1] = '\0';
+
+ MappingEntry mapping;
+ mapping.first = info;
+ mapping.second.assign(identifier.begin(), identifier.end());
+ mapping_list_.push_back(mapping);
+}
+
+void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) {
+ AppMemoryList::iterator iter =
+ std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr);
+ if (iter != app_memory_list_.end()) {
+ // Don't allow registering the same pointer twice.
+ return;
+ }
+
+ AppMemory app_memory;
+ app_memory.ptr = ptr;
+ app_memory.length = length;
+ app_memory_list_.push_back(app_memory);
+}
+
+void ExceptionHandler::UnregisterAppMemory(void* ptr) {
+ AppMemoryList::iterator iter =
+ std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr);
+ if (iter != app_memory_list_.end()) {
+ app_memory_list_.erase(iter);
+ }
+}
+
+// static
+bool ExceptionHandler::WriteMinidumpForChild(pid_t child,
+ pid_t child_blamed_thread,
+ const string& dump_path,
+ MinidumpCallback callback,
+ void* callback_context) {
+ // This function is not run in a compromised context.
+ MinidumpDescriptor descriptor(dump_path);
+ descriptor.UpdatePath();
+#if defined(MOZ_OXIDIZED_BREAKPAD)
+ nsCString error_msg;
+ if (!write_minidump_linux(descriptor.path(), child, child_blamed_thread, &error_msg))
+ return false;
+#else
+ if (!google_breakpad::WriteMinidump(descriptor.path(),
+ child,
+ child_blamed_thread))
+ return false;
+#endif
+
+ // nullptr here for phc::AddrInfo* is ok because this is not a crash.
+ return callback ? callback(descriptor, callback_context, nullptr, true)
+ : true;
+}
+
+void SetFirstChanceExceptionHandler(FirstChanceHandler callback) {
+ g_first_chance_handler_ = callback;
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.h b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.h
new file mode 100644
index 0000000000..46ad399419
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.h
@@ -0,0 +1,289 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
+#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
+
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/ucontext.h>
+
+#include <string>
+
+#include "linux/crash_generation/crash_generation_client.h"
+#include "linux/handler/minidump_descriptor.h"
+#include "linux/minidump_writer/minidump_writer.h"
+#include "common/scoped_ptr.h"
+#include "common/using_std_string.h"
+#include "google_breakpad/common/minidump_format.h"
+
+#ifdef MOZ_PHC
+#include "PHC.h"
+#else
+namespace mozilla { namespace phc { class AddrInfo {}; } }
+#endif
+
+namespace google_breakpad {
+
+// ExceptionHandler
+//
+// ExceptionHandler can write a minidump file when an exception occurs,
+// or when WriteMinidump() is called explicitly by your program.
+//
+// To have the exception handler write minidumps when an uncaught exception
+// (crash) occurs, you should create an instance early in the execution
+// of your program, and keep it around for the entire time you want to
+// have crash handling active (typically, until shutdown).
+// (NOTE): There should be only be one this kind of exception handler
+// object per process.
+//
+// If you want to write minidumps without installing the exception handler,
+// you can create an ExceptionHandler with install_handler set to false,
+// then call WriteMinidump. You can also use this technique if you want to
+// use different minidump callbacks for different call sites.
+//
+// In either case, a callback function is called when a minidump is written,
+// which receives the full path or file descriptor of the minidump. The
+// caller can collect and write additional application state to that minidump,
+// and launch an external crash-reporting application.
+//
+// Caller should try to make the callbacks as crash-friendly as possible,
+// it should avoid use heap memory allocation as much as possible.
+
+class ExceptionHandler {
+ public:
+ // A callback function to run before Breakpad performs any substantial
+ // processing of an exception. A FilterCallback is called before writing
+ // a minidump. |context| is the parameter supplied by the user as
+ // callback_context when the handler was created.
+ //
+ // If a FilterCallback returns true, Breakpad will continue processing,
+ // attempting to write a minidump. If a FilterCallback returns false,
+ // Breakpad will immediately report the exception as unhandled without
+ // writing a minidump, allowing another handler the opportunity to handle it.
+ typedef bool (*FilterCallback)(void *context);
+
+ // A callback function to run after the minidump has been written.
+ // |descriptor| contains the file descriptor or file path containing the
+ // minidump. |context| is the parameter supplied by the user as
+ // callback_context when the handler was created. |succeeded| indicates
+ // whether a minidump file was successfully written.
+ //
+ // If an exception occurred and the callback returns true, Breakpad will
+ // treat the exception as fully-handled, suppressing any other handlers from
+ // being notified of the exception. If the callback returns false, Breakpad
+ // will treat the exception as unhandled, and allow another handler to handle
+ // it. If there are no other handlers, Breakpad will report the exception to
+ // the system as unhandled, allowing a debugger or native crash dialog the
+ // opportunity to handle the exception. Most callback implementations
+ // should normally return the value of |succeeded|, or when they wish to
+ // not report an exception of handled, false. Callbacks will rarely want to
+ // return true directly (unless |succeeded| is true).
+ typedef bool (*MinidumpCallback)(const MinidumpDescriptor& descriptor,
+ void* context,
+ const mozilla::phc::AddrInfo* addr_info,
+ bool succeeded);
+
+ // In certain cases, a user may wish to handle the generation of the minidump
+ // themselves. In this case, they can install a handler callback which is
+ // called when a crash has occurred. If this function returns true, no other
+ // processing of occurs and the process will shortly be crashed. If this
+ // returns false, the normal processing continues.
+ typedef bool (*HandlerCallback)(const void* crash_context,
+ size_t crash_context_size,
+ void* context);
+
+ // Creates a new ExceptionHandler instance to handle writing minidumps.
+ // Before writing a minidump, the optional |filter| callback will be called.
+ // Its return value determines whether or not Breakpad should write a
+ // minidump. The minidump content will be written to the file path or file
+ // descriptor from |descriptor|, and the optional |callback| is called after
+ // writing the dump file, as described above.
+ // If install_handler is true, then a minidump will be written whenever
+ // an unhandled exception occurs. If it is false, minidumps will only
+ // be written when WriteMinidump is called.
+ // If |server_fd| is valid, the minidump is generated out-of-process. If it
+ // is -1, in-process generation will always be used.
+ ExceptionHandler(const MinidumpDescriptor& descriptor,
+ FilterCallback filter,
+ MinidumpCallback callback,
+ void* callback_context,
+ bool install_handler,
+ const int server_fd);
+ ~ExceptionHandler();
+
+ const MinidumpDescriptor& minidump_descriptor() const {
+ return minidump_descriptor_;
+ }
+
+ void set_minidump_descriptor(const MinidumpDescriptor& descriptor) {
+ minidump_descriptor_ = descriptor;
+ }
+
+ void set_crash_handler(HandlerCallback callback) {
+ crash_handler_ = callback;
+ }
+
+ void set_crash_generation_client(CrashGenerationClient* client) {
+ crash_generation_client_.reset(client);
+ }
+
+ // Writes a minidump immediately. This can be used to capture the execution
+ // state independently of a crash.
+ // Returns true on success.
+ // If the ExceptionHandler has been created with a path, a new file is
+ // generated for each minidump. The file path can be retrieved in the
+ // MinidumpDescriptor passed to the MinidumpCallback or by accessing the
+ // MinidumpDescriptor directly from the ExceptionHandler (with
+ // minidump_descriptor()).
+ // If the ExceptionHandler has been created with a file descriptor, the file
+ // descriptor is repositioned to its beginning and the previous generated
+ // minidump is overwritten.
+ // Note that this method is not supposed to be called from a compromised
+ // context as it uses the heap.
+ bool WriteMinidump();
+
+ // Convenience form of WriteMinidump which does not require an
+ // ExceptionHandler instance.
+ static bool WriteMinidump(const string& dump_path,
+ MinidumpCallback callback,
+ void* callback_context);
+
+ // Write a minidump of |child| immediately. This can be used to
+ // capture the execution state of |child| independently of a crash.
+ // Pass a meaningful |child_blamed_thread| to make that thread in
+ // the child process the one from which a crash signature is
+ // extracted.
+ //
+ // WARNING: the return of this function *must* happen before
+ // the code that will eventually reap |child| executes.
+ // Otherwise there's a pernicious race condition in which |child|
+ // exits, is reaped, another process created with its pid, then that
+ // new process dumped.
+ static bool WriteMinidumpForChild(pid_t child,
+ pid_t child_blamed_thread,
+ const string& dump_path,
+ MinidumpCallback callback,
+ void* callback_context);
+
+ // This structure is passed to minidump_writer.h:WriteMinidump via an opaque
+ // blob. It shouldn't be needed in any user code.
+ struct CrashContext {
+ siginfo_t siginfo;
+ pid_t tid; // the crashing thread.
+ ucontext_t context;
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ // #ifdef this out because FP state is not part of user ABI for Linux ARM.
+ // In case of MIPS Linux FP state is already part of ucontext_t so
+ // 'float_state' is not required.
+ fpstate_t float_state;
+#endif
+ };
+
+ // Returns whether out-of-process dump generation is used or not.
+ bool IsOutOfProcess() const {
+ return crash_generation_client_.get() != NULL;
+ }
+
+ // Add information about a memory mapping. This can be used if
+ // a custom library loader is used that maps things in a way
+ // that the linux dumper can't handle by reading the maps file.
+ void AddMappingInfo(const string& name,
+ const wasteful_vector<uint8_t>& identifier,
+ uintptr_t start_address,
+ size_t mapping_size,
+ size_t file_offset);
+
+ // Register a block of memory of length bytes starting at address ptr
+ // to be copied to the minidump when a crash happens.
+ void RegisterAppMemory(void* ptr, size_t length);
+
+ // Unregister a block of memory that was registered with RegisterAppMemory.
+ void UnregisterAppMemory(void* ptr);
+
+ // Force signal handling for the specified signal.
+ bool SimulateSignalDelivery(int sig);
+
+ // Report a crash signal from an SA_SIGINFO signal handler.
+ bool HandleSignal(int sig, siginfo_t* info, void* uc);
+
+ private:
+ // Save the old signal handlers and install new ones.
+ static bool InstallHandlersLocked();
+ // Restore the old signal handlers.
+ static void RestoreHandlersLocked();
+
+ void PreresolveSymbols();
+ bool GenerateDump(CrashContext *context,
+ const mozilla::phc::AddrInfo* addr_info);
+ void SendContinueSignalToChild();
+ void WaitForContinueSignal();
+
+ static void SignalHandler(int sig, siginfo_t* info, void* uc);
+ static int ThreadEntry(void* arg);
+ bool DoDump(pid_t crashing_process, const void* context,
+ size_t context_size);
+
+ const FilterCallback filter_;
+ const MinidumpCallback callback_;
+ void* const callback_context_;
+
+ scoped_ptr<CrashGenerationClient> crash_generation_client_;
+
+ MinidumpDescriptor minidump_descriptor_;
+
+ // Must be volatile. The compiler is unaware of the code which runs in
+ // the signal handler which reads this variable. Without volatile the
+ // compiler is free to optimise away writes to this variable which it
+ // believes are never read.
+ volatile HandlerCallback crash_handler_;
+
+ // We need to explicitly enable ptrace of parent processes on some
+ // kernels, but we need to know the PID of the cloned process before we
+ // can do this. We create a pipe which we can use to block the
+ // cloned process after creating it, until we have explicitly enabled
+ // ptrace. This is used to store the file descriptors for the pipe
+ int fdes[2] = {-1, -1};
+
+ // Callers can add extra info about mappings for cases where the
+ // dumper code cannot extract enough information from /proc/<pid>/maps.
+ MappingList mapping_list_;
+
+ // Callers can request additional memory regions to be included in
+ // the dump.
+ AppMemoryList app_memory_list_;
+};
+
+typedef bool (*FirstChanceHandler)(int, siginfo_t*, void*);
+void SetFirstChanceExceptionHandler(FirstChanceHandler callback);
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler_unittest.cc
new file mode 100644
index 0000000000..8fa59456c9
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler_unittest.cc
@@ -0,0 +1,1290 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <poll.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#if defined(__mips__)
+#include <sys/cachectl.h>
+#endif
+
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "linux/handler/exception_handler.h"
+#include "linux/minidump_writer/minidump_writer.h"
+#include "common/linux/eintr_wrapper.h"
+#include "common/linux/ignore_ret.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/tests/auto_tempdir.h"
+#include "common/using_std_string.h"
+#include "third_party/lss/linux_syscall_support.h"
+#include "google_breakpad/processor/minidump.h"
+
+using namespace google_breakpad;
+
+namespace {
+
+// Flush the instruction cache for a given memory range.
+// Only required on ARM and mips.
+void FlushInstructionCache(const char* memory, uint32_t memory_size) {
+#if defined(__arm__)
+ long begin = reinterpret_cast<long>(memory);
+ long end = begin + static_cast<long>(memory_size);
+# if defined(__ANDROID__)
+ // Provided by Android's <unistd.h>
+ cacheflush(begin, end, 0);
+# elif defined(__linux__)
+ // GLibc/ARM doesn't provide a wrapper for it, do a direct syscall.
+# ifndef __ARM_NR_cacheflush
+# define __ARM_NR_cacheflush 0xf0002
+# endif
+ syscall(__ARM_NR_cacheflush, begin, end, 0);
+# else
+# error "Your operating system is not supported yet"
+# endif
+#elif defined(__mips__)
+# if defined(__ANDROID__)
+ // Provided by Android's <unistd.h>
+ long begin = reinterpret_cast<long>(memory);
+ long end = begin + static_cast<long>(memory_size);
+#if _MIPS_SIM == _ABIO32
+ cacheflush(begin, end, 0);
+#else
+ syscall(__NR_cacheflush, begin, end, ICACHE);
+#endif
+# elif defined(__linux__)
+ // See http://www.linux-mips.org/wiki/Cacheflush_Syscall.
+ cacheflush(const_cast<char*>(memory), memory_size, ICACHE);
+# else
+# error "Your operating system is not supported yet"
+# endif
+#endif
+}
+
+void sigchld_handler(int signo) { }
+
+int CreateTMPFile(const string& dir, string* path) {
+ string file = dir + "/exception-handler-unittest.XXXXXX";
+ const char* c_file = file.c_str();
+ // Copy that string, mkstemp needs a C string it can modify.
+ char* c_path = strdup(c_file);
+ const int fd = mkstemp(c_path);
+ if (fd >= 0)
+ *path = c_path;
+ free(c_path);
+ return fd;
+}
+
+class ExceptionHandlerTest : public ::testing::Test {
+ protected:
+ void SetUp() {
+ // We need to be able to wait for children, so SIGCHLD cannot be SIG_IGN.
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sigchld_handler;
+ ASSERT_NE(sigaction(SIGCHLD, &sa, &old_action), -1);
+ }
+
+ void TearDown() {
+ sigaction(SIGCHLD, &old_action, NULL);
+ }
+
+ struct sigaction old_action;
+};
+
+
+void WaitForProcessToTerminate(pid_t process_id, int expected_status) {
+ int status;
+ ASSERT_NE(HANDLE_EINTR(waitpid(process_id, &status, 0)), -1);
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_EQ(expected_status, WTERMSIG(status));
+}
+
+// Reads the minidump path sent over the pipe |fd| and sets it in |path|.
+void ReadMinidumpPathFromPipe(int fd, string* path) {
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
+ ASSERT_EQ(1, r);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+
+ int32_t len;
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(len)), read(fd, &len, sizeof(len)));
+ ASSERT_LT(len, 2048);
+ char* filename = static_cast<char*>(malloc(len + 1));
+ ASSERT_EQ(len, read(fd, filename, len));
+ filename[len] = 0;
+ close(fd);
+ *path = filename;
+ free(filename);
+}
+
+} // namespace
+
+TEST(ExceptionHandlerTest, SimpleWithPath) {
+ AutoTempDir temp_dir;
+ ExceptionHandler handler(
+ MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1);
+ EXPECT_EQ(temp_dir.path(), handler.minidump_descriptor().directory());
+ string temp_subdir = temp_dir.path() + "/subdir";
+ handler.set_minidump_descriptor(MinidumpDescriptor(temp_subdir));
+ EXPECT_EQ(temp_subdir, handler.minidump_descriptor().directory());
+}
+
+TEST(ExceptionHandlerTest, SimpleWithFD) {
+ AutoTempDir temp_dir;
+ string path;
+ const int fd = CreateTMPFile(temp_dir.path(), &path);
+ ExceptionHandler handler(MinidumpDescriptor(fd), NULL, NULL, NULL, true, -1);
+ close(fd);
+}
+
+static bool DoneCallback(const MinidumpDescriptor& descriptor,
+ void* context,
+ bool succeeded) {
+ if (!succeeded)
+ return false;
+
+ if (!descriptor.IsFD()) {
+ int fd = reinterpret_cast<intptr_t>(context);
+ uint32_t len = 0;
+ len = my_strlen(descriptor.path());
+ IGNORE_RET(HANDLE_EINTR(sys_write(fd, &len, sizeof(len))));
+ IGNORE_RET(HANDLE_EINTR(sys_write(fd, descriptor.path(), len)));
+ }
+ return true;
+}
+
+#ifndef ADDRESS_SANITIZER
+
+// This is a replacement for "*reinterpret_cast<volatile int*>(NULL) = 0;"
+// It is needed because GCC is allowed to assume that the program will
+// not execute any undefined behavior (UB) operation. Further, when GCC
+// observes that UB statement is reached, it can assume that all statements
+// leading to the UB one are never executed either, and can completely
+// optimize them out. In the case of ExceptionHandlerTest::ExternalDumper,
+// GCC-4.9 optimized out the entire set up of ExceptionHandler, causing
+// test failure.
+volatile int *p_null; // external linkage, so GCC can't tell that it
+ // remains NULL. Volatile just for a good measure.
+static void DoNullPointerDereference() {
+ *p_null = 1;
+}
+
+void ChildCrash(bool use_fd) {
+ AutoTempDir temp_dir;
+ int fds[2] = {0};
+ int minidump_fd = -1;
+ string minidump_path;
+ if (use_fd) {
+ minidump_fd = CreateTMPFile(temp_dir.path(), &minidump_path);
+ } else {
+ ASSERT_NE(pipe(fds), -1);
+ }
+
+ const pid_t child = fork();
+ if (child == 0) {
+ {
+ google_breakpad::scoped_ptr<ExceptionHandler> handler;
+ if (use_fd) {
+ handler.reset(new ExceptionHandler(MinidumpDescriptor(minidump_fd),
+ NULL, NULL, NULL, true, -1));
+ } else {
+ close(fds[0]); // Close the reading end.
+ void* fd_param = reinterpret_cast<void*>(fds[1]);
+ handler.reset(new ExceptionHandler(MinidumpDescriptor(temp_dir.path()),
+ NULL, DoneCallback, fd_param,
+ true, -1));
+ }
+ // Crash with the exception handler in scope.
+ DoNullPointerDereference();
+ }
+ }
+ if (!use_fd)
+ close(fds[1]); // Close the writting end.
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV));
+
+ if (!use_fd)
+ ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path));
+
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_path.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+ unlink(minidump_path.c_str());
+}
+
+TEST(ExceptionHandlerTest, ChildCrashWithPath) {
+ ASSERT_NO_FATAL_FAILURE(ChildCrash(false));
+}
+
+TEST(ExceptionHandlerTest, ChildCrashWithFD) {
+ ASSERT_NO_FATAL_FAILURE(ChildCrash(true));
+}
+
+#if !defined(__ANDROID_API__) || __ANDROID_API__ >= __ANDROID_API_N__
+static void* SleepFunction(void* unused) {
+ while (true) usleep(1000000);
+ return NULL;
+}
+
+static void* CrashFunction(void* b_ptr) {
+ pthread_barrier_t* b = reinterpret_cast<pthread_barrier_t*>(b_ptr);
+ pthread_barrier_wait(b);
+ DoNullPointerDereference();
+ return NULL;
+}
+
+// Tests that concurrent crashes do not enter a loop by alternately triggering
+// the signal handler.
+TEST(ExceptionHandlerTest, ParallelChildCrashesDontHang) {
+ AutoTempDir temp_dir;
+ const pid_t child = fork();
+ if (child == 0) {
+ google_breakpad::scoped_ptr<ExceptionHandler> handler(
+ new ExceptionHandler(MinidumpDescriptor(temp_dir.path()), NULL, NULL,
+ NULL, true, -1));
+
+ // We start a number of threads to make sure handling the signal takes
+ // enough time for the second thread to enter the signal handler.
+ int num_sleep_threads = 100;
+ google_breakpad::scoped_array<pthread_t> sleep_threads(
+ new pthread_t[num_sleep_threads]);
+ for (int i = 0; i < num_sleep_threads; ++i) {
+ ASSERT_EQ(0, pthread_create(&sleep_threads[i], NULL, SleepFunction,
+ NULL));
+ }
+
+ int num_crash_threads = 2;
+ google_breakpad::scoped_array<pthread_t> crash_threads(
+ new pthread_t[num_crash_threads]);
+ // Barrier to synchronize crashing both threads at the same time.
+ pthread_barrier_t b;
+ ASSERT_EQ(0, pthread_barrier_init(&b, NULL, num_crash_threads + 1));
+ for (int i = 0; i < num_crash_threads; ++i) {
+ ASSERT_EQ(0, pthread_create(&crash_threads[i], NULL, CrashFunction, &b));
+ }
+ pthread_barrier_wait(&b);
+ for (int i = 0; i < num_crash_threads; ++i) {
+ ASSERT_EQ(0, pthread_join(crash_threads[i], NULL));
+ }
+ }
+
+ // Wait a while until the child should have crashed.
+ usleep(1000000);
+ // Kill the child if it is still running.
+ kill(child, SIGKILL);
+
+ // If the child process terminated by itself, it will have returned SIGSEGV.
+ // If however it got stuck in a loop, it will have been killed by the
+ // SIGKILL.
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV));
+}
+#endif // !defined(__ANDROID_API__) || __ANDROID_API__ >= __ANDROID_API_N__
+
+static bool DoneCallbackReturnFalse(const MinidumpDescriptor& descriptor,
+ void* context,
+ bool succeeded) {
+ return false;
+}
+
+static bool DoneCallbackReturnTrue(const MinidumpDescriptor& descriptor,
+ void* context,
+ bool succeeded) {
+ return true;
+}
+
+static bool DoneCallbackRaiseSIGKILL(const MinidumpDescriptor& descriptor,
+ void* context,
+ bool succeeded) {
+ raise(SIGKILL);
+ return true;
+}
+
+static bool FilterCallbackReturnFalse(void* context) {
+ return false;
+}
+
+static bool FilterCallbackReturnTrue(void* context) {
+ return true;
+}
+
+// SIGKILL cannot be blocked and a handler cannot be installed for it. In the
+// following tests, if the child dies with signal SIGKILL, then the signal was
+// redelivered to this handler. If the child dies with SIGSEGV then it wasn't.
+static void RaiseSIGKILL(int sig) {
+ raise(SIGKILL);
+}
+
+static bool InstallRaiseSIGKILL() {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = RaiseSIGKILL;
+ return sigaction(SIGSEGV, &sa, NULL) != -1;
+}
+
+static void CrashWithCallbacks(ExceptionHandler::FilterCallback filter,
+ ExceptionHandler::MinidumpCallback done,
+ string path) {
+ ExceptionHandler handler(
+ MinidumpDescriptor(path), filter, done, NULL, true, -1);
+ // Crash with the exception handler in scope.
+ DoNullPointerDereference();
+}
+
+TEST(ExceptionHandlerTest, RedeliveryOnFilterCallbackFalse) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ASSERT_TRUE(InstallRaiseSIGKILL());
+ CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path());
+ }
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL));
+}
+
+TEST(ExceptionHandlerTest, RedeliveryOnDoneCallbackFalse) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ASSERT_TRUE(InstallRaiseSIGKILL());
+ CrashWithCallbacks(NULL, DoneCallbackReturnFalse, temp_dir.path());
+ }
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL));
+}
+
+TEST(ExceptionHandlerTest, NoRedeliveryOnDoneCallbackTrue) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ASSERT_TRUE(InstallRaiseSIGKILL());
+ CrashWithCallbacks(NULL, DoneCallbackReturnTrue, temp_dir.path());
+ }
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV));
+}
+
+TEST(ExceptionHandlerTest, NoRedeliveryOnFilterCallbackTrue) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ASSERT_TRUE(InstallRaiseSIGKILL());
+ CrashWithCallbacks(FilterCallbackReturnTrue, NULL, temp_dir.path());
+ }
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV));
+}
+
+TEST(ExceptionHandlerTest, RedeliveryToDefaultHandler) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ // Custom signal handlers, which may have been installed by a test launcher,
+ // are undesirable in this child.
+ signal(SIGSEGV, SIG_DFL);
+
+ CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path());
+ }
+
+ // As RaiseSIGKILL wasn't installed, the redelivery should just kill the child
+ // with SIGSEGV.
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV));
+}
+
+// Check that saving and restoring the signal handler with 'signal'
+// instead of 'sigaction' doesn't make the Breakpad signal handler
+// crash. See comments in ExceptionHandler::SignalHandler for full
+// details.
+TEST(ExceptionHandlerTest, RedeliveryOnBadSignalHandlerFlag) {
+ AutoTempDir temp_dir;
+ const pid_t child = fork();
+ if (child == 0) {
+ // Install the RaiseSIGKILL handler for SIGSEGV.
+ ASSERT_TRUE(InstallRaiseSIGKILL());
+
+ // Create a new exception handler, this installs a new SIGSEGV
+ // handler, after saving the old one.
+ ExceptionHandler handler(
+ MinidumpDescriptor(temp_dir.path()), NULL,
+ DoneCallbackReturnFalse, NULL, true, -1);
+
+ // Install the default SIGSEGV handler, saving the current one.
+ // Then re-install the current one with 'signal', this loses the
+ // SA_SIGINFO flag associated with the Breakpad handler.
+ sighandler_t old_handler = signal(SIGSEGV, SIG_DFL);
+ ASSERT_NE(reinterpret_cast<void*>(old_handler),
+ reinterpret_cast<void*>(SIG_ERR));
+ ASSERT_NE(reinterpret_cast<void*>(signal(SIGSEGV, old_handler)),
+ reinterpret_cast<void*>(SIG_ERR));
+
+ // Crash with the exception handler in scope.
+ DoNullPointerDereference();
+ }
+ // SIGKILL means Breakpad's signal handler didn't crash.
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL));
+}
+
+TEST(ExceptionHandlerTest, StackedHandlersDeliveredToTop) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()),
+ NULL,
+ NULL,
+ NULL,
+ true,
+ -1);
+ CrashWithCallbacks(NULL, DoneCallbackRaiseSIGKILL, temp_dir.path());
+ }
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL));
+}
+
+TEST(ExceptionHandlerTest, StackedHandlersNotDeliveredToBottom) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()),
+ NULL,
+ DoneCallbackRaiseSIGKILL,
+ NULL,
+ true,
+ -1);
+ CrashWithCallbacks(NULL, NULL, temp_dir.path());
+ }
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV));
+}
+
+TEST(ExceptionHandlerTest, StackedHandlersFilteredToBottom) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()),
+ NULL,
+ DoneCallbackRaiseSIGKILL,
+ NULL,
+ true,
+ -1);
+ CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path());
+ }
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL));
+}
+
+TEST(ExceptionHandlerTest, StackedHandlersUnhandledToBottom) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()),
+ NULL,
+ DoneCallbackRaiseSIGKILL,
+ NULL,
+ true,
+ -1);
+ CrashWithCallbacks(NULL, DoneCallbackReturnFalse, temp_dir.path());
+ }
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL));
+}
+
+namespace {
+const int kSimpleFirstChanceReturnStatus = 42;
+bool SimpleFirstChanceHandler(int, siginfo_t*, void*) {
+ _exit(kSimpleFirstChanceReturnStatus);
+}
+}
+
+TEST(ExceptionHandlerTest, FirstChanceHandlerRuns) {
+ AutoTempDir temp_dir;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ ExceptionHandler handler(
+ MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1);
+ google_breakpad::SetFirstChanceExceptionHandler(SimpleFirstChanceHandler);
+ DoNullPointerDereference();
+ }
+ int status;
+ ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(kSimpleFirstChanceReturnStatus, WEXITSTATUS(status));
+}
+
+#endif // !ADDRESS_SANITIZER
+
+const unsigned char kIllegalInstruction[] = {
+#if defined(__mips__)
+ // mfc2 zero,Impl - usually illegal in userspace.
+ 0x48, 0x00, 0x00, 0x48
+#else
+ // This crashes with SIGILL on x86/x86-64/arm.
+ 0xff, 0xff, 0xff, 0xff
+#endif
+};
+
+// Test that memory around the instruction pointer is written
+// to the dump as a MinidumpMemoryRegion.
+TEST(ExceptionHandlerTest, InstructionPointerMemory) {
+ AutoTempDir temp_dir;
+ int fds[2];
+ ASSERT_NE(pipe(fds), -1);
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const uint32_t kMemorySize = 256; // bytes
+ const int kOffset = kMemorySize / 2;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[0]);
+ ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL,
+ DoneCallback, reinterpret_cast<void*>(fds[1]),
+ true, -1);
+ // Get some executable memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ if (!memory)
+ exit(0);
+
+ // Write some instructions that will crash. Put them in the middle
+ // of the block of memory, because the minidump should contain 128
+ // bytes on either side of the instruction pointer.
+ memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction));
+ FlushInstructionCache(memory, kMemorySize);
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ memory_function();
+ }
+ close(fds[1]);
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL));
+
+ string minidump_path;
+ ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path));
+
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_path.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_path);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_LT(0U, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ EXPECT_EQ(kMemorySize, region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint8_t prefix_bytes[kOffset];
+ uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(kIllegalInstruction)];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ memset(suffix_bytes, 0, sizeof(suffix_bytes));
+ EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kOffset, kIllegalInstruction,
+ sizeof(kIllegalInstruction)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(kIllegalInstruction),
+ suffix_bytes, sizeof(suffix_bytes)) == 0);
+
+ unlink(minidump_path.c_str());
+}
+
+// Test that the memory region around the instruction pointer is
+// bounded correctly on the low end.
+TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
+ AutoTempDir temp_dir;
+ int fds[2];
+ ASSERT_NE(pipe(fds), -1);
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const uint32_t kMemorySize = 256; // bytes
+ const int kOffset = 0;
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[0]);
+ ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL,
+ DoneCallback, reinterpret_cast<void*>(fds[1]),
+ true, -1);
+ // Get some executable memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ if (!memory)
+ exit(0);
+
+ // Write some instructions that will crash. Put them in the middle
+ // of the block of memory, because the minidump should contain 128
+ // bytes on either side of the instruction pointer.
+ memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction));
+ FlushInstructionCache(memory, kMemorySize);
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ memory_function();
+ }
+ close(fds[1]);
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL));
+
+ string minidump_path;
+ ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path));
+
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_path.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_path);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_LT(0U, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ EXPECT_EQ(kMemorySize / 2, region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint8_t suffix_bytes[kMemorySize / 2 - sizeof(kIllegalInstruction)];
+ memset(suffix_bytes, 0, sizeof(suffix_bytes));
+ EXPECT_TRUE(memcmp(bytes + kOffset, kIllegalInstruction,
+ sizeof(kIllegalInstruction)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(kIllegalInstruction),
+ suffix_bytes, sizeof(suffix_bytes)) == 0);
+ unlink(minidump_path.c_str());
+}
+
+// Test that the memory region around the instruction pointer is
+// bounded correctly on the high end.
+TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
+ AutoTempDir temp_dir;
+ int fds[2];
+ ASSERT_NE(pipe(fds), -1);
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ // Use 4k here because the OS will hand out a single page even
+ // if a smaller size is requested, and this test wants to
+ // test the upper bound of the memory range.
+ const uint32_t kMemorySize = 4096; // bytes
+ const int kOffset = kMemorySize - sizeof(kIllegalInstruction);
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[0]);
+ ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL,
+ DoneCallback, reinterpret_cast<void*>(fds[1]),
+ true, -1);
+ // Get some executable memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ if (!memory)
+ exit(0);
+
+ // Write some instructions that will crash. Put them in the middle
+ // of the block of memory, because the minidump should contain 128
+ // bytes on either side of the instruction pointer.
+ memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction));
+ FlushInstructionCache(memory, kMemorySize);
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ memory_function();
+ }
+ close(fds[1]);
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL));
+
+ string minidump_path;
+ ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path));
+
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_path.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+
+ // Read the minidump. Locate the exception record and the memory list, and
+ // then ensure that there is a memory region in the memory list that covers
+ // the instruction pointer from the exception record.
+ Minidump minidump(minidump_path);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_LT(0U, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ const size_t kPrefixSize = 128; // bytes
+ EXPECT_EQ(kPrefixSize + sizeof(kIllegalInstruction), region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint8_t prefix_bytes[kPrefixSize];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kPrefixSize,
+ kIllegalInstruction, sizeof(kIllegalInstruction)) == 0);
+
+ unlink(minidump_path.c_str());
+}
+
+#ifndef ADDRESS_SANITIZER
+
+// Ensure that an extra memory block doesn't get added when the instruction
+// pointer is not in mapped memory.
+TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
+ AutoTempDir temp_dir;
+ int fds[2];
+ ASSERT_NE(pipe(fds), -1);
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[0]);
+ ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL,
+ DoneCallback, reinterpret_cast<void*>(fds[1]),
+ true, -1);
+ // Try calling a NULL pointer.
+ typedef void (*void_function)(void);
+ // Volatile markings are needed to keep Clang from generating invalid
+ // opcodes. See http://crbug.com/498354 for details.
+ volatile void_function memory_function =
+ reinterpret_cast<void_function>(NULL);
+ memory_function();
+ // not reached
+ exit(1);
+ }
+ close(fds[1]);
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV));
+
+ string minidump_path;
+ ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path));
+
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_path.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is no memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_path);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ ASSERT_TRUE(exception);
+
+ MinidumpContext* exception_context = exception->GetContext();
+ ASSERT_TRUE(exception_context);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(exception_context->GetInstructionPointer(&instruction_pointer));
+ EXPECT_EQ(instruction_pointer, 0u);
+
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(memory_list);
+
+ unsigned int region_count = memory_list->region_count();
+ ASSERT_GE(region_count, 1u);
+
+ for (unsigned int region_index = 0;
+ region_index < region_count;
+ ++region_index) {
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionAtIndex(region_index);
+ uint64_t region_base = region->GetBase();
+ EXPECT_FALSE(instruction_pointer >= region_base &&
+ instruction_pointer < region_base + region->GetSize());
+ }
+
+ unlink(minidump_path.c_str());
+}
+
+#endif // !ADDRESS_SANITIZER
+
+// Test that anonymous memory maps can be annotated with names and IDs.
+TEST(ExceptionHandlerTest, ModuleInfo) {
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const uint32_t kMemorySize = sysconf(_SC_PAGESIZE);
+ const char* kMemoryName = "a fake module";
+ const uint8_t kModuleGUID[sizeof(MDGUID)] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+ };
+ const string module_identifier = "33221100554477668899AABBCCDDEEFF0";
+
+ // Get some memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
+ ASSERT_TRUE(memory);
+
+ PageAllocator allocator;
+ auto_wasteful_vector<uint8_t, sizeof(MDGUID)> guid(&allocator);
+ guid.assign(std::begin(kModuleGUID), std::end(kModuleGUID));
+ AutoTempDir temp_dir;
+ ExceptionHandler handler(
+ MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1);
+
+ // Add info about the anonymous memory mapping.
+ handler.AddMappingInfo(kMemoryName,
+ guid,
+ kMemoryAddress,
+ kMemorySize,
+ 0);
+ ASSERT_TRUE(handler.WriteMinidump());
+
+ const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor();
+ // Read the minidump. Load the module list, and ensure that the mmap'ed
+ // |memory| is listed with the given module name and debug ID.
+ Minidump minidump(minidump_desc.path());
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module =
+ module_list->GetModuleForAddress(kMemoryAddress);
+ ASSERT_TRUE(module);
+
+ EXPECT_EQ(kMemoryAddress, module->base_address());
+ EXPECT_EQ(kMemorySize, module->size());
+ EXPECT_EQ(kMemoryName, module->code_file());
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+
+ unlink(minidump_desc.path());
+}
+
+#ifndef ADDRESS_SANITIZER
+
+static const unsigned kControlMsgSize =
+ CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
+
+static bool
+CrashHandler(const void* crash_context, size_t crash_context_size,
+ void* context) {
+ const int fd = (intptr_t) context;
+ int fds[2];
+ if (pipe(fds) == -1) {
+ // There doesn't seem to be any way to reliably handle
+ // this failure without the parent process hanging
+ // At least make sure that this process doesn't access
+ // unexpected file descriptors
+ fds[0] = -1;
+ fds[1] = -1;
+ }
+ struct kernel_msghdr msg = {0};
+ struct kernel_iovec iov;
+ iov.iov_base = const_cast<void*>(crash_context);
+ iov.iov_len = crash_context_size;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ 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((struct msghdr*) &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();
+
+ ssize_t ret = HANDLE_EINTR(sys_sendmsg(fd, &msg, 0));
+ sys_close(fds[1]);
+ if (ret <= 0)
+ return false;
+
+ char b;
+ IGNORE_RET(HANDLE_EINTR(sys_read(fds[0], &b, 1)));
+
+ return true;
+}
+
+TEST(ExceptionHandlerTest, ExternalDumper) {
+ int fds[2];
+ ASSERT_NE(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds), -1);
+ static const int on = 1;
+ setsockopt(fds[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+ setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[0]);
+ ExceptionHandler handler(MinidumpDescriptor("/tmp1"), NULL, NULL,
+ reinterpret_cast<void*>(fds[1]), true, -1);
+ handler.set_crash_handler(CrashHandler);
+ DoNullPointerDereference();
+ }
+ close(fds[1]);
+ struct msghdr msg = {0};
+ struct iovec iov;
+ static const unsigned kCrashContextSize =
+ sizeof(ExceptionHandler::CrashContext);
+ char context[kCrashContextSize];
+ char control[kControlMsgSize];
+ iov.iov_base = context;
+ iov.iov_len = kCrashContextSize;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = kControlMsgSize;
+
+ const ssize_t n = HANDLE_EINTR(recvmsg(fds[0], &msg, 0));
+ ASSERT_EQ(static_cast<ssize_t>(kCrashContextSize), n);
+ ASSERT_EQ(kControlMsgSize, msg.msg_controllen);
+ ASSERT_EQ(static_cast<__typeof__(msg.msg_flags)>(0), msg.msg_flags);
+ ASSERT_EQ(0, close(fds[0]));
+
+ 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);
+ ASSERT_EQ(sizeof(int), len);
+ signal_fd = *(reinterpret_cast<int*>(CMSG_DATA(hdr)));
+ } else if (hdr->cmsg_type == SCM_CREDENTIALS) {
+ const struct ucred *cred =
+ reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
+ crashing_pid = cred->pid;
+ }
+ }
+
+ ASSERT_NE(crashing_pid, -1);
+ ASSERT_NE(signal_fd, -1);
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + "/exception-handler-unittest";
+ ASSERT_TRUE(WriteMinidump(templ.c_str(), crashing_pid, context,
+ kCrashContextSize));
+ static const char b = 0;
+ ASSERT_EQ(1, (HANDLE_EINTR(write(signal_fd, &b, 1))));
+ ASSERT_EQ(0, close(signal_fd));
+
+ ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV));
+
+ struct stat st;
+ ASSERT_EQ(0, stat(templ.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+ unlink(templ.c_str());
+}
+
+#endif // !ADDRESS_SANITIZER
+
+TEST(ExceptionHandlerTest, WriteMinidumpExceptionStream) {
+ AutoTempDir temp_dir;
+ ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, NULL,
+ NULL, false, -1);
+ ASSERT_TRUE(handler.WriteMinidump());
+
+ string minidump_path = handler.minidump_descriptor().path();
+
+ // Read the minidump and check the exception stream.
+ Minidump minidump(minidump_path);
+ ASSERT_TRUE(minidump.Read());
+ MinidumpException* exception = minidump.GetException();
+ ASSERT_TRUE(exception);
+ const MDRawExceptionStream* raw = exception->exception();
+ ASSERT_TRUE(raw);
+ EXPECT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED,
+ raw->exception_record.exception_code);
+}
+
+TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithFD) {
+ AutoTempDir temp_dir;
+ string path;
+ const int fd = CreateTMPFile(temp_dir.path(), &path);
+ ExceptionHandler handler(MinidumpDescriptor(fd), NULL, NULL, NULL, false, -1);
+ ASSERT_TRUE(handler.WriteMinidump());
+ // Check by the size of the data written to the FD that a minidump was
+ // generated.
+ off_t size = lseek(fd, 0, SEEK_CUR);
+ ASSERT_GT(size, 0);
+
+ // Generate another minidump.
+ ASSERT_TRUE(handler.WriteMinidump());
+ size = lseek(fd, 0, SEEK_CUR);
+ ASSERT_GT(size, 0);
+}
+
+TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithPath) {
+ AutoTempDir temp_dir;
+ ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, NULL,
+ NULL, false, -1);
+ ASSERT_TRUE(handler.WriteMinidump());
+
+ const MinidumpDescriptor& minidump_1 = handler.minidump_descriptor();
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_1.path(), &st));
+ ASSERT_GT(st.st_size, 0);
+ string minidump_1_path(minidump_1.path());
+ // Check it is a valid minidump.
+ Minidump minidump1(minidump_1_path);
+ ASSERT_TRUE(minidump1.Read());
+ unlink(minidump_1.path());
+
+ // Generate another minidump, it should go to a different file.
+ ASSERT_TRUE(handler.WriteMinidump());
+ const MinidumpDescriptor& minidump_2 = handler.minidump_descriptor();
+ ASSERT_EQ(0, stat(minidump_2.path(), &st));
+ ASSERT_GT(st.st_size, 0);
+ string minidump_2_path(minidump_2.path());
+ // Check it is a valid minidump.
+ Minidump minidump2(minidump_2_path);
+ ASSERT_TRUE(minidump2.Read());
+ unlink(minidump_2.path());
+
+ // 2 distinct files should be produced.
+ ASSERT_STRNE(minidump_1_path.c_str(), minidump_2_path.c_str());
+}
+
+// Test that an additional memory region can be added to the minidump.
+TEST(ExceptionHandlerTest, AdditionalMemory) {
+ const uint32_t kMemorySize = sysconf(_SC_PAGESIZE);
+
+ // Get some heap memory.
+ uint8_t* memory = new uint8_t[kMemorySize];
+ const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
+ ASSERT_TRUE(memory);
+
+ // Stick some data into the memory so the contents can be verified.
+ for (uint32_t i = 0; i < kMemorySize; ++i) {
+ memory[i] = i % 255;
+ }
+
+ AutoTempDir temp_dir;
+ ExceptionHandler handler(
+ MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1);
+
+ // Add the memory region to the list of memory to be included.
+ handler.RegisterAppMemory(memory, kMemorySize);
+ handler.WriteMinidump();
+
+ const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor();
+
+ // Read the minidump. Ensure that the memory region is present
+ Minidump minidump(minidump_desc.path());
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(dump_memory_list);
+ const MinidumpMemoryRegion* region =
+ dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
+ ASSERT_TRUE(region);
+
+ EXPECT_EQ(kMemoryAddress, region->GetBase());
+ EXPECT_EQ(kMemorySize, region->GetSize());
+
+ // Verify memory contents.
+ EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
+
+ delete[] memory;
+}
+
+// Test that a memory region that was previously registered
+// can be unregistered.
+TEST(ExceptionHandlerTest, AdditionalMemoryRemove) {
+ const uint32_t kMemorySize = sysconf(_SC_PAGESIZE);
+
+ // Get some heap memory.
+ uint8_t* memory = new uint8_t[kMemorySize];
+ const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
+ ASSERT_TRUE(memory);
+
+ AutoTempDir temp_dir;
+ ExceptionHandler handler(
+ MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1);
+
+ // Add the memory region to the list of memory to be included.
+ handler.RegisterAppMemory(memory, kMemorySize);
+
+ // ...and then remove it
+ handler.UnregisterAppMemory(memory);
+ handler.WriteMinidump();
+
+ const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor();
+
+ // Read the minidump. Ensure that the memory region is not present.
+ Minidump minidump(minidump_desc.path());
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(dump_memory_list);
+ const MinidumpMemoryRegion* region =
+ dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
+ EXPECT_FALSE(region);
+
+ delete[] memory;
+}
+
+static bool SimpleCallback(const MinidumpDescriptor& descriptor,
+ void* context,
+ bool succeeded) {
+ string* filename = reinterpret_cast<string*>(context);
+ *filename = descriptor.path();
+ return true;
+}
+
+TEST(ExceptionHandlerTest, WriteMinidumpForChild) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
+ close(fds[0]);
+ syscall(__NR_exit);
+ }
+ close(fds[0]);
+
+ AutoTempDir temp_dir;
+ string minidump_filename;
+ ASSERT_TRUE(
+ ExceptionHandler::WriteMinidumpForChild(child, child,
+ temp_dir.path(), SimpleCallback,
+ (void*)&minidump_filename));
+
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+ // Check that the crashing thread is the main thread of |child|
+ MinidumpException* exception = minidump.GetException();
+ ASSERT_TRUE(exception);
+ uint32_t thread_id;
+ ASSERT_TRUE(exception->GetThreadID(&thread_id));
+ EXPECT_EQ(child, static_cast<int32_t>(thread_id));
+
+ const MDRawExceptionStream* raw = exception->exception();
+ ASSERT_TRUE(raw);
+ EXPECT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED,
+ raw->exception_record.exception_code);
+
+ close(fds[1]);
+ unlink(minidump_filename.c_str());
+}
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.cc b/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.cc
new file mode 100644
index 0000000000..b0ef05bd9d
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "linux/handler/guid_generator.h"
+
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+//
+// GUIDGenerator
+//
+// This class is used to generate random GUID.
+// Currently use random number to generate a GUID since Linux has
+// no native GUID generator. This should be OK since we don't expect
+// crash to happen very offen.
+//
+class GUIDGenerator {
+ public:
+ static uint16_t BytesToUInt16(const uint8_t bytes[]) {
+ return ((uint16_t) bytes[1] << 8) | ((uint16_t) bytes[0]);
+ }
+
+ // The last field in a GUID is 48 bits long so we're converting only 6 bytes
+ static uint64_t BytesToUInt48(const uint8_t bytes[]) {
+ return ((uint64_t) bytes[0] << 40) | ((uint64_t) bytes[1] << 32) |
+ ((uint64_t) bytes[2] << 24) | ((uint64_t) bytes[3] << 16) |
+ ((uint64_t) bytes[4] << 8) | (uint64_t) bytes[5];
+ }
+
+ static void UInt32ToBytes(uint8_t bytes[], uint32_t n) {
+ bytes[0] = n & 0xff;
+ bytes[1] = (n >> 8) & 0xff;
+ bytes[2] = (n >> 16) & 0xff;
+ bytes[3] = (n >> 24) & 0xff;
+ }
+
+ static bool CreateGUID(GUID *guid) {
+ InitOnce();
+ guid->data1 = random();
+ guid->data2 = (uint16_t)(random());
+ guid->data3 = (uint16_t)(random());
+ UInt32ToBytes(&guid->data4[0], random());
+ UInt32ToBytes(&guid->data4[4], random());
+ return true;
+ }
+
+ private:
+ static void InitOnce() {
+ pthread_once(&once_control, &InitOnceImpl);
+ }
+
+ static void InitOnceImpl() {
+ srandom(time(NULL));
+ }
+
+ static pthread_once_t once_control;
+};
+
+pthread_once_t GUIDGenerator::once_control = PTHREAD_ONCE_INIT;
+
+bool CreateGUID(GUID *guid) {
+ return GUIDGenerator::CreateGUID(guid);
+}
+
+// Parse guid to string.
+bool GUIDToString(const GUID *guid, char *buf, size_t buf_len) {
+ // Should allow more space the the max length of GUID.
+ assert(buf_len > kGUIDStringLength);
+ int num = snprintf(buf, buf_len, kGUIDFormatString,
+ guid->data1, guid->data2, guid->data3,
+ GUIDGenerator::BytesToUInt16(&(guid->data4[0])),
+ GUIDGenerator::BytesToUInt48(&(guid->data4[2])));
+ if (num != kGUIDStringLength)
+ return false;
+
+ buf[num] = '\0';
+ return true;
+}
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.h b/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.h
new file mode 100644
index 0000000000..de97eda1cb
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/handler/guid_generator.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef LINUX_HANDLER_GUID_GENERATOR_H__
+#define LINUX_HANDLER_GUID_GENERATOR_H__
+
+#include "google_breakpad/common/minidump_format.h"
+
+typedef MDGUID GUID;
+
+// Format string for parsing GUID.
+const char kGUIDFormatString[] = "%08x-%04x-%04x-%04x-%012" PRIx64;
+// Length of GUID string. Don't count the ending '\0'.
+const size_t kGUIDStringLength = 36;
+
+// Create a guid.
+bool CreateGUID(GUID *guid);
+
+// Get the string from guid.
+bool GUIDToString(const GUID *guid, char *buf, size_t buf_len);
+
+#endif
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/microdump_extra_info.h b/toolkit/crashreporter/breakpad-client/linux/handler/microdump_extra_info.h
new file mode 100644
index 0000000000..bf01f0c7b1
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/handler/microdump_extra_info.h
@@ -0,0 +1,52 @@
+// Copyright 2015 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_
+#define CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_
+
+namespace google_breakpad {
+
+struct MicrodumpExtraInfo {
+ // Strings pointed to by this struct are not copied, and are
+ // expected to remain valid for the lifetime of the process.
+ const char* build_fingerprint;
+ const char* product_info;
+ const char* gpu_fingerprint;
+ const char* process_type;
+
+ MicrodumpExtraInfo()
+ : build_fingerprint(NULL),
+ product_info(NULL),
+ gpu_fingerprint(NULL),
+ process_type(NULL) {}
+};
+
+}
+
+#endif // CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.cc b/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.cc
new file mode 100644
index 0000000000..21cf4b0dce
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdio.h>
+
+#include "linux/handler/guid_generator.h"
+#include "linux/handler/minidump_descriptor.h"
+
+namespace google_breakpad {
+
+//static
+const MinidumpDescriptor::MicrodumpOnConsole
+ MinidumpDescriptor::kMicrodumpOnConsole = {};
+
+MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor)
+ : mode_(descriptor.mode_),
+ fd_(descriptor.fd_),
+ directory_(descriptor.directory_),
+ c_path_(NULL),
+ size_limit_(descriptor.size_limit_),
+ address_within_principal_mapping_(
+ descriptor.address_within_principal_mapping_),
+ skip_dump_if_principal_mapping_not_referenced_(
+ descriptor.skip_dump_if_principal_mapping_not_referenced_),
+ sanitize_stacks_(descriptor.sanitize_stacks_),
+ microdump_extra_info_(descriptor.microdump_extra_info_) {
+ // The copy constructor is not allowed to be called on a MinidumpDescriptor
+ // with a valid path_, as getting its c_path_ would require the heap which
+ // can cause problems in compromised environments.
+ assert(descriptor.path_.empty());
+}
+
+MinidumpDescriptor& MinidumpDescriptor::operator=(
+ const MinidumpDescriptor& descriptor) {
+ assert(descriptor.path_.empty());
+
+ mode_ = descriptor.mode_;
+ fd_ = descriptor.fd_;
+ directory_ = descriptor.directory_;
+ path_.clear();
+ if (c_path_) {
+ // This descriptor already had a path set, so generate a new one.
+ c_path_ = NULL;
+ UpdatePath();
+ }
+ size_limit_ = descriptor.size_limit_;
+ address_within_principal_mapping_ =
+ descriptor.address_within_principal_mapping_;
+ skip_dump_if_principal_mapping_not_referenced_ =
+ descriptor.skip_dump_if_principal_mapping_not_referenced_;
+ sanitize_stacks_ = descriptor.sanitize_stacks_;
+ microdump_extra_info_ = descriptor.microdump_extra_info_;
+ return *this;
+}
+
+void MinidumpDescriptor::UpdatePath() {
+ assert(mode_ == kWriteMinidumpToFile && !directory_.empty());
+
+ GUID guid;
+ char guid_str[kGUIDStringLength + 1];
+ if (!CreateGUID(&guid) || !GUIDToString(&guid, guid_str, sizeof(guid_str))) {
+ assert(false);
+ }
+
+ path_.clear();
+ path_ = directory_ + "/" + guid_str + ".dmp";
+ c_path_ = path_.c_str();
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.h b/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.h
new file mode 100644
index 0000000000..c3deae8a74
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/handler/minidump_descriptor.h
@@ -0,0 +1,199 @@
+// Copyright (c) 2012 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_
+#define CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_
+
+#include <assert.h>
+#include <sys/types.h>
+
+#include <string>
+
+#include "linux/handler/microdump_extra_info.h"
+#include "common/using_std_string.h"
+
+// This class describes how a crash dump should be generated, either:
+// - Writing a full minidump to a file in a given directory (the actual path,
+// inside the directory, is determined by this class).
+// - Writing a full minidump to a given fd.
+// - Writing a reduced microdump to the console (logcat on Android).
+namespace google_breakpad {
+
+class MinidumpDescriptor {
+ public:
+ struct MicrodumpOnConsole {};
+ static const MicrodumpOnConsole kMicrodumpOnConsole;
+
+ MinidumpDescriptor()
+ : mode_(kUninitialized),
+ fd_(-1),
+ size_limit_(-1),
+ address_within_principal_mapping_(0),
+ skip_dump_if_principal_mapping_not_referenced_(false) {}
+
+ explicit MinidumpDescriptor(const string& directory)
+ : mode_(kWriteMinidumpToFile),
+ fd_(-1),
+ directory_(directory),
+ c_path_(NULL),
+ size_limit_(-1),
+ address_within_principal_mapping_(0),
+ skip_dump_if_principal_mapping_not_referenced_(false),
+ sanitize_stacks_(false) {
+ assert(!directory.empty());
+ }
+
+ explicit MinidumpDescriptor(int fd)
+ : mode_(kWriteMinidumpToFd),
+ fd_(fd),
+ c_path_(NULL),
+ size_limit_(-1),
+ address_within_principal_mapping_(0),
+ skip_dump_if_principal_mapping_not_referenced_(false),
+ sanitize_stacks_(false) {
+ assert(fd != -1);
+ }
+
+ explicit MinidumpDescriptor(const MicrodumpOnConsole&)
+ : mode_(kWriteMicrodumpToConsole),
+ fd_(-1),
+ size_limit_(-1),
+ address_within_principal_mapping_(0),
+ skip_dump_if_principal_mapping_not_referenced_(false),
+ sanitize_stacks_(false) {}
+
+ explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor);
+ MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor);
+
+ static MinidumpDescriptor getMicrodumpDescriptor();
+
+ bool IsFD() const { return mode_ == kWriteMinidumpToFd; }
+
+ int fd() const { return fd_; }
+
+ string directory() const { return directory_; }
+
+ const char* path() const { return c_path_; }
+
+ bool IsMicrodumpOnConsole() const {
+ return mode_ == kWriteMicrodumpToConsole;
+ }
+
+ // Updates the path so it is unique.
+ // Should be called from a normal context: this methods uses the heap.
+ void UpdatePath();
+
+ off_t size_limit() const { return size_limit_; }
+ void set_size_limit(off_t limit) { size_limit_ = limit; }
+
+ uintptr_t address_within_principal_mapping() const {
+ return address_within_principal_mapping_;
+ }
+ void set_address_within_principal_mapping(
+ uintptr_t address_within_principal_mapping) {
+ address_within_principal_mapping_ = address_within_principal_mapping;
+ }
+
+ bool skip_dump_if_principal_mapping_not_referenced() {
+ return skip_dump_if_principal_mapping_not_referenced_;
+ }
+ void set_skip_dump_if_principal_mapping_not_referenced(
+ bool skip_dump_if_principal_mapping_not_referenced) {
+ skip_dump_if_principal_mapping_not_referenced_ =
+ skip_dump_if_principal_mapping_not_referenced;
+ }
+
+ bool sanitize_stacks() const { return sanitize_stacks_; }
+ void set_sanitize_stacks(bool sanitize_stacks) {
+ sanitize_stacks_ = sanitize_stacks;
+ }
+
+ MicrodumpExtraInfo* microdump_extra_info() {
+ assert(IsMicrodumpOnConsole());
+ return &microdump_extra_info_;
+ }
+
+ private:
+ enum DumpMode {
+ kUninitialized = 0,
+ kWriteMinidumpToFile,
+ kWriteMinidumpToFd,
+ kWriteMicrodumpToConsole
+ };
+
+ // Specifies the dump mode (see DumpMode).
+ DumpMode mode_;
+
+ // The file descriptor where the minidump is generated.
+ int fd_;
+
+ // The directory where the minidump should be generated.
+ string directory_;
+
+ // The full path to the generated minidump.
+ string path_;
+
+ // The C string of |path_|. Precomputed so it can be access from a compromised
+ // context.
+ const char* c_path_;
+
+ off_t size_limit_;
+
+ // This member points somewhere into the main module for this
+ // process (the module that is considerered interesting for the
+ // purposes of debugging crashes).
+ uintptr_t address_within_principal_mapping_;
+
+ // If set, threads that do not reference the address range
+ // associated with |address_within_principal_mapping_| will not have their
+ // stacks logged.
+ bool skip_dump_if_principal_mapping_not_referenced_;
+
+ // If set, stacks are sanitized to remove PII. This involves
+ // overwriting any pointer-aligned words that are not either
+ // pointers into a process mapping or small integers (+/-4096). This
+ // leaves enough information to unwind stacks, and preserve some
+ // register values, but elides strings and other program data.
+ bool sanitize_stacks_;
+
+ // The extra microdump data (e.g. product name/version, build
+ // fingerprint, gpu fingerprint) that should be appended to the dump
+ // (microdump only). Microdumps don't have the ability of appending
+ // extra metadata after the dump is generated (as opposite to
+ // minidumps MIME fields), therefore the extra data must be provided
+ // upfront. Any memory pointed to by members of the
+ // MicrodumpExtraInfo struct must be valid for the lifetime of the
+ // process (read: the caller has to guarantee that it is stored in
+ // global static storage.)
+ MicrodumpExtraInfo microdump_extra_info_;
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/log/log.cc b/toolkit/crashreporter/breakpad-client/linux/log/log.cc
new file mode 100644
index 0000000000..3346acc6d8
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/log/log.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "linux/log/log.h"
+
+#if defined(__ANDROID__)
+#include <android/log.h>
+#include <dlfcn.h>
+#else
+#include "third_party/lss/linux_syscall_support.h"
+#endif
+
+namespace logger {
+
+#if defined(__ANDROID__)
+namespace {
+
+// __android_log_buf_write() is not exported in the NDK and is being used by
+// dynamic runtime linking. Its declaration is taken from Android's
+// system/core/include/log/log.h.
+using AndroidLogBufferWriteFunc = int (*)(int bufID, int prio, const char *tag,
+ const char *text);
+const int kAndroidCrashLogId = 4; // From LOG_ID_CRASH in log.h.
+const char kAndroidLogTag[] = "google-breakpad";
+
+bool g_crash_log_initialized = false;
+AndroidLogBufferWriteFunc g_android_log_buf_write = nullptr;
+
+} // namespace
+
+void initializeCrashLogWriter() {
+ if (g_crash_log_initialized)
+ return;
+ g_android_log_buf_write = reinterpret_cast<AndroidLogBufferWriteFunc>(
+ dlsym(RTLD_DEFAULT, "__android_log_buf_write"));
+ g_crash_log_initialized = true;
+}
+
+int writeToCrashLog(const char* buf) {
+ // Try writing to the crash log ring buffer. If not available, fall back to
+ // the standard log buffer.
+ if (g_android_log_buf_write) {
+ return g_android_log_buf_write(kAndroidCrashLogId, ANDROID_LOG_FATAL,
+ kAndroidLogTag, buf);
+ }
+ return __android_log_write(ANDROID_LOG_FATAL, kAndroidLogTag, buf);
+}
+#endif
+
+int write(const char* buf, size_t nbytes) {
+#if defined(__ANDROID__)
+ return __android_log_write(ANDROID_LOG_WARN, kAndroidLogTag, buf);
+#else
+ return sys_write(2, buf, nbytes);
+#endif
+}
+
+} // namespace logger
diff --git a/toolkit/crashreporter/breakpad-client/linux/log/log.h b/toolkit/crashreporter/breakpad-client/linux/log/log.h
new file mode 100644
index 0000000000..f94bbd5fb7
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/log/log.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_LOG_LOG_H_
+#define CLIENT_LINUX_LOG_LOG_H_
+
+#include <stddef.h>
+
+namespace logger {
+
+int write(const char* buf, size_t nbytes);
+
+// In the case of Android the log can be written to the default system log
+// (default behavior of write() above, or to the crash log (see
+// writeToCrashLog() below).
+#if defined(__ANDROID__)
+
+// The logger must be initialized in a non-compromised context.
+void initializeCrashLogWriter();
+
+// Once initialized, writeToCrashLog is safe to use in a compromised context,
+// even if the initialization failed, in which case this will silently fall
+// back on write().
+int writeToCrashLog(const char* buf);
+#endif
+
+} // namespace logger
+
+#endif // CLIENT_LINUX_LOG_LOG_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc
new file mode 100644
index 0000000000..8f25b7be02
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc
@@ -0,0 +1,664 @@
+// Copyright (c) 2014, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This translation unit generates microdumps into the console (logcat on
+// Android). See crbug.com/410294 for more info and design docs.
+
+#include "linux/microdump_writer/microdump_writer.h"
+
+#include <limits>
+
+#include <sys/utsname.h>
+
+#include "linux/dump_writer_common/thread_info.h"
+#include "linux/dump_writer_common/ucontext_reader.h"
+#include "linux/handler/exception_handler.h"
+#include "linux/handler/microdump_extra_info.h"
+#include "linux/log/log.h"
+#include "linux/minidump_writer/linux_ptrace_dumper.h"
+#include "common/linux/file_id.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/memory_allocator.h"
+
+namespace {
+
+using google_breakpad::auto_wasteful_vector;
+using google_breakpad::ExceptionHandler;
+using google_breakpad::kDefaultBuildIdSize;
+using google_breakpad::LinuxDumper;
+using google_breakpad::LinuxPtraceDumper;
+using google_breakpad::MappingInfo;
+using google_breakpad::MappingList;
+using google_breakpad::MicrodumpExtraInfo;
+using google_breakpad::RawContextCPU;
+using google_breakpad::ThreadInfo;
+using google_breakpad::UContextReader;
+
+const size_t kLineBufferSize = 2048;
+
+#if !defined(__LP64__)
+// The following are only used by DumpFreeSpace, so need to be compiled
+// in conditionally in the same way.
+
+template <typename Dst, typename Src>
+Dst saturated_cast(Src src) {
+ if (src >= std::numeric_limits<Dst>::max())
+ return std::numeric_limits<Dst>::max();
+ if (src <= std::numeric_limits<Dst>::min())
+ return std::numeric_limits<Dst>::min();
+ return static_cast<Dst>(src);
+}
+
+int Log2Floor(uint64_t n) {
+ // Copied from chromium src/base/bits.h
+ if (n == 0)
+ return -1;
+ int log = 0;
+ uint64_t value = n;
+ for (int i = 5; i >= 0; --i) {
+ int shift = (1 << i);
+ uint64_t x = value >> shift;
+ if (x != 0) {
+ value = x;
+ log += shift;
+ }
+ }
+ assert(value == 1u);
+ return log;
+}
+
+bool MappingsAreAdjacent(const MappingInfo& a, const MappingInfo& b) {
+ // Because of load biasing, we can end up with a situation where two
+ // mappings actually overlap. So we will define adjacency to also include a
+ // b start address that lies within a's address range (including starting
+ // immediately after a).
+ // Because load biasing only ever moves the start address backwards, the end
+ // address should still increase.
+ return a.start_addr <= b.start_addr && a.start_addr + a.size >= b.start_addr;
+}
+
+bool MappingLessThan(const MappingInfo* a, const MappingInfo* b) {
+ // Return true if mapping a is before mapping b.
+ // For the same reason (load biasing) we compare end addresses, which - unlike
+ // start addresses - will not have been modified.
+ return a->start_addr + a->size < b->start_addr + b->size;
+}
+
+size_t NextOrderedMapping(
+ const google_breakpad::wasteful_vector<MappingInfo*>& mappings,
+ size_t curr) {
+ // Find the mapping that directly follows mappings[curr].
+ // If no such mapping exists, return |invalid| to indicate this.
+ const size_t invalid = std::numeric_limits<size_t>::max();
+ size_t best = invalid;
+ for (size_t next = 0; next < mappings.size(); ++next) {
+ if (MappingLessThan(mappings[curr], mappings[next]) &&
+ (best == invalid || MappingLessThan(mappings[next], mappings[best]))) {
+ best = next;
+ }
+ }
+ return best;
+}
+
+#endif // !__LP64__
+
+class MicrodumpWriter {
+ public:
+ MicrodumpWriter(const ExceptionHandler::CrashContext* context,
+ const MappingList& mappings,
+ bool skip_dump_if_principal_mapping_not_referenced,
+ uintptr_t address_within_principal_mapping,
+ bool sanitize_stack,
+ const MicrodumpExtraInfo& microdump_extra_info,
+ LinuxDumper* dumper)
+ : ucontext_(context ? &context->context : NULL),
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ float_state_(context ? &context->float_state : NULL),
+#endif
+ dumper_(dumper),
+ mapping_list_(mappings),
+ skip_dump_if_principal_mapping_not_referenced_(
+ skip_dump_if_principal_mapping_not_referenced),
+ address_within_principal_mapping_(address_within_principal_mapping),
+ sanitize_stack_(sanitize_stack),
+ microdump_extra_info_(microdump_extra_info),
+ log_line_(NULL),
+ stack_copy_(NULL),
+ stack_len_(0),
+ stack_lower_bound_(0),
+ stack_pointer_(0) {
+ log_line_ = reinterpret_cast<char*>(Alloc(kLineBufferSize));
+ if (log_line_)
+ log_line_[0] = '\0'; // Clear out the log line buffer.
+ }
+
+ ~MicrodumpWriter() { dumper_->ThreadsResume(); }
+
+ bool Init() {
+ // In the exceptional case where the system was out of memory and there
+ // wasn't even room to allocate the line buffer, bail out. There is nothing
+ // useful we can possibly achieve without the ability to Log. At least let's
+ // try to not crash.
+ if (!dumper_->Init() || !log_line_)
+ return false;
+ return dumper_->ThreadsSuspend() && dumper_->LateInit();
+ }
+
+ void Dump() {
+ CaptureResult stack_capture_result = CaptureCrashingThreadStack(-1);
+ if (stack_capture_result == CAPTURE_UNINTERESTING) {
+ LogLine("Microdump skipped (uninteresting)");
+ return;
+ }
+
+ LogLine("-----BEGIN BREAKPAD MICRODUMP-----");
+ DumpProductInformation();
+ DumpOSInformation();
+ DumpProcessType();
+ DumpCrashReason();
+ DumpGPUInformation();
+#if !defined(__LP64__)
+ DumpFreeSpace();
+#endif
+ if (stack_capture_result == CAPTURE_OK)
+ DumpThreadStack();
+ DumpCPUState();
+ DumpMappings();
+ LogLine("-----END BREAKPAD MICRODUMP-----");
+ }
+
+ private:
+ enum CaptureResult { CAPTURE_OK, CAPTURE_FAILED, CAPTURE_UNINTERESTING };
+
+ // Writes one line to the system log.
+ void LogLine(const char* msg) {
+#if defined(__ANDROID__)
+ logger::writeToCrashLog(msg);
+#else
+ logger::write(msg, my_strlen(msg));
+ logger::write("\n", 1);
+#endif
+ }
+
+ // Stages the given string in the current line buffer.
+ void LogAppend(const char* str) {
+ my_strlcat(log_line_, str, kLineBufferSize);
+ }
+
+ // As above (required to take precedence over template specialization below).
+ void LogAppend(char* str) {
+ LogAppend(const_cast<const char*>(str));
+ }
+
+ // Stages the hex repr. of the given int type in the current line buffer.
+ template<typename T>
+ void LogAppend(T value) {
+ // Make enough room to hex encode the largest int type + NUL.
+ static const char HEX[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F'};
+ char hexstr[sizeof(T) * 2 + 1];
+ for (int i = sizeof(T) * 2 - 1; i >= 0; --i, value >>= 4)
+ hexstr[i] = HEX[static_cast<uint8_t>(value) & 0x0F];
+ hexstr[sizeof(T) * 2] = '\0';
+ LogAppend(hexstr);
+ }
+
+ // Stages the buffer content hex-encoded in the current line buffer.
+ void LogAppend(const void* buf, size_t length) {
+ const uint8_t* ptr = reinterpret_cast<const uint8_t*>(buf);
+ for (size_t i = 0; i < length; ++i, ++ptr)
+ LogAppend(*ptr);
+ }
+
+ // Writes out the current line buffer on the system log.
+ void LogCommitLine() {
+ LogLine(log_line_);
+ log_line_[0] = 0;
+ }
+
+ CaptureResult CaptureCrashingThreadStack(int max_stack_len) {
+ stack_pointer_ = UContextReader::GetStackPointer(ucontext_);
+
+ if (!dumper_->GetStackInfo(reinterpret_cast<const void**>(&stack_lower_bound_),
+ &stack_len_, stack_pointer_)) {
+ return CAPTURE_FAILED;
+ }
+
+ if (max_stack_len >= 0 &&
+ stack_len_ > static_cast<size_t>(max_stack_len)) {
+ stack_len_ = max_stack_len;
+ }
+
+ stack_copy_ = reinterpret_cast<uint8_t*>(Alloc(stack_len_));
+ dumper_->CopyFromProcess(stack_copy_, dumper_->crash_thread(),
+ reinterpret_cast<const void*>(stack_lower_bound_),
+ stack_len_);
+
+ if (!skip_dump_if_principal_mapping_not_referenced_) return CAPTURE_OK;
+
+ const MappingInfo* principal_mapping =
+ dumper_->FindMappingNoBias(address_within_principal_mapping_);
+ if (!principal_mapping) return CAPTURE_UNINTERESTING;
+
+ uintptr_t low_addr = principal_mapping->system_mapping_info.start_addr;
+ uintptr_t high_addr = principal_mapping->system_mapping_info.end_addr;
+ uintptr_t pc = UContextReader::GetInstructionPointer(ucontext_);
+ if (low_addr <= pc && pc <= high_addr) return CAPTURE_OK;
+
+ if (dumper_->StackHasPointerToMapping(stack_copy_, stack_len_,
+ stack_pointer_ - stack_lower_bound_,
+ *principal_mapping)) {
+ return CAPTURE_OK;
+ }
+ return CAPTURE_UNINTERESTING;
+ }
+
+ void DumpProductInformation() {
+ LogAppend("V ");
+ if (microdump_extra_info_.product_info) {
+ LogAppend(microdump_extra_info_.product_info);
+ } else {
+ LogAppend("UNKNOWN:0.0.0.0");
+ }
+ LogCommitLine();
+ }
+
+ void DumpProcessType() {
+ LogAppend("P ");
+ if (microdump_extra_info_.process_type) {
+ LogAppend(microdump_extra_info_.process_type);
+ } else {
+ LogAppend("UNKNOWN");
+ }
+ LogCommitLine();
+ }
+
+ void DumpCrashReason() {
+ LogAppend("R ");
+ LogAppend(dumper_->crash_signal());
+ LogAppend(" ");
+ LogAppend(dumper_->GetCrashSignalString());
+ LogAppend(" ");
+ LogAppend(dumper_->crash_address());
+ LogCommitLine();
+ }
+
+ void DumpOSInformation() {
+ const uint8_t n_cpus = static_cast<uint8_t>(sysconf(_SC_NPROCESSORS_CONF));
+
+#if defined(__ANDROID__)
+ const char kOSId[] = "A";
+#else
+ const char kOSId[] = "L";
+#endif
+
+// Dump the runtime architecture. On multiarch devices it might not match the
+// hw architecture (the one returned by uname()), for instance in the case of
+// a 32-bit app running on a aarch64 device.
+#if defined(__aarch64__)
+ const char kArch[] = "arm64";
+#elif defined(__ARMEL__)
+ const char kArch[] = "arm";
+#elif defined(__x86_64__)
+ const char kArch[] = "x86_64";
+#elif defined(__i386__)
+ const char kArch[] = "x86";
+#elif defined(__mips__)
+# if _MIPS_SIM == _ABIO32
+ const char kArch[] = "mips";
+# elif _MIPS_SIM == _ABI64
+ const char kArch[] = "mips64";
+# else
+# error "This mips ABI is currently not supported (n32)"
+#endif
+#else
+#error "This code has not been ported to your platform yet"
+#endif
+
+ LogAppend("O ");
+ LogAppend(kOSId);
+ LogAppend(" ");
+ LogAppend(kArch);
+ LogAppend(" ");
+ LogAppend(n_cpus);
+ LogAppend(" ");
+
+ // Dump the HW architecture (e.g., armv7l, aarch64).
+ struct utsname uts;
+ const bool has_uts_info = (uname(&uts) == 0);
+ const char* hwArch = has_uts_info ? uts.machine : "unknown_hw_arch";
+ LogAppend(hwArch);
+ LogAppend(" ");
+
+ // If the client has attached a build fingerprint to the MinidumpDescriptor
+ // use that one. Otherwise try to get some basic info from uname().
+ if (microdump_extra_info_.build_fingerprint) {
+ LogAppend(microdump_extra_info_.build_fingerprint);
+ } else if (has_uts_info) {
+ LogAppend(uts.release);
+ LogAppend(" ");
+ LogAppend(uts.version);
+ } else {
+ LogAppend("no build fingerprint available");
+ }
+ LogCommitLine();
+ }
+
+ void DumpGPUInformation() {
+ LogAppend("G ");
+ if (microdump_extra_info_.gpu_fingerprint) {
+ LogAppend(microdump_extra_info_.gpu_fingerprint);
+ } else {
+ LogAppend("UNKNOWN");
+ }
+ LogCommitLine();
+ }
+
+ void DumpThreadStack() {
+ if (sanitize_stack_) {
+ dumper_->SanitizeStackCopy(stack_copy_, stack_len_, stack_pointer_,
+ stack_pointer_ - stack_lower_bound_);
+ }
+
+ LogAppend("S 0 ");
+ LogAppend(stack_pointer_);
+ LogAppend(" ");
+ LogAppend(stack_lower_bound_);
+ LogAppend(" ");
+ LogAppend(stack_len_);
+ LogCommitLine();
+
+ const size_t STACK_DUMP_CHUNK_SIZE = 384;
+ for (size_t stack_off = 0; stack_off < stack_len_;
+ stack_off += STACK_DUMP_CHUNK_SIZE) {
+ LogAppend("S ");
+ LogAppend(stack_lower_bound_ + stack_off);
+ LogAppend(" ");
+ LogAppend(stack_copy_ + stack_off,
+ std::min(STACK_DUMP_CHUNK_SIZE, stack_len_ - stack_off));
+ LogCommitLine();
+ }
+ }
+
+ void DumpCPUState() {
+ RawContextCPU cpu;
+ my_memset(&cpu, 0, sizeof(RawContextCPU));
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ UContextReader::FillCPUContext(&cpu, ucontext_, float_state_);
+#else
+ UContextReader::FillCPUContext(&cpu, ucontext_);
+#endif
+ LogAppend("C ");
+ LogAppend(&cpu, sizeof(cpu));
+ LogCommitLine();
+ }
+
+ // If there is caller-provided information about this mapping
+ // in the mapping_list_ list, return true. Otherwise, return false.
+ bool HaveMappingInfo(const MappingInfo& mapping) {
+ for (MappingList::const_iterator iter = mapping_list_.begin();
+ iter != mapping_list_.end();
+ ++iter) {
+ // Ignore any mappings that are wholly contained within
+ // mappings in the mapping_info_ list.
+ if (mapping.start_addr >= iter->first.start_addr &&
+ (mapping.start_addr + mapping.size) <=
+ (iter->first.start_addr + iter->first.size)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Dump information about the provided |mapping|. If |identifier| is non-NULL,
+ // use it instead of calculating a file ID from the mapping.
+ void DumpModule(const MappingInfo& mapping,
+ bool member,
+ unsigned int mapping_id,
+ const std::vector<uint8_t>* identifier) {
+
+ auto_wasteful_vector<uint8_t, kDefaultBuildIdSize> identifier_bytes(
+ dumper_->allocator());
+
+ if (identifier) {
+ // GUID was provided by caller.
+ identifier_bytes.insert(identifier_bytes.end(),
+ identifier->begin(),
+ identifier->end());
+ } else {
+ dumper_->ElfFileIdentifierForMapping(
+ mapping,
+ member,
+ mapping_id,
+ identifier_bytes);
+ }
+
+ // Copy as many bytes of |identifier| as will fit into a MDGUID
+ MDGUID module_identifier = {0};
+ memcpy(&module_identifier, &identifier_bytes[0],
+ std::min(sizeof(MDGUID), identifier_bytes.size()));
+
+ char file_name[NAME_MAX];
+ char file_path[NAME_MAX];
+ dumper_->GetMappingEffectiveNameAndPath(
+ mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
+
+ LogAppend("M ");
+ LogAppend(static_cast<uintptr_t>(mapping.start_addr));
+ LogAppend(" ");
+ LogAppend(mapping.offset);
+ LogAppend(" ");
+ LogAppend(mapping.size);
+ LogAppend(" ");
+ LogAppend(module_identifier.data1);
+ LogAppend(module_identifier.data2);
+ LogAppend(module_identifier.data3);
+ LogAppend(module_identifier.data4[0]);
+ LogAppend(module_identifier.data4[1]);
+ LogAppend(module_identifier.data4[2]);
+ LogAppend(module_identifier.data4[3]);
+ LogAppend(module_identifier.data4[4]);
+ LogAppend(module_identifier.data4[5]);
+ LogAppend(module_identifier.data4[6]);
+ LogAppend(module_identifier.data4[7]);
+ LogAppend("0 "); // Age is always 0 on Linux.
+ LogAppend(file_name);
+ LogCommitLine();
+ }
+
+#if !defined(__LP64__)
+ void DumpFreeSpace() {
+ const MappingInfo* stack_mapping = nullptr;
+ ThreadInfo info;
+ if (dumper_->GetThreadInfoByIndex(dumper_->GetMainThreadIndex(), &info)) {
+ stack_mapping = dumper_->FindMappingNoBias(info.stack_pointer);
+ }
+
+ const google_breakpad::wasteful_vector<MappingInfo*>& mappings =
+ dumper_->mappings();
+ if (mappings.size() == 0) return;
+
+ // This is complicated by the fact that mappings is not in order. It should
+ // be mostly in order, however the mapping that contains the entry point for
+ // the process is always at the front of the vector.
+
+ static const int HBITS = sizeof(size_t) * 8;
+ size_t hole_histogram[HBITS];
+ my_memset(hole_histogram, 0, sizeof(hole_histogram));
+
+ // Find the lowest address mapping.
+ size_t curr = 0;
+ for (size_t i = 1; i < mappings.size(); ++i) {
+ if (mappings[i]->start_addr < mappings[curr]->start_addr) curr = i;
+ }
+
+ uintptr_t lo_addr = mappings[curr]->start_addr;
+
+ size_t hole_cnt = 0;
+ size_t hole_max = 0;
+ size_t hole_sum = 0;
+
+ while (true) {
+ // Skip to the end of an adjacent run of mappings. This is an optimization
+ // for the fact that mappings is mostly sorted.
+ while (curr != mappings.size() - 1 &&
+ MappingsAreAdjacent(*mappings[curr], *mappings[curr + 1])) {
+ ++curr;
+ }
+
+ if (mappings[curr] == stack_mapping) {
+ // Because we can't determine the top of userspace mappable
+ // memory we treat the start of the process stack as the top
+ // of the allocatable address space. Once we reach
+ // |stack_mapping| we are done scanning for free space regions.
+ break;
+ }
+
+ size_t next = NextOrderedMapping(mappings, curr);
+ if (next == std::numeric_limits<size_t>::max())
+ break;
+
+ uintptr_t hole_lo = mappings[curr]->start_addr + mappings[curr]->size;
+ uintptr_t hole_hi = mappings[next]->start_addr;
+
+ if (hole_hi > hole_lo) {
+ size_t hole_sz = hole_hi - hole_lo;
+ hole_sum += hole_sz;
+ hole_max = std::max(hole_sz, hole_max);
+ ++hole_cnt;
+ ++hole_histogram[Log2Floor(hole_sz)];
+ }
+ curr = next;
+ }
+
+ uintptr_t hi_addr = mappings[curr]->start_addr + mappings[curr]->size;
+
+ LogAppend("H ");
+ LogAppend(lo_addr);
+ LogAppend(" ");
+ LogAppend(hi_addr);
+ LogAppend(" ");
+ LogAppend(saturated_cast<uint16_t>(hole_cnt));
+ LogAppend(" ");
+ LogAppend(hole_max);
+ LogAppend(" ");
+ LogAppend(hole_sum);
+ for (unsigned int i = 0; i < HBITS; ++i) {
+ if (!hole_histogram[i]) continue;
+ LogAppend(" ");
+ LogAppend(saturated_cast<uint8_t>(i));
+ LogAppend(":");
+ LogAppend(saturated_cast<uint8_t>(hole_histogram[i]));
+ }
+ LogCommitLine();
+ }
+#endif
+
+ // Write information about the mappings in effect.
+ void DumpMappings() {
+ // First write all the mappings from the dumper
+ for (unsigned i = 0; i < dumper_->mappings().size(); ++i) {
+ const MappingInfo& mapping = *dumper_->mappings()[i];
+ if (mapping.name[0] == 0 || // only want modules with filenames.
+ !mapping.exec || // only want executable mappings.
+ mapping.size < 4096 || // too small to get a signature for.
+ HaveMappingInfo(mapping)) {
+ continue;
+ }
+
+ DumpModule(mapping, true, i, NULL);
+ }
+ // Next write all the mappings provided by the caller
+ for (MappingList::const_iterator iter = mapping_list_.begin();
+ iter != mapping_list_.end();
+ ++iter) {
+ DumpModule(iter->first, false, 0, &iter->second);
+ }
+ }
+
+ void* Alloc(unsigned bytes) { return dumper_->allocator()->Alloc(bytes); }
+
+ const ucontext_t* const ucontext_;
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ const google_breakpad::fpstate_t* const float_state_;
+#endif
+ LinuxDumper* dumper_;
+ const MappingList& mapping_list_;
+ bool skip_dump_if_principal_mapping_not_referenced_;
+ uintptr_t address_within_principal_mapping_;
+ bool sanitize_stack_;
+ const MicrodumpExtraInfo microdump_extra_info_;
+ char* log_line_;
+
+ // The local copy of crashed process stack memory, beginning at
+ // |stack_lower_bound_|.
+ uint8_t* stack_copy_;
+
+ // The length of crashed process stack copy.
+ size_t stack_len_;
+
+ // The address of the page containing the stack pointer in the
+ // crashed process. |stack_lower_bound_| <= |stack_pointer_|
+ uintptr_t stack_lower_bound_;
+
+ // The stack pointer of the crashed thread.
+ uintptr_t stack_pointer_;
+};
+} // namespace
+
+namespace google_breakpad {
+
+bool WriteMicrodump(pid_t crashing_process,
+ const void* blob,
+ size_t blob_size,
+ const MappingList& mappings,
+ bool skip_dump_if_principal_mapping_not_referenced,
+ uintptr_t address_within_principal_mapping,
+ bool sanitize_stack,
+ const MicrodumpExtraInfo& microdump_extra_info) {
+ LinuxPtraceDumper dumper(crashing_process);
+ const ExceptionHandler::CrashContext* context = NULL;
+ if (blob) {
+ if (blob_size != sizeof(ExceptionHandler::CrashContext))
+ return false;
+ context = reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
+ dumper.SetCrashInfoFromSigInfo(context->siginfo);
+ dumper.set_crash_thread(context->tid);
+ }
+ MicrodumpWriter writer(context, mappings,
+ skip_dump_if_principal_mapping_not_referenced,
+ address_within_principal_mapping, sanitize_stack,
+ microdump_extra_info, &dumper);
+ if (!writer.Init())
+ return false;
+ writer.Dump();
+ return true;
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.h b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.h
new file mode 100644
index 0000000000..63b5463247
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2014, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "linux/dump_writer_common/mapping_info.h"
+
+namespace google_breakpad {
+
+struct MicrodumpExtraInfo;
+
+// Writes a microdump (a reduced dump containing only the state of the crashing
+// thread) on the console (logcat on Android). These functions do not malloc nor
+// use libc functions which may. Thus, it can be used in contexts where the
+// state of the heap may be corrupt.
+// Args:
+// crashing_process: the pid of the crashing process. This must be trusted.
+// blob: a blob of data from the crashing process. See exception_handler.h
+// blob_size: the length of |blob| in bytes.
+// mappings: a list of additional mappings provided by the application.
+// build_fingerprint: a (optional) C string which determines the OS
+// build fingerprint (e.g., aosp/occam/mako:5.1.1/LMY47W/1234:eng/dev-keys).
+// product_info: a (optional) C string which determines the product name and
+// version (e.g., WebView:42.0.2311.136).
+//
+// Returns true iff successful.
+bool WriteMicrodump(pid_t crashing_process,
+ const void* blob,
+ size_t blob_size,
+ const MappingList& mappings,
+ bool skip_dump_if_main_module_not_referenced,
+ uintptr_t address_within_main_module,
+ bool sanitize_stack,
+ const MicrodumpExtraInfo& microdump_extra_info);
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer_unittest.cc
new file mode 100644
index 0000000000..6b7fb5f6ac
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer_unittest.cc
@@ -0,0 +1,421 @@
+// Copyright (c) 2014 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <ctype.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <ucontext.h>
+
+#include <sstream>
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "linux/handler/exception_handler.h"
+#include "linux/handler/microdump_extra_info.h"
+#include "linux/microdump_writer/microdump_writer.h"
+#include "common/linux/breakpad_getcontext.h"
+#include "common/linux/eintr_wrapper.h"
+#include "common/linux/ignore_ret.h"
+#include "common/scoped_ptr.h"
+#include "common/tests/auto_tempdir.h"
+#include "common/using_std_string.h"
+
+using namespace google_breakpad;
+
+extern "C" {
+extern char __executable_start;
+extern char __etext;
+}
+
+namespace {
+
+typedef testing::Test MicrodumpWriterTest;
+
+MicrodumpExtraInfo MakeMicrodumpExtraInfo(
+ const char* build_fingerprint,
+ const char* product_info,
+ const char* gpu_fingerprint) {
+ MicrodumpExtraInfo info;
+ info.build_fingerprint = build_fingerprint;
+ info.product_info = product_info;
+ info.gpu_fingerprint = gpu_fingerprint;
+ info.process_type = "Browser";
+ return info;
+}
+
+bool ContainsMicrodump(const std::string& buf) {
+ return std::string::npos != buf.find("-----BEGIN BREAKPAD MICRODUMP-----") &&
+ std::string::npos != buf.find("-----END BREAKPAD MICRODUMP-----");
+}
+
+const char kIdentifiableString[] = "_IDENTIFIABLE_";
+const uintptr_t kCrashAddress = 0xdeaddeadu;
+
+void CrashAndGetMicrodump(const MappingList& mappings,
+ const MicrodumpExtraInfo& microdump_extra_info,
+ std::string* microdump,
+ bool skip_dump_if_principal_mapping_not_referenced = false,
+ uintptr_t address_within_principal_mapping = 0,
+ bool sanitize_stack = false) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ AutoTempDir temp_dir;
+ string stderr_file = temp_dir.path() + "/stderr.log";
+ int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
+ ASSERT_NE(-1, err_fd);
+
+ char identifiable_string[sizeof(kIdentifiableString)];
+
+ // This string should not appear in the resulting microdump if it
+ // has been sanitized.
+ strcpy(identifiable_string, kIdentifiableString);
+ // Force the strcpy to not be optimized away.
+ IGNORE_RET(write(STDOUT_FILENO, identifiable_string, 0));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+ // Pretend the current context is the child context (which is
+ // approximately right) so that we have a valid stack pointer, and
+ // can fetch child stack data via ptrace.
+ getcontext(&context.context);
+ // Set a non-zero tid to avoid tripping asserts.
+ context.tid = child;
+ context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED;
+ context.siginfo.si_addr = reinterpret_cast<void*>(kCrashAddress);
+
+ // Redirect temporarily stderr to the stderr.log file.
+ int save_err = dup(STDERR_FILENO);
+ ASSERT_NE(-1, save_err);
+ ASSERT_NE(-1, dup2(err_fd, STDERR_FILENO));
+
+ ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings,
+ skip_dump_if_principal_mapping_not_referenced,
+ address_within_principal_mapping, sanitize_stack,
+ microdump_extra_info));
+
+ // Revert stderr back to the console.
+ dup2(save_err, STDERR_FILENO);
+ close(save_err);
+
+ // Read back the stderr file and check for the microdump marker.
+ fsync(err_fd);
+ lseek(err_fd, 0, SEEK_SET);
+
+ microdump->clear();
+ char buf[1024];
+
+ while (true) {
+ int bytes_read = IGNORE_EINTR(read(err_fd, buf, 1024));
+ if (bytes_read <= 0) break;
+ microdump->append(buf, buf + bytes_read);
+ }
+ close(err_fd);
+ close(fds[1]);
+}
+
+void ExtractMicrodumpStackContents(const string& microdump_content,
+ string* result) {
+ std::istringstream iss(microdump_content);
+ result->clear();
+ for (string line; std::getline(iss, line);) {
+ if (line.find("S ") == 0) {
+ std::istringstream stack_data(line);
+ std::string key;
+ std::string addr;
+ std::string data;
+ stack_data >> key >> addr >> data;
+ EXPECT_TRUE((data.size() & 1u) == 0u);
+ result->reserve(result->size() + data.size() / 2);
+ for (size_t i = 0; i < data.size(); i += 2) {
+ std::string byte = data.substr(i, 2);
+ result->push_back(static_cast<char>(strtoul(byte.c_str(), NULL, 16)));
+ }
+ }
+ }
+}
+
+void CheckMicrodumpContents(const string& microdump_content,
+ const MicrodumpExtraInfo& expected_info) {
+ std::istringstream iss(microdump_content);
+ bool did_find_os_info = false;
+ bool did_find_product_info = false;
+ bool did_find_process_type = false;
+ bool did_find_crash_reason = false;
+ bool did_find_gpu_info = false;
+ for (string line; std::getline(iss, line);) {
+ if (line.find("O ") == 0) {
+ std::istringstream os_info_tokens(line);
+ string token;
+ os_info_tokens.ignore(2); // Ignore the "O " preamble.
+ // Check the OS descriptor char (L=Linux, A=Android).
+ os_info_tokens >> token;
+ ASSERT_TRUE(token == "L" || token == "A");
+
+ os_info_tokens >> token; // HW architecture.
+ os_info_tokens >> token; // Number of cpus.
+ for (size_t i = 0; i < token.size(); ++i)
+ ASSERT_TRUE(isxdigit(token[i]));
+ os_info_tokens >> token; // SW architecture.
+
+ // Check that the build fingerprint is in the right place.
+ os_info_tokens >> token;
+ ASSERT_FALSE(os_info_tokens.fail());
+ if (expected_info.build_fingerprint)
+ ASSERT_EQ(expected_info.build_fingerprint, token);
+ did_find_os_info = true;
+ } else if (line.find("P ") == 0) {
+ if (expected_info.process_type)
+ ASSERT_EQ(string("P ") + expected_info.process_type, line);
+ did_find_process_type = true;
+ } else if (line.find("R ") == 0) {
+ std::istringstream crash_reason_tokens(line);
+ string token;
+ unsigned crash_reason;
+ string crash_reason_str;
+ uintptr_t crash_address;
+ crash_reason_tokens.ignore(2); // Ignore the "R " preamble.
+ crash_reason_tokens >> std::hex >> crash_reason >> crash_reason_str >>
+ crash_address;
+ ASSERT_FALSE(crash_reason_tokens.fail());
+ ASSERT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, crash_reason);
+ ASSERT_EQ("DUMP_REQUESTED", crash_reason_str);
+ ASSERT_EQ(kCrashAddress, crash_address);
+ did_find_crash_reason = true;
+ } else if (line.find("V ") == 0) {
+ if (expected_info.product_info)
+ ASSERT_EQ(string("V ") + expected_info.product_info, line);
+ did_find_product_info = true;
+ } else if (line.find("G ") == 0) {
+ if (expected_info.gpu_fingerprint)
+ ASSERT_EQ(string("G ") + expected_info.gpu_fingerprint, line);
+ did_find_gpu_info = true;
+ }
+ }
+ ASSERT_TRUE(did_find_os_info);
+ ASSERT_TRUE(did_find_product_info);
+ ASSERT_TRUE(did_find_process_type);
+ ASSERT_TRUE(did_find_crash_reason);
+ ASSERT_TRUE(did_find_gpu_info);
+}
+
+bool MicrodumpStackContains(const string& microdump_content,
+ const string& expected_content) {
+ string result;
+ ExtractMicrodumpStackContents(microdump_content, &result);
+ return result.find(kIdentifiableString) != string::npos;
+}
+
+void CheckMicrodumpContents(const string& microdump_content,
+ const string& expected_fingerprint,
+ const string& expected_product_info,
+ const string& expected_gpu_fingerprint) {
+ CheckMicrodumpContents(
+ microdump_content,
+ MakeMicrodumpExtraInfo(expected_fingerprint.c_str(),
+ expected_product_info.c_str(),
+ expected_gpu_fingerprint.c_str()));
+}
+
+TEST(MicrodumpWriterTest, BasicWithMappings) {
+ // Push some extra mapping to check the MappingList logic.
+ const uint32_t memory_size = sysconf(_SC_PAGESIZE);
+ const char* kMemoryName = "libfoo.so";
+ const uint8_t kModuleGUID[sizeof(MDGUID)] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+ };
+
+ MappingInfo info;
+ info.start_addr = memory_size;
+ info.size = memory_size;
+ info.offset = 42;
+ strcpy(info.name, kMemoryName);
+
+ MappingList mappings;
+ MappingEntry mapping;
+ mapping.first = info;
+ mapping.second.assign(std::begin(kModuleGUID), std::end(kModuleGUID));
+ mappings.push_back(mapping);
+
+ std::string buf;
+ CrashAndGetMicrodump(mappings, MicrodumpExtraInfo(), &buf);
+ ASSERT_TRUE(ContainsMicrodump(buf));
+
+#ifdef __LP64__
+ ASSERT_NE(std::string::npos,
+ buf.find("M 0000000000001000 000000000000002A 0000000000001000 "
+ "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
+#else
+ ASSERT_NE(std::string::npos,
+ buf.find("M 00001000 0000002A 00001000 "
+ "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
+#endif
+
+ // In absence of a product info in the minidump, the writer should just write
+ // an unknown marker.
+ ASSERT_NE(std::string::npos, buf.find("V UNKNOWN:0.0.0.0"));
+}
+
+// Ensure that no output occurs if the interest region is set, but
+// doesn't overlap anything on the stack.
+TEST(MicrodumpWriterTest, NoOutputIfUninteresting) {
+ const char kProductInfo[] = "MockProduct:42.0.2311.99";
+ const char kBuildFingerprint[] =
+ "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
+ const char kGPUFingerprint[] =
+ "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
+ const MicrodumpExtraInfo kMicrodumpExtraInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
+
+ std::string buf;
+ MappingList no_mappings;
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, true, 0);
+ ASSERT_FALSE(ContainsMicrodump(buf));
+}
+
+// Ensure that stack content does not contain an identifiable string if the
+// stack is sanitized.
+TEST(MicrodumpWriterTest, StringRemovedBySanitization) {
+ const char kProductInfo[] = "MockProduct:42.0.2311.99";
+ const char kBuildFingerprint[] =
+ "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
+ const char kGPUFingerprint[] =
+ "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
+
+ const MicrodumpExtraInfo kMicrodumpExtraInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
+
+ std::string buf;
+ MappingList no_mappings;
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, true);
+ ASSERT_TRUE(ContainsMicrodump(buf));
+ ASSERT_FALSE(MicrodumpStackContains(buf, kIdentifiableString));
+}
+
+// Ensure that stack content does contain an identifiable string if the
+// stack is not sanitized.
+TEST(MicrodumpWriterTest, StringPresentIfNotSanitized) {
+ const char kProductInfo[] = "MockProduct:42.0.2311.99";
+ const char kBuildFingerprint[] =
+ "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
+ const char kGPUFingerprint[] =
+ "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
+
+ const MicrodumpExtraInfo kMicrodumpExtraInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
+
+ std::string buf;
+ MappingList no_mappings;
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, false);
+ ASSERT_TRUE(ContainsMicrodump(buf));
+ ASSERT_TRUE(MicrodumpStackContains(buf, kIdentifiableString));
+}
+
+// Ensure that output occurs if the interest region is set, and
+// does overlap something on the stack.
+TEST(MicrodumpWriterTest, OutputIfInteresting) {
+ const char kProductInfo[] = "MockProduct:42.0.2311.99";
+ const char kBuildFingerprint[] =
+ "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
+ const char kGPUFingerprint[] =
+ "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
+
+ const MicrodumpExtraInfo kMicrodumpExtraInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
+
+ std::string buf;
+ MappingList no_mappings;
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, true,
+ reinterpret_cast<uintptr_t>(CrashAndGetMicrodump));
+ ASSERT_TRUE(ContainsMicrodump(buf));
+}
+
+// Ensure that the product info and build fingerprint metadata show up in the
+// final microdump if present.
+TEST(MicrodumpWriterTest, BuildFingerprintAndProductInfo) {
+ const char kProductInfo[] = "MockProduct:42.0.2311.99";
+ const char kBuildFingerprint[] =
+ "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
+ const char kGPUFingerprint[] =
+ "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
+ const MicrodumpExtraInfo kMicrodumpExtraInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
+ std::string buf;
+ MappingList no_mappings;
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf);
+ ASSERT_TRUE(ContainsMicrodump(buf));
+ CheckMicrodumpContents(buf, kMicrodumpExtraInfo);
+}
+
+TEST(MicrodumpWriterTest, NoProductInfo) {
+ const char kBuildFingerprint[] = "foobar";
+ const char kGPUFingerprint[] = "bazqux";
+ std::string buf;
+ MappingList no_mappings;
+
+ const MicrodumpExtraInfo kMicrodumpExtraInfoNoProductInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, NULL, kGPUFingerprint));
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoProductInfo, &buf);
+ ASSERT_TRUE(ContainsMicrodump(buf));
+ CheckMicrodumpContents(buf, kBuildFingerprint, "UNKNOWN:0.0.0.0",
+ kGPUFingerprint);
+}
+
+TEST(MicrodumpWriterTest, NoGPUInfo) {
+ const char kProductInfo[] = "bazqux";
+ const char kBuildFingerprint[] = "foobar";
+ std::string buf;
+ MappingList no_mappings;
+
+ const MicrodumpExtraInfo kMicrodumpExtraInfoNoGPUInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, NULL));
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoGPUInfo, &buf);
+ ASSERT_TRUE(ContainsMicrodump(buf));
+ CheckMicrodumpContents(buf, kBuildFingerprint, kProductInfo, "UNKNOWN");
+}
+} // namespace
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set.h
new file mode 100644
index 0000000000..1cca9aa5a0
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
+
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include "common/linux/linux_libc_support.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// Helper class used to model a set of CPUs, as read from sysfs
+// files like /sys/devices/system/cpu/present
+// See See http://www.kernel.org/doc/Documentation/cputopology.txt
+class CpuSet {
+public:
+ // The maximum number of supported CPUs.
+ static const size_t kMaxCpus = 1024;
+
+ CpuSet() {
+ my_memset(mask_, 0, sizeof(mask_));
+ }
+
+ // Parse a sysfs file to extract the corresponding CPU set.
+ bool ParseSysFile(int fd) {
+ char buffer[512];
+ int ret = sys_read(fd, buffer, sizeof(buffer)-1);
+ if (ret < 0)
+ return false;
+
+ buffer[ret] = '\0';
+
+ // Expected format: comma-separated list of items, where each
+ // item can be a decimal integer, or two decimal integers separated
+ // by a dash.
+ // E.g.:
+ // 0
+ // 0,1,2,3
+ // 0-3
+ // 1,10-23
+ const char* p = buffer;
+ const char* p_end = p + ret;
+ while (p < p_end) {
+ // Skip leading space, if any
+ while (p < p_end && my_isspace(*p))
+ p++;
+
+ // Find start and size of current item.
+ const char* item = p;
+ size_t item_len = static_cast<size_t>(p_end - p);
+ const char* item_next =
+ static_cast<const char*>(my_memchr(p, ',', item_len));
+ if (item_next != NULL) {
+ p = item_next + 1;
+ item_len = static_cast<size_t>(item_next - item);
+ } else {
+ p = p_end;
+ item_next = p_end;
+ }
+
+ // Ignore trailing spaces.
+ while (item_next > item && my_isspace(item_next[-1]))
+ item_next--;
+
+ // skip empty items.
+ if (item_next == item)
+ continue;
+
+ // read first decimal value.
+ uintptr_t start = 0;
+ const char* next = my_read_decimal_ptr(&start, item);
+ uintptr_t end = start;
+ if (*next == '-')
+ my_read_decimal_ptr(&end, next+1);
+
+ while (start <= end)
+ SetBit(start++);
+ }
+ return true;
+ }
+
+ // Intersect this CPU set with another one.
+ void IntersectWith(const CpuSet& other) {
+ for (size_t nn = 0; nn < kMaskWordCount; ++nn)
+ mask_[nn] &= other.mask_[nn];
+ }
+
+ // Return the number of CPUs in this set.
+ int GetCount() {
+ int result = 0;
+ for (size_t nn = 0; nn < kMaskWordCount; ++nn) {
+ result += __builtin_popcount(mask_[nn]);
+ }
+ return result;
+ }
+
+private:
+ void SetBit(uintptr_t index) {
+ size_t nn = static_cast<size_t>(index);
+ if (nn < kMaxCpus)
+ mask_[nn / kMaskWordBits] |= (1U << (nn % kMaskWordBits));
+ }
+
+ typedef uint32_t MaskWordType;
+ static const size_t kMaskWordBits = 8*sizeof(MaskWordType);
+ static const size_t kMaskWordCount =
+ (kMaxCpus + kMaskWordBits - 1) / kMaskWordBits;
+
+ MaskWordType mask_[kMaskWordCount];
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set_unittest.cc
new file mode 100644
index 0000000000..75172e9938
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set_unittest.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "breakpad_googletest_includes.h"
+#include "linux/minidump_writer/cpu_set.h"
+#include "common/linux/tests/auto_testfile.h"
+
+using namespace google_breakpad;
+
+namespace {
+
+typedef testing::Test CpuSetTest;
+
+// Helper class to write test text file to a temporary file and return
+// its file descriptor.
+class ScopedTestFile : public AutoTestFile {
+public:
+ explicit ScopedTestFile(const char* text)
+ : AutoTestFile("cpu_set", text) {
+ }
+};
+
+}
+
+TEST(CpuSetTest, EmptyCount) {
+ CpuSet set;
+ ASSERT_EQ(0, set.GetCount());
+}
+
+TEST(CpuSetTest, OneCpu) {
+ ScopedTestFile file("10");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(1, set.GetCount());
+}
+
+TEST(CpuSetTest, OneCpuTerminated) {
+ ScopedTestFile file("10\n");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(1, set.GetCount());
+}
+
+TEST(CpuSetTest, TwoCpusWithComma) {
+ ScopedTestFile file("1,10");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(2, set.GetCount());
+}
+
+TEST(CpuSetTest, TwoCpusWithRange) {
+ ScopedTestFile file("1-2");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(2, set.GetCount());
+}
+
+TEST(CpuSetTest, TenCpusWithRange) {
+ ScopedTestFile file("9-18");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(10, set.GetCount());
+}
+
+TEST(CpuSetTest, MultiItems) {
+ ScopedTestFile file("0, 2-4, 128");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(5, set.GetCount());
+}
+
+TEST(CpuSetTest, IntersectWith) {
+ ScopedTestFile file1("9-19");
+ ASSERT_TRUE(file1.IsOk());
+ CpuSet set1;
+ ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
+ ASSERT_EQ(11, set1.GetCount());
+
+ ScopedTestFile file2("16-24");
+ ASSERT_TRUE(file2.IsOk());
+ CpuSet set2;
+ ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
+ ASSERT_EQ(9, set2.GetCount());
+
+ set1.IntersectWith(set2);
+ ASSERT_EQ(4, set1.GetCount());
+ ASSERT_EQ(9, set2.GetCount());
+}
+
+TEST(CpuSetTest, SelfIntersection) {
+ ScopedTestFile file1("9-19");
+ ASSERT_TRUE(file1.IsOk());
+ CpuSet set1;
+ ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
+ ASSERT_EQ(11, set1.GetCount());
+
+ set1.IntersectWith(set1);
+ ASSERT_EQ(11, set1.GetCount());
+}
+
+TEST(CpuSetTest, EmptyIntersection) {
+ ScopedTestFile file1("0-19");
+ ASSERT_TRUE(file1.IsOk());
+ CpuSet set1;
+ ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
+ ASSERT_EQ(20, set1.GetCount());
+
+ ScopedTestFile file2("20-39");
+ ASSERT_TRUE(file2.IsOk());
+ CpuSet set2;
+ ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
+ ASSERT_EQ(20, set2.GetCount());
+
+ set1.IntersectWith(set2);
+ ASSERT_EQ(0, set1.GetCount());
+
+ ASSERT_EQ(20, set2.GetCount());
+}
+
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader.h
new file mode 100644
index 0000000000..a4bde18031
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+#include "common/linux/linux_libc_support.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// A class for enumerating a directory without using diropen/readdir or other
+// functions which may allocate memory.
+class DirectoryReader {
+ public:
+ DirectoryReader(int fd)
+ : fd_(fd),
+ buf_used_(0) {
+ }
+
+ // Return the next entry from the directory
+ // name: (output) the NUL terminated entry name
+ //
+ // Returns true iff successful (false on EOF).
+ //
+ // After calling this, one must call |PopEntry| otherwise you'll get the same
+ // entry over and over.
+ bool GetNextEntry(const char** name) {
+ struct kernel_dirent* const dent =
+ reinterpret_cast<kernel_dirent*>(buf_);
+
+ if (buf_used_ == 0) {
+ // need to read more entries.
+ const int n = sys_getdents(fd_, dent, sizeof(buf_));
+ if (n < 0) {
+ return false;
+ } else if (n == 0) {
+ hit_eof_ = true;
+ } else {
+ buf_used_ += n;
+ }
+ }
+
+ if (buf_used_ == 0 && hit_eof_)
+ return false;
+
+ assert(buf_used_ > 0);
+
+ *name = dent->d_name;
+ return true;
+ }
+
+ void PopEntry() {
+ if (!buf_used_)
+ return;
+
+ const struct kernel_dirent* const dent =
+ reinterpret_cast<kernel_dirent*>(buf_);
+
+ buf_used_ -= dent->d_reclen;
+ my_memmove(buf_, buf_ + dent->d_reclen, buf_used_);
+ }
+
+ private:
+ const int fd_;
+ bool hit_eof_;
+ unsigned buf_used_;
+ uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1];
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader_unittest.cc
new file mode 100644
index 0000000000..b33507db95
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <set>
+#include <string>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include "linux/minidump_writer/directory_reader.h"
+#include "common/using_std_string.h"
+#include "breakpad_googletest_includes.h"
+
+using namespace google_breakpad;
+
+namespace {
+typedef testing::Test DirectoryReaderTest;
+}
+
+TEST(DirectoryReaderTest, CompareResults) {
+ std::set<string> dent_set;
+
+ DIR *const dir = opendir("/proc/self");
+ ASSERT_TRUE(dir != NULL);
+
+ struct dirent* dent;
+ while ((dent = readdir(dir)))
+ dent_set.insert(dent->d_name);
+
+ closedir(dir);
+
+ const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY);
+ ASSERT_GE(fd, 0);
+
+ DirectoryReader dir_reader(fd);
+ unsigned seen = 0;
+
+ const char* name;
+ while (dir_reader.GetNextEntry(&name)) {
+ ASSERT_TRUE(dent_set.find(name) != dent_set.end());
+ seen++;
+ dir_reader.PopEntry();
+ }
+
+ ASSERT_TRUE(dent_set.find("status") != dent_set.end());
+ ASSERT_TRUE(dent_set.find("stat") != dent_set.end());
+ ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end());
+
+ ASSERT_EQ(dent_set.size(), seen);
+ close(fd);
+}
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader.h
new file mode 100644
index 0000000000..d8e2dbcc11
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
+
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include "common/linux/linux_libc_support.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// A class for reading a file, line by line, without using fopen/fgets or other
+// functions which may allocate memory.
+class LineReader {
+ public:
+ LineReader(int fd)
+ : fd_(fd),
+ hit_eof_(false),
+ buf_used_(0) {
+ }
+
+ // The maximum length of a line.
+ static const size_t kMaxLineLen = 1024;
+
+ // Return the next line from the file.
+ // line: (output) a pointer to the start of the line. The line is NUL
+ // terminated.
+ // len: (output) the length of the line (not inc the NUL byte)
+ //
+ // Returns true iff successful (false on EOF).
+ //
+ // One must call |PopLine| after this function, otherwise you'll continue to
+ // get the same line over and over.
+ bool GetNextLine(const char **line, unsigned *len) {
+ for (;;) {
+ if (buf_used_ == 0 && hit_eof_)
+ return false;
+
+ for (unsigned i = 0; i < buf_used_; ++i) {
+ if (buf_[i] == '\n' || buf_[i] == 0) {
+ buf_[i] = 0;
+ *len = i;
+ *line = buf_;
+ return true;
+ }
+ }
+
+ if (buf_used_ == sizeof(buf_)) {
+ // we scanned the whole buffer and didn't find an end-of-line marker.
+ // This line is too long to process.
+ return false;
+ }
+
+ // We didn't find any end-of-line terminators in the buffer. However, if
+ // this is the last line in the file it might not have one:
+ if (hit_eof_) {
+ assert(buf_used_);
+ // There's room for the NUL because of the buf_used_ == sizeof(buf_)
+ // check above.
+ buf_[buf_used_] = 0;
+ *len = buf_used_;
+ buf_used_ += 1; // since we appended the NUL.
+ *line = buf_;
+ return true;
+ }
+
+ // Otherwise, we should pull in more data from the file
+ const ssize_t n = sys_read(fd_, buf_ + buf_used_,
+ sizeof(buf_) - buf_used_);
+ if (n < 0) {
+ return false;
+ } else if (n == 0) {
+ hit_eof_ = true;
+ } else {
+ buf_used_ += n;
+ }
+
+ // At this point, we have either set the hit_eof_ flag, or we have more
+ // data to process...
+ }
+ }
+
+ void PopLine(unsigned len) {
+ // len doesn't include the NUL byte at the end.
+
+ assert(buf_used_ >= len + 1);
+ buf_used_ -= len + 1;
+ my_memmove(buf_, buf_ + len + 1, buf_used_);
+ }
+
+ private:
+ const int fd_;
+
+ bool hit_eof_;
+ unsigned buf_used_;
+ char buf_[kMaxLineLen];
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader_unittest.cc
new file mode 100644
index 0000000000..576b8b5cdb
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "linux/minidump_writer/line_reader.h"
+#include "breakpad_googletest_includes.h"
+#include "common/linux/tests/auto_testfile.h"
+
+using namespace google_breakpad;
+
+namespace {
+
+typedef testing::Test LineReaderTest;
+
+class ScopedTestFile : public AutoTestFile {
+public:
+ explicit ScopedTestFile(const char* text)
+ : AutoTestFile("line_reader", text) {
+ }
+
+ ScopedTestFile(const char* text, size_t text_len)
+ : AutoTestFile("line_reader", text, text_len) {
+ }
+};
+
+}
+
+TEST(LineReaderTest, EmptyFile) {
+ ScopedTestFile file("");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
+
+ const char *line;
+ unsigned len;
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+}
+
+TEST(LineReaderTest, OneLineTerminated) {
+ ScopedTestFile file("a\n");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
+
+ const char *line;
+ unsigned int len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ((unsigned int)1, len);
+ ASSERT_EQ('a', line[0]);
+ ASSERT_EQ('\0', line[1]);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+}
+
+TEST(LineReaderTest, OneLine) {
+ ScopedTestFile file("a");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ((unsigned)1, len);
+ ASSERT_EQ('a', line[0]);
+ ASSERT_EQ('\0', line[1]);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+}
+
+TEST(LineReaderTest, TwoLinesTerminated) {
+ ScopedTestFile file("a\nb\n");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ((unsigned)1, len);
+ ASSERT_EQ('a', line[0]);
+ ASSERT_EQ('\0', line[1]);
+ reader.PopLine(len);
+
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ((unsigned)1, len);
+ ASSERT_EQ('b', line[0]);
+ ASSERT_EQ('\0', line[1]);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+}
+
+TEST(LineReaderTest, TwoLines) {
+ ScopedTestFile file("a\nb");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ((unsigned)1, len);
+ ASSERT_EQ('a', line[0]);
+ ASSERT_EQ('\0', line[1]);
+ reader.PopLine(len);
+
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ((unsigned)1, len);
+ ASSERT_EQ('b', line[0]);
+ ASSERT_EQ('\0', line[1]);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+}
+
+TEST(LineReaderTest, MaxLength) {
+ char l[LineReader::kMaxLineLen-1];
+ memset(l, 'a', sizeof(l));
+ ScopedTestFile file(l, sizeof(l));
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(sizeof(l), len);
+ ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
+ ASSERT_EQ('\0', line[len]);
+}
+
+TEST(LineReaderTest, TooLong) {
+ // Note: this writes kMaxLineLen 'a' chars in the test file.
+ char l[LineReader::kMaxLineLen];
+ memset(l, 'a', sizeof(l));
+ ScopedTestFile file(l, sizeof(l));
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
+
+ const char *line;
+ unsigned len;
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+}
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.cc
new file mode 100644
index 0000000000..38feb0aaa7
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.cc
@@ -0,0 +1,308 @@
+// Copyright (c) 2012, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// linux_core_dumper.cc: Implement google_breakpad::LinuxCoreDumper.
+// See linux_core_dumper.h for details.
+
+#include "linux/minidump_writer/linux_core_dumper.h"
+
+#include <asm/ptrace.h>
+#include <assert.h>
+#include <elf.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/procfs.h>
+#if defined(__mips__) && defined(__ANDROID__)
+// To get register definitions.
+#include <asm/reg.h>
+#endif
+
+#include "common/linux/elf_gnu_compat.h"
+#include "common/linux/linux_libc_support.h"
+
+namespace google_breakpad {
+
+LinuxCoreDumper::LinuxCoreDumper(pid_t pid,
+ const char* core_path,
+ const char* procfs_path,
+ const char* root_prefix)
+ : LinuxDumper(pid, root_prefix),
+ core_path_(core_path),
+ procfs_path_(procfs_path),
+ thread_infos_(&allocator_, 8) {
+ assert(core_path_);
+}
+
+bool LinuxCoreDumper::BuildProcPath(char* path, pid_t pid,
+ const char* node) const {
+ if (!path || !node)
+ return false;
+
+ size_t node_len = my_strlen(node);
+ if (node_len == 0)
+ return false;
+
+ size_t procfs_path_len = my_strlen(procfs_path_);
+ size_t total_length = procfs_path_len + 1 + node_len;
+ if (total_length >= NAME_MAX)
+ return false;
+
+ memcpy(path, procfs_path_, procfs_path_len);
+ path[procfs_path_len] = '/';
+ memcpy(path + procfs_path_len + 1, node, node_len);
+ path[total_length] = '\0';
+ return true;
+}
+
+bool LinuxCoreDumper::CopyFromProcess(void* dest, pid_t child,
+ const void* src, size_t length) {
+ ElfCoreDump::Addr virtual_address = reinterpret_cast<ElfCoreDump::Addr>(src);
+ // TODO(benchan): Investigate whether the data to be copied could span
+ // across multiple segments in the core dump file. ElfCoreDump::CopyData
+ // and this method do not handle that case yet.
+ if (!core_.CopyData(dest, virtual_address, length)) {
+ // If the data segment is not found in the core dump, fill the result
+ // with marker characters.
+ memset(dest, 0xab, length);
+ return false;
+ }
+ return true;
+}
+
+bool LinuxCoreDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
+ if (index >= thread_infos_.size())
+ return false;
+
+ *info = thread_infos_[index];
+ const uint8_t* stack_pointer;
+#if defined(__i386)
+ memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
+#elif defined(__x86_64)
+ memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
+#elif defined(__ARM_EABI__)
+ memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
+#elif defined(__aarch64__)
+ memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp));
+#elif defined(__mips__)
+ stack_pointer =
+ reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]);
+#else
+#error "This code hasn't been ported to your platform yet."
+#endif
+ info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer);
+ return true;
+}
+
+bool LinuxCoreDumper::GetThreadNameByIndex(size_t index, char* name,
+ size_t size) {
+ // Not implemented
+ return false;
+}
+
+bool LinuxCoreDumper::IsPostMortem() const {
+ return true;
+}
+
+bool LinuxCoreDumper::ThreadsSuspend() {
+ return true;
+}
+
+bool LinuxCoreDumper::ThreadsResume() {
+ return true;
+}
+
+bool LinuxCoreDumper::EnumerateThreads() {
+ if (!mapped_core_file_.Map(core_path_, 0)) {
+ fprintf(stderr, "Could not map core dump file into memory\n");
+ return false;
+ }
+
+ core_.SetContent(mapped_core_file_.content());
+ if (!core_.IsValid()) {
+ fprintf(stderr, "Invalid core dump file\n");
+ return false;
+ }
+
+ ElfCoreDump::Note note = core_.GetFirstNote();
+ if (!note.IsValid()) {
+ fprintf(stderr, "PT_NOTE section not found\n");
+ return false;
+ }
+
+ bool first_thread = true;
+ do {
+ ElfCoreDump::Word type = note.GetType();
+ MemoryRange name = note.GetName();
+ MemoryRange description = note.GetDescription();
+
+ if (type == 0 || name.IsEmpty() || description.IsEmpty()) {
+ fprintf(stderr, "Could not found a valid PT_NOTE.\n");
+ return false;
+ }
+
+ // Based on write_note_info() in linux/kernel/fs/binfmt_elf.c, notes are
+ // ordered as follows (NT_PRXFPREG and NT_386_TLS are i386 specific):
+ // Thread Name Type
+ // -------------------------------------------------------------------
+ // 1st thread CORE NT_PRSTATUS
+ // process-wide CORE NT_PRPSINFO
+ // process-wide CORE NT_SIGINFO
+ // process-wide CORE NT_AUXV
+ // 1st thread CORE NT_FPREGSET
+ // 1st thread LINUX NT_PRXFPREG
+ // 1st thread LINUX NT_386_TLS
+ //
+ // 2nd thread CORE NT_PRSTATUS
+ // 2nd thread CORE NT_FPREGSET
+ // 2nd thread LINUX NT_PRXFPREG
+ // 2nd thread LINUX NT_386_TLS
+ //
+ // 3rd thread CORE NT_PRSTATUS
+ // 3rd thread CORE NT_FPREGSET
+ // 3rd thread LINUX NT_PRXFPREG
+ // 3rd thread LINUX NT_386_TLS
+ //
+ // The following code only works if notes are ordered as expected.
+ switch (type) {
+ case NT_PRSTATUS: {
+ if (description.length() != sizeof(elf_prstatus)) {
+ fprintf(stderr, "Found NT_PRSTATUS descriptor of unexpected size\n");
+ return false;
+ }
+
+ const elf_prstatus* status =
+ reinterpret_cast<const elf_prstatus*>(description.data());
+ pid_t pid = status->pr_pid;
+ ThreadInfo info;
+ memset(&info, 0, sizeof(ThreadInfo));
+ info.tgid = status->pr_pgrp;
+ info.ppid = status->pr_ppid;
+#if defined(__mips__)
+#if defined(__ANDROID__)
+ for (int i = EF_R0; i <= EF_R31; i++)
+ info.mcontext.gregs[i - EF_R0] = status->pr_reg[i];
+#else // __ANDROID__
+ for (int i = EF_REG0; i <= EF_REG31; i++)
+ info.mcontext.gregs[i - EF_REG0] = status->pr_reg[i];
+#endif // __ANDROID__
+ info.mcontext.mdlo = status->pr_reg[EF_LO];
+ info.mcontext.mdhi = status->pr_reg[EF_HI];
+ info.mcontext.pc = status->pr_reg[EF_CP0_EPC];
+#else // __mips__
+ memcpy(&info.regs, status->pr_reg, sizeof(info.regs));
+#endif // __mips__
+ if (first_thread) {
+ crash_thread_ = pid;
+ crash_signal_ = status->pr_info.si_signo;
+ crash_signal_code_ = status->pr_info.si_code;
+ }
+ first_thread = false;
+ threads_.push_back(pid);
+ thread_infos_.push_back(info);
+ break;
+ }
+ case NT_SIGINFO: {
+ if (description.length() != sizeof(siginfo_t)) {
+ fprintf(stderr, "Found NT_SIGINFO descriptor of unexpected size\n");
+ return false;
+ }
+
+ const siginfo_t* info =
+ reinterpret_cast<const siginfo_t*>(description.data());
+
+ // Set crash_address when si_addr is valid for the signal.
+ switch (info->si_signo) {
+ case MD_EXCEPTION_CODE_LIN_SIGBUS:
+ case MD_EXCEPTION_CODE_LIN_SIGFPE:
+ case MD_EXCEPTION_CODE_LIN_SIGILL:
+ case MD_EXCEPTION_CODE_LIN_SIGSEGV:
+ case MD_EXCEPTION_CODE_LIN_SIGSYS:
+ case MD_EXCEPTION_CODE_LIN_SIGTRAP:
+ crash_address_ = reinterpret_cast<uintptr_t>(info->si_addr);
+ break;
+ }
+
+ // Set crash_exception_info for common signals. Since exception info is
+ // unsigned, but some of these fields might be signed, we always cast.
+ switch (info->si_signo) {
+ case MD_EXCEPTION_CODE_LIN_SIGKILL:
+ set_crash_exception_info({
+ static_cast<uint64_t>(info->si_pid),
+ static_cast<uint64_t>(info->si_uid),
+ });
+ break;
+ case MD_EXCEPTION_CODE_LIN_SIGSYS:
+#ifdef si_syscall
+ set_crash_exception_info({
+ static_cast<uint64_t>(info->si_syscall),
+ static_cast<uint64_t>(info->si_arch),
+ });
+#endif
+ break;
+ }
+ break;
+ }
+#if defined(__i386) || defined(__x86_64)
+ case NT_FPREGSET: {
+ if (thread_infos_.empty())
+ return false;
+
+ ThreadInfo* info = &thread_infos_.back();
+ if (description.length() != sizeof(info->fpregs)) {
+ fprintf(stderr, "Found NT_FPREGSET descriptor of unexpected size\n");
+ return false;
+ }
+
+ memcpy(&info->fpregs, description.data(), sizeof(info->fpregs));
+ break;
+ }
+#endif
+#if defined(__i386)
+ case NT_PRXFPREG: {
+ if (thread_infos_.empty())
+ return false;
+
+ ThreadInfo* info = &thread_infos_.back();
+ if (description.length() != sizeof(info->fpxregs)) {
+ fprintf(stderr, "Found NT_PRXFPREG descriptor of unexpected size\n");
+ return false;
+ }
+
+ memcpy(&info->fpxregs, description.data(), sizeof(info->fpxregs));
+ break;
+ }
+#endif
+ }
+ note = note.GetNextNote();
+ } while (note.IsValid());
+
+ return true;
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.h
new file mode 100644
index 0000000000..7805854a33
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// linux_core_dumper.h: Define the google_breakpad::LinuxCoreDumper
+// class, which is derived from google_breakpad::LinuxDumper to extract
+// information from a crashed process via its core dump and proc files.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_
+
+#include "linux/minidump_writer/linux_dumper.h"
+#include "common/linux/elf_core_dump.h"
+#include "common/linux/memory_mapped_file.h"
+
+namespace google_breakpad {
+
+class LinuxCoreDumper : public LinuxDumper {
+ public:
+ // Constructs a dumper for extracting information of a given process
+ // with a process ID of |pid| via its core dump file at |core_path| and
+ // its proc files at |procfs_path|. If |procfs_path| is a copy of
+ // /proc/<pid>, it should contain the following files:
+ // auxv, cmdline, environ, exe, maps, status
+ // See LinuxDumper for the purpose of |root_prefix|.
+ LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path,
+ const char* root_prefix = "");
+
+ // Implements LinuxDumper::BuildProcPath().
+ // Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
+ // |path| is a character array of at least NAME_MAX bytes to return the
+ // result.|node| is the final node without any slashes. Return true on
+ // success.
+ //
+ // As this dumper performs a post-mortem dump and makes use of a copy
+ // of the proc files of the crashed process, this derived method does
+ // not actually make use of |pid| and always returns a subpath of
+ // |procfs_path_| regardless of whether |pid| corresponds to the main
+ // process or a thread of the process, i.e. assuming both the main process
+ // and its threads have the following proc files with the same content:
+ // auxv, cmdline, environ, exe, maps, status
+ virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const;
+
+ // Implements LinuxDumper::CopyFromProcess().
+ // Copies content of |length| bytes from a given process |child|,
+ // starting from |src|, into |dest|. This method extracts the content
+ // the core dump and fills |dest| with a sequence of marker bytes
+ // if the expected data is not found in the core dump. Returns true if
+ // the expected data is found in the core dump.
+ virtual bool CopyFromProcess(void* dest, pid_t child, const void* src,
+ size_t length);
+
+ // Implements LinuxDumper::GetThreadInfoByIndex().
+ // Reads information about the |index|-th thread of |threads_|.
+ // Returns true on success. One must have called |ThreadsSuspend| first.
+ virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info);
+
+ // Implements LinuxDumper::GetThreadNameByIndex().
+ // Reads the name of the |index|-th thread of |threads_|.
+ // Returns true on success. One must have called |ThreadsSuspend| first.
+ virtual bool GetThreadNameByIndex(size_t index, char* info, size_t size);
+
+ // Implements LinuxDumper::IsPostMortem().
+ // Always returns true to indicate that this dumper performs a
+ // post-mortem dump of a crashed process via a core dump file.
+ virtual bool IsPostMortem() const;
+
+ // Implements LinuxDumper::ThreadsSuspend().
+ // As the dumper performs a post-mortem dump via a core dump file,
+ // there is no threads to suspend. This method does nothing and
+ // always returns true.
+ virtual bool ThreadsSuspend();
+
+ // Implements LinuxDumper::ThreadsResume().
+ // As the dumper performs a post-mortem dump via a core dump file,
+ // there is no threads to resume. This method does nothing and
+ // always returns true.
+ virtual bool ThreadsResume();
+
+ protected:
+ // Implements LinuxDumper::EnumerateThreads().
+ // Enumerates all threads of the given process into |threads_|.
+ virtual bool EnumerateThreads();
+
+ private:
+ // Path of the core dump file.
+ const char* core_path_;
+
+ // Path of the directory containing the proc files of the given process,
+ // which is usually a copy of /proc/<pid>.
+ const char* procfs_path_;
+
+ // Memory-mapped core dump file at |core_path_|.
+ MemoryMappedFile mapped_core_file_;
+
+ // Content of the core dump file.
+ ElfCoreDump core_;
+
+ // Thread info found in the core dump file.
+ wasteful_vector<ThreadInfo> thread_infos_;
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_LINUX_CORE_DUMPER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper_unittest.cc
new file mode 100644
index 0000000000..2e973167fd
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper_unittest.cc
@@ -0,0 +1,192 @@
+// Copyright (c) 2012, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// linux_core_dumper_unittest.cc:
+// Unit tests for google_breakpad::LinuxCoreDumoer.
+
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "linux/minidump_writer/linux_core_dumper.h"
+#include "common/linux/tests/crash_generator.h"
+#include "common/using_std_string.h"
+
+using namespace google_breakpad;
+
+TEST(LinuxCoreDumperTest, GetMappingAbsolutePath) {
+ const LinuxCoreDumper dumper(getpid(), "core", "/tmp", "/mnt/root");
+ const MappingInfo mapping = {0, 0, {0, 0}, 0, false, "/usr/lib/libc.so"};
+
+ char path[PATH_MAX];
+ dumper.GetMappingAbsolutePath(mapping, path);
+
+ EXPECT_STREQ("/mnt/root/usr/lib/libc.so", path);
+}
+
+TEST(LinuxCoreDumperTest, BuildProcPath) {
+ const pid_t pid = getpid();
+ const char procfs_path[] = "/procfs_copy";
+ LinuxCoreDumper dumper(getpid(), "core_file", procfs_path);
+
+ char maps_path[NAME_MAX] = "";
+ char maps_path_expected[NAME_MAX];
+ snprintf(maps_path_expected, sizeof(maps_path_expected),
+ "%s/maps", procfs_path);
+ EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
+ EXPECT_STREQ(maps_path_expected, maps_path);
+
+ EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
+ EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
+ EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
+
+ char long_node[NAME_MAX];
+ size_t long_node_len = NAME_MAX - strlen(procfs_path) - 1;
+ memset(long_node, 'a', long_node_len);
+ long_node[long_node_len] = '\0';
+ EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, long_node));
+}
+
+TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) {
+ CrashGenerator crash_generator;
+ if (!crash_generator.HasDefaultCorePattern()) {
+ fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test "
+ "is skipped due to non-default core pattern\n");
+ return;
+ }
+
+ const unsigned kNumOfThreads = 3;
+ const unsigned kCrashThread = 1;
+ const int kCrashSignal = SIGABRT;
+ pid_t child_pid;
+ ASSERT_TRUE(crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
+ kCrashSignal, &child_pid));
+
+ const string core_file = crash_generator.GetCoreFilePath();
+ const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
+
+#if defined(__ANDROID__)
+ struct stat st;
+ if (stat(core_file.c_str(), &st) != 0) {
+ fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test is "
+ "skipped due to no core file being generated\n");
+ return;
+ }
+#endif
+
+ LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
+
+ EXPECT_TRUE(dumper.Init());
+
+ EXPECT_TRUE(dumper.IsPostMortem());
+
+ // These are no-ops and should always return true.
+ EXPECT_TRUE(dumper.ThreadsSuspend());
+ EXPECT_TRUE(dumper.ThreadsResume());
+
+ // Linux does not set the crash address with SIGABRT, so make sure it always
+ // sets the crash address to 0.
+ EXPECT_EQ(0U, dumper.crash_address());
+ EXPECT_EQ(kCrashSignal, dumper.crash_signal());
+ EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
+ dumper.crash_thread());
+
+#if defined(THREAD_SANITIZER)
+ EXPECT_GE(dumper.threads().size(), kNumOfThreads);
+#else
+ EXPECT_EQ(dumper.threads().size(), kNumOfThreads);
+#endif
+ for (unsigned i = 0; i < kNumOfThreads; ++i) {
+ ThreadInfo info;
+ EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &info));
+ const void* stack;
+ size_t stack_len;
+ EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len, info.stack_pointer));
+ EXPECT_EQ(getpid(), info.ppid);
+ }
+}
+
+TEST(LinuxCoreDumperTest, VerifyExceptionDetails) {
+ CrashGenerator crash_generator;
+ if (!crash_generator.HasDefaultCorePattern()) {
+ fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test "
+ "is skipped due to non-default core pattern\n");
+ return;
+ }
+
+#ifndef si_syscall
+ fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test is "
+ "skipped due to old kernel/C library headers\n");
+ return;
+#endif
+
+ const unsigned kNumOfThreads = 2;
+ const unsigned kCrashThread = 1;
+ const int kCrashSignal = SIGSYS;
+ pid_t child_pid;
+ ASSERT_TRUE(crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
+ kCrashSignal, &child_pid));
+
+ const string core_file = crash_generator.GetCoreFilePath();
+ const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
+
+#if defined(__ANDROID__)
+ struct stat st;
+ if (stat(core_file.c_str(), &st) != 0) {
+ fprintf(stderr, "LinuxCoreDumperTest.VerifyExceptionDetails test is "
+ "skipped due to no core file being generated\n");
+ return;
+ }
+#endif
+
+ LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
+
+ EXPECT_TRUE(dumper.Init());
+
+ EXPECT_TRUE(dumper.IsPostMortem());
+
+#if defined(__ANDROID__)
+ // TODO: For some reason, Android doesn't seem to pass this.
+ if (!dumper.crash_address()) {
+ fprintf(stderr, "LinuxCoreDumperTest.VerifyExceptionDetails test is "
+ "skipped due to missing signal details on Android\n");
+ return;
+ }
+#endif
+
+ // Check the exception details.
+ EXPECT_NE(0U, dumper.crash_address());
+ EXPECT_EQ(kCrashSignal, dumper.crash_signal());
+ EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
+ dumper.crash_thread());
+
+ // We check the length, but not the actual fields. We sent SIGSYS ourselves
+ // instead of the kernel, so the extended fields are garbage.
+ const std::vector<uint64_t> info(dumper.crash_exception_info());
+ EXPECT_EQ(2U, info.size());
+}
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc
new file mode 100644
index 0000000000..3b400ce8ca
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc
@@ -0,0 +1,999 @@
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// linux_dumper.cc: Implement google_breakpad::LinuxDumper.
+// See linux_dumper.h for details.
+
+// This code deals with the mechanics of getting information about a crashed
+// process. Since this code may run in a compromised address space, the same
+// rules apply as detailed at the top of minidump_writer.h: no libc calls and
+// use the alternative allocator.
+
+#include "linux/minidump_writer/linux_dumper.h"
+
+#include <assert.h>
+#include <elf.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "linux/minidump_writer/line_reader.h"
+#include "common/linux/elfutils.h"
+#include "common/linux/file_id.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/linux/memory_mapped_file.h"
+#include "common/linux/safe_readlink.h"
+#include "google_breakpad/common/minidump_exception_linux.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+#if defined(__ANDROID__)
+
+// Android packed relocations definitions are not yet available from the
+// NDK header files, so we have to provide them manually here.
+#ifndef DT_LOOS
+#define DT_LOOS 0x6000000d
+#endif
+#ifndef DT_ANDROID_REL
+static const int DT_ANDROID_REL = DT_LOOS + 2;
+#endif
+#ifndef DT_ANDROID_RELA
+static const int DT_ANDROID_RELA = DT_LOOS + 4;
+#endif
+
+#endif // __ANDROID __
+
+static const char kMappedFileUnsafePrefix[] = "/dev/";
+static const char kDeletedSuffix[] = " (deleted)";
+static const char kReservedFlags[] = " ---p";
+static const char kMozillaIpcPrefix[] = "org.mozilla.ipc.";
+
+inline static bool IsMappedFileOpenUnsafe(
+ const google_breakpad::MappingInfo& mapping) {
+ // It is unsafe to attempt to open a mapped file that lives under /dev,
+ // because the semantics of the open may be driver-specific so we'd risk
+ // hanging the crash dumper. And a file in /dev/ almost certainly has no
+ // ELF file identifier anyways.
+ return my_strncmp(mapping.name,
+ kMappedFileUnsafePrefix,
+ sizeof(kMappedFileUnsafePrefix) - 1) == 0;
+}
+
+namespace google_breakpad {
+
+namespace {
+
+bool MappingContainsAddress(const MappingInfo& mapping, uintptr_t address) {
+ return mapping.system_mapping_info.start_addr <= address &&
+ address < mapping.system_mapping_info.end_addr;
+}
+
+#if defined(__CHROMEOS__)
+
+// Recover memory mappings before writing dump on ChromeOS
+//
+// On Linux, breakpad relies on /proc/[pid]/maps to associate symbols from
+// addresses. ChromeOS' hugepage implementation replaces some segments with
+// anonymous private pages, which is a restriction of current implementation
+// in Linux kernel at the time of writing. Thus, breakpad can no longer
+// symbolize addresses from those text segments replaced with hugepages.
+//
+// This postprocess tries to recover the mappings. Because hugepages are always
+// inserted in between some .text sections, it tries to infer the names and
+// offsets of the segments, by looking at segments immediately precede and
+// succeed them.
+//
+// For example, a text segment before hugepage optimization
+// 02001000-03002000 r-xp /opt/google/chrome/chrome
+//
+// can be broken into
+// 02001000-02200000 r-xp /opt/google/chrome/chrome
+// 02200000-03000000 r-xp
+// 03000000-03002000 r-xp /opt/google/chrome/chrome
+//
+// For more details, see:
+// crbug.com/628040 ChromeOS' use of hugepages confuses crash symbolization
+
+// Copied from CrOS' hugepage implementation, which is unlikely to change.
+// The hugepage size is 2M.
+const unsigned int kHpageShift = 21;
+const size_t kHpageSize = (1 << kHpageShift);
+const size_t kHpageMask = (~(kHpageSize - 1));
+
+// Find and merge anonymous r-xp segments with surrounding named segments.
+// There are two cases:
+
+// Case 1: curr, next
+// curr is anonymous
+// curr is r-xp
+// curr.size >= 2M
+// curr.size is a multiple of 2M.
+// next is backed by some file.
+// curr and next are contiguous.
+// offset(next) == sizeof(curr)
+void TryRecoverMappings(MappingInfo *curr, MappingInfo *next) {
+ // Merged segments are marked with size = 0.
+ if (curr->size == 0 || next->size == 0)
+ return;
+
+ if (curr->size >= kHpageSize &&
+ curr->exec &&
+ (curr->size & kHpageMask) == curr->size &&
+ (curr->start_addr & kHpageMask) == curr->start_addr &&
+ curr->name[0] == '\0' &&
+ next->name[0] != '\0' &&
+ curr->start_addr + curr->size == next->start_addr &&
+ curr->size == next->offset) {
+
+ // matched
+ my_strlcpy(curr->name, next->name, NAME_MAX);
+ if (next->exec) {
+ // (curr, next)
+ curr->size += next->size;
+ next->size = 0;
+ }
+ }
+}
+
+// Case 2: prev, curr, next
+// curr is anonymous
+// curr is r-xp
+// curr.size >= 2M
+// curr.size is a multiple of 2M.
+// next and prev are backed by the same file.
+// prev, curr and next are contiguous.
+// offset(next) == offset(prev) + sizeof(prev) + sizeof(curr)
+void TryRecoverMappings(MappingInfo *prev, MappingInfo *curr,
+ MappingInfo *next) {
+ // Merged segments are marked with size = 0.
+ if (prev->size == 0 || curr->size == 0 || next->size == 0)
+ return;
+
+ if (curr->size >= kHpageSize &&
+ curr->exec &&
+ (curr->size & kHpageMask) == curr->size &&
+ (curr->start_addr & kHpageMask) == curr->start_addr &&
+ curr->name[0] == '\0' &&
+ next->name[0] != '\0' &&
+ curr->start_addr + curr->size == next->start_addr &&
+ prev->start_addr + prev->size == curr->start_addr &&
+ my_strncmp(prev->name, next->name, NAME_MAX) == 0 &&
+ next->offset == prev->offset + prev->size + curr->size) {
+
+ // matched
+ my_strlcpy(curr->name, prev->name, NAME_MAX);
+ if (prev->exec) {
+ curr->offset = prev->offset;
+ curr->start_addr = prev->start_addr;
+ if (next->exec) {
+ // (prev, curr, next)
+ curr->size += prev->size + next->size;
+ prev->size = 0;
+ next->size = 0;
+ } else {
+ // (prev, curr), next
+ curr->size += prev->size;
+ prev->size = 0;
+ }
+ } else {
+ curr->offset = prev->offset + prev->size;
+ if (next->exec) {
+ // prev, (curr, next)
+ curr->size += next->size;
+ next->size = 0;
+ } else {
+ // prev, curr, next
+ }
+ }
+ }
+}
+
+// mappings_ is sorted excepted for the first entry.
+// This function tries to merge segemnts into the first entry,
+// then check for other sorted entries.
+// See LinuxDumper::EnumerateMappings().
+void CrOSPostProcessMappings(wasteful_vector<MappingInfo*>& mappings) {
+ // Find the candidate "next" to first segment, which is the only one that
+ // could be out-of-order.
+ size_t l = 1;
+ size_t r = mappings.size();
+ size_t next = mappings.size();
+ while (l < r) {
+ int m = (l + r) / 2;
+ if (mappings[m]->start_addr > mappings[0]->start_addr)
+ r = next = m;
+ else
+ l = m + 1;
+ }
+
+ // Shows the range that contains the entry point is
+ // [first_start_addr, first_end_addr)
+ size_t first_start_addr = mappings[0]->start_addr;
+ size_t first_end_addr = mappings[0]->start_addr + mappings[0]->size;
+
+ // Put the out-of-order segment in order.
+ std::rotate(mappings.begin(), mappings.begin() + 1, mappings.begin() + next);
+
+ // Iterate through normal, sorted cases.
+ // Normal case 1.
+ for (size_t i = 0; i < mappings.size() - 1; i++)
+ TryRecoverMappings(mappings[i], mappings[i + 1]);
+
+ // Normal case 2.
+ for (size_t i = 0; i < mappings.size() - 2; i++)
+ TryRecoverMappings(mappings[i], mappings[i + 1], mappings[i + 2]);
+
+ // Collect merged (size == 0) segments.
+ size_t f, e;
+ for (f = e = 0; e < mappings.size(); e++)
+ if (mappings[e]->size > 0)
+ mappings[f++] = mappings[e];
+ mappings.resize(f);
+
+ // The entry point is in the first mapping. We want to find the location
+ // of the entry point after merging segment. To do this, we want to find
+ // the mapping that covers the first mapping from the original mapping list.
+ // If the mapping is not in the beginning, we move it to the begining via
+ // a right rotate by using reverse iterators.
+ for (l = 0; l < mappings.size(); l++) {
+ if (mappings[l]->start_addr <= first_start_addr
+ && (mappings[l]->start_addr + mappings[l]->size >= first_end_addr))
+ break;
+ }
+ if (l > 0) {
+ r = mappings.size();
+ std::rotate(mappings.rbegin() + r - l - 1, mappings.rbegin() + r - l,
+ mappings.rend());
+ }
+}
+
+#endif // __CHROMEOS__
+
+} // namespace
+
+// All interesting auvx entry types are below AT_SYSINFO_EHDR
+#define AT_MAX AT_SYSINFO_EHDR
+
+LinuxDumper::LinuxDumper(pid_t pid, const char* root_prefix)
+ : pid_(pid),
+ root_prefix_(root_prefix),
+ crash_address_(0),
+ crash_signal_(0),
+ crash_signal_code_(0),
+ crash_thread_(pid),
+ threads_(&allocator_, 8),
+ mappings_(&allocator_),
+ auxv_(&allocator_, AT_MAX + 1) {
+ assert(root_prefix_ && my_strlen(root_prefix_) < PATH_MAX);
+ // The passed-in size to the constructor (above) is only a hint.
+ // Must call .resize() to do actual initialization of the elements.
+ auxv_.resize(AT_MAX + 1);
+}
+
+LinuxDumper::~LinuxDumper() {
+}
+
+bool LinuxDumper::Init() {
+ return ReadAuxv() && EnumerateThreads() && EnumerateMappings();
+}
+
+bool LinuxDumper::LateInit() {
+#if defined(__ANDROID__)
+ LatePostprocessMappings();
+#endif
+
+#if defined(__CHROMEOS__)
+ CrOSPostProcessMappings(mappings_);
+#endif
+
+ return true;
+}
+
+bool
+LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
+ bool member,
+ unsigned int mapping_id,
+ wasteful_vector<uint8_t>& identifier) {
+ assert(!member || mapping_id < mappings_.size());
+ if (IsMappedFileOpenUnsafe(mapping))
+ return false;
+
+ // Special-case linux-gate because it's not a real file.
+ if (my_strcmp(mapping.name, kLinuxGateLibraryName) == 0) {
+ void* linux_gate = NULL;
+ if (pid_ == sys_getpid()) {
+ linux_gate = reinterpret_cast<void*>(mapping.start_addr);
+ } else {
+ linux_gate = allocator_.Alloc(mapping.size);
+ CopyFromProcess(linux_gate, pid_,
+ reinterpret_cast<const void*>(mapping.start_addr),
+ mapping.size);
+ }
+ return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier);
+ }
+
+ char filename[PATH_MAX];
+ if (!GetMappingAbsolutePath(mapping, filename))
+ return false;
+ bool filename_modified = HandleDeletedFileInMapping(filename);
+
+ MemoryMappedFile mapped_file(filename, mapping.offset);
+ if (!mapped_file.data() || mapped_file.size() < SELFMAG)
+ return false;
+
+ bool success =
+ FileID::ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier);
+ if (success && member && filename_modified) {
+ mappings_[mapping_id]->name[my_strlen(mapping.name) -
+ sizeof(kDeletedSuffix) + 1] = '\0';
+ }
+
+ return success;
+}
+
+void LinuxDumper::SetCrashInfoFromSigInfo(const siginfo_t& siginfo) {
+ set_crash_address(reinterpret_cast<uintptr_t>(siginfo.si_addr));
+ set_crash_signal(siginfo.si_signo);
+ set_crash_signal_code(siginfo.si_code);
+}
+
+const char* LinuxDumper::GetCrashSignalString() const {
+ switch (static_cast<unsigned int>(crash_signal_)) {
+ case MD_EXCEPTION_CODE_LIN_SIGHUP:
+ return "SIGHUP";
+ case MD_EXCEPTION_CODE_LIN_SIGINT:
+ return "SIGINT";
+ case MD_EXCEPTION_CODE_LIN_SIGQUIT:
+ return "SIGQUIT";
+ case MD_EXCEPTION_CODE_LIN_SIGILL:
+ return "SIGILL";
+ case MD_EXCEPTION_CODE_LIN_SIGTRAP:
+ return "SIGTRAP";
+ case MD_EXCEPTION_CODE_LIN_SIGABRT:
+ return "SIGABRT";
+ case MD_EXCEPTION_CODE_LIN_SIGBUS:
+ return "SIGBUS";
+ case MD_EXCEPTION_CODE_LIN_SIGFPE:
+ return "SIGFPE";
+ case MD_EXCEPTION_CODE_LIN_SIGKILL:
+ return "SIGKILL";
+ case MD_EXCEPTION_CODE_LIN_SIGUSR1:
+ return "SIGUSR1";
+ case MD_EXCEPTION_CODE_LIN_SIGSEGV:
+ return "SIGSEGV";
+ case MD_EXCEPTION_CODE_LIN_SIGUSR2:
+ return "SIGUSR2";
+ case MD_EXCEPTION_CODE_LIN_SIGPIPE:
+ return "SIGPIPE";
+ case MD_EXCEPTION_CODE_LIN_SIGALRM:
+ return "SIGALRM";
+ case MD_EXCEPTION_CODE_LIN_SIGTERM:
+ return "SIGTERM";
+ case MD_EXCEPTION_CODE_LIN_SIGSTKFLT:
+ return "SIGSTKFLT";
+ case MD_EXCEPTION_CODE_LIN_SIGCHLD:
+ return "SIGCHLD";
+ case MD_EXCEPTION_CODE_LIN_SIGCONT:
+ return "SIGCONT";
+ case MD_EXCEPTION_CODE_LIN_SIGSTOP:
+ return "SIGSTOP";
+ case MD_EXCEPTION_CODE_LIN_SIGTSTP:
+ return "SIGTSTP";
+ case MD_EXCEPTION_CODE_LIN_SIGTTIN:
+ return "SIGTTIN";
+ case MD_EXCEPTION_CODE_LIN_SIGTTOU:
+ return "SIGTTOU";
+ case MD_EXCEPTION_CODE_LIN_SIGURG:
+ return "SIGURG";
+ case MD_EXCEPTION_CODE_LIN_SIGXCPU:
+ return "SIGXCPU";
+ case MD_EXCEPTION_CODE_LIN_SIGXFSZ:
+ return "SIGXFSZ";
+ case MD_EXCEPTION_CODE_LIN_SIGVTALRM:
+ return "SIGVTALRM";
+ case MD_EXCEPTION_CODE_LIN_SIGPROF:
+ return "SIGPROF";
+ case MD_EXCEPTION_CODE_LIN_SIGWINCH:
+ return "SIGWINCH";
+ case MD_EXCEPTION_CODE_LIN_SIGIO:
+ return "SIGIO";
+ case MD_EXCEPTION_CODE_LIN_SIGPWR:
+ return "SIGPWR";
+ case MD_EXCEPTION_CODE_LIN_SIGSYS:
+ return "SIGSYS";
+ case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
+ return "DUMP_REQUESTED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+bool LinuxDumper::GetMappingAbsolutePath(const MappingInfo& mapping,
+ char path[PATH_MAX]) const {
+ return my_strlcpy(path, root_prefix_, PATH_MAX) < PATH_MAX &&
+ my_strlcat(path, mapping.name, PATH_MAX) < PATH_MAX;
+}
+
+namespace {
+// Find the shared object name (SONAME) by examining the ELF information
+// for |mapping|. If the SONAME is found copy it into the passed buffer
+// |soname| and return true. The size of the buffer is |soname_size|.
+// The SONAME will be truncated if it is too long to fit in the buffer.
+bool ElfFileSoName(const LinuxDumper& dumper,
+ const MappingInfo& mapping, char* soname, size_t soname_size) {
+ if (IsMappedFileOpenUnsafe(mapping)) {
+ // Not safe
+ return false;
+ }
+
+ char filename[PATH_MAX];
+ if (!dumper.GetMappingAbsolutePath(mapping, filename))
+ return false;
+
+ MemoryMappedFile mapped_file(filename, mapping.offset);
+ if (!mapped_file.data() || mapped_file.size() < SELFMAG) {
+ // mmap failed
+ return false;
+ }
+
+ return ElfFileSoNameFromMappedFile(mapped_file.data(), soname, soname_size);
+}
+
+} // namespace
+
+
+void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
+ char* file_path,
+ size_t file_path_size,
+ char* file_name,
+ size_t file_name_size) {
+ my_strlcpy(file_path, mapping.name, file_path_size);
+
+ // Tools such as minidump_stackwalk use the name of the module to look up
+ // symbols produced by dump_syms. dump_syms will prefer to use a module's
+ // DT_SONAME as the module name, if one exists, and will fall back to the
+ // filesystem name of the module.
+
+ // Just use the filesystem name if no SONAME is present.
+ if (!ElfFileSoName(*this, mapping, file_name, file_name_size)) {
+ // file_path := /path/to/libname.so
+ // file_name := libname.so
+ const char* basename = my_strrchr(file_path, '/');
+ basename = basename == NULL ? file_path : (basename + 1);
+ my_strlcpy(file_name, basename, file_name_size);
+ return;
+ }
+
+ if (mapping.exec && mapping.offset != 0) {
+ // If an executable is mapped from a non-zero offset, this is likely because
+ // the executable was loaded directly from inside an archive file (e.g., an
+ // apk on Android).
+ // In this case, we append the file_name to the mapped archive path:
+ // file_name := libname.so
+ // file_path := /path/to/ARCHIVE.APK/libname.so
+ if (my_strlen(file_path) + 1 + my_strlen(file_name) < file_path_size) {
+ my_strlcat(file_path, "/", file_path_size);
+ my_strlcat(file_path, file_name, file_path_size);
+ }
+ } else {
+ // Otherwise, replace the basename with the SONAME.
+ char* basename = const_cast<char*>(my_strrchr(file_path, '/'));
+ if (basename) {
+ my_strlcpy(basename + 1, file_name,
+ file_path_size - my_strlen(file_path) +
+ my_strlen(basename + 1));
+ } else {
+ my_strlcpy(file_path, file_name, file_path_size);
+ }
+ }
+}
+
+bool LinuxDumper::ReadAuxv() {
+ char auxv_path[NAME_MAX];
+ if (!BuildProcPath(auxv_path, pid_, "auxv")) {
+ return false;
+ }
+
+ int fd = sys_open(auxv_path, O_RDONLY, 0);
+ if (fd < 0) {
+ return false;
+ }
+
+ elf_aux_entry one_aux_entry;
+ bool res = false;
+ while (sys_read(fd,
+ &one_aux_entry,
+ sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) &&
+ one_aux_entry.a_type != AT_NULL) {
+ if (one_aux_entry.a_type <= AT_MAX) {
+ auxv_[one_aux_entry.a_type] = one_aux_entry.a_un.a_val;
+ res = true;
+ }
+ }
+ sys_close(fd);
+ return res;
+}
+
+bool LinuxDumper::IsIPCSharedMemorySegment(const char* name) {
+ if (my_strstr(name, kMozillaIpcPrefix) &&
+ my_strstr(name, kDeletedSuffix)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool LinuxDumper::EnumerateMappings() {
+ char maps_path[NAME_MAX];
+ if (!BuildProcPath(maps_path, pid_, "maps"))
+ return false;
+
+ // linux_gate_loc is the beginning of the kernel's mapping of
+ // linux-gate.so in the process. It doesn't actually show up in the
+ // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR
+ // aux vector entry, which gives the information necessary to special
+ // case its entry when creating the list of mappings.
+ // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
+ // information.
+ const void* linux_gate_loc =
+ reinterpret_cast<void *>(auxv_[AT_SYSINFO_EHDR]);
+ // Although the initial executable is usually the first mapping, it's not
+ // guaranteed (see http://crosbug.com/25355); therefore, try to use the
+ // actual entry point to find the mapping.
+ const void* entry_point_loc = reinterpret_cast<void *>(auxv_[AT_ENTRY]);
+
+ const int fd = sys_open(maps_path, O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+ LineReader* const line_reader = new(allocator_) LineReader(fd);
+
+ const char* line;
+ unsigned line_len;
+ while (line_reader->GetNextLine(&line, &line_len)) {
+ uintptr_t start_addr, end_addr, offset;
+
+ const char* i1 = my_read_hex_ptr(&start_addr, line);
+ if (*i1 == '-') {
+ const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
+ if (*i2 == ' ') {
+ bool exec = (*(i2 + 3) == 'x');
+ const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
+ if (*i3 == ' ') {
+ const char* name = NULL;
+ // Only copy name if the name is a valid path name, or if
+ // it's the VDSO image.
+ if (((name = my_strchr(line, '/')) == NULL) &&
+ linux_gate_loc &&
+ reinterpret_cast<void*>(start_addr) == linux_gate_loc) {
+ name = kLinuxGateLibraryName;
+ offset = 0;
+ }
+ // Skip shared memory segments used for IPC
+ if (name && IsIPCSharedMemorySegment(name)) {
+ line_reader->PopLine(line_len);
+ continue;
+ }
+ // Merge adjacent mappings into one module, assuming they're a single
+ // library mapped by the dynamic linker.
+ if (name && !mappings_.empty()) {
+ MappingInfo* module = mappings_.back();
+ if ((start_addr == module->start_addr + module->size) &&
+ (my_strlen(name) == my_strlen(module->name)) &&
+ (my_strncmp(name, module->name, my_strlen(name)) == 0)) {
+ module->system_mapping_info.end_addr = end_addr;
+ module->size = end_addr - module->start_addr;
+ module->exec |= exec;
+ line_reader->PopLine(line_len);
+ continue;
+ }
+ }
+ // Also merge mappings that result from address ranges that the
+ // linker reserved but which a loaded library did not use. These
+ // appear as an anonymous private mapping with no access flags set
+ // and which directly follow an executable mapping.
+ if (!name && !mappings_.empty()) {
+ MappingInfo* module = mappings_.back();
+ uintptr_t module_end_addr = module->start_addr + module->size;
+ if ((start_addr == module_end_addr) &&
+ module->exec &&
+ module->name[0] == '/' &&
+ ((offset == 0) || (offset == module_end_addr)) &&
+ my_strncmp(i2, kReservedFlags,
+ sizeof(kReservedFlags) - 1) == 0) {
+ module->size = end_addr - module->start_addr;
+ line_reader->PopLine(line_len);
+ continue;
+ }
+ }
+ MappingInfo* const module = new(allocator_) MappingInfo;
+ mappings_.push_back(module);
+ my_memset(module, 0, sizeof(MappingInfo));
+ module->system_mapping_info.start_addr = start_addr;
+ module->system_mapping_info.end_addr = end_addr;
+ module->start_addr = start_addr;
+ module->size = end_addr - start_addr;
+ module->offset = offset;
+ module->exec = exec;
+ if (name != NULL) {
+ const unsigned l = my_strlen(name);
+ if (l < sizeof(module->name))
+ my_memcpy(module->name, name, l);
+ }
+ }
+ }
+ }
+ line_reader->PopLine(line_len);
+ }
+
+ if (entry_point_loc) {
+ for (size_t i = 0; i < mappings_.size(); ++i) {
+ MappingInfo* module = mappings_[i];
+
+ // If this module contains the entry-point, and it's not already the first
+ // one, then we need to make it be first. This is because the minidump
+ // format assumes the first module is the one that corresponds to the main
+ // executable (as codified in
+ // processor/minidump.cc:MinidumpModuleList::GetMainModule()).
+ if ((entry_point_loc >= reinterpret_cast<void*>(module->start_addr)) &&
+ (entry_point_loc <
+ reinterpret_cast<void*>(module->start_addr + module->size))) {
+ for (size_t j = i; j > 0; j--)
+ mappings_[j] = mappings_[j - 1];
+ mappings_[0] = module;
+ break;
+ }
+ }
+ }
+
+ sys_close(fd);
+
+ return !mappings_.empty();
+}
+
+#if defined(__ANDROID__)
+
+bool LinuxDumper::GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr) {
+ CopyFromProcess(ehdr, pid_,
+ reinterpret_cast<const void*>(start_addr),
+ sizeof(*ehdr));
+ return my_memcmp(&ehdr->e_ident, ELFMAG, SELFMAG) == 0;
+}
+
+void LinuxDumper::ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr,
+ uintptr_t start_addr,
+ uintptr_t* min_vaddr_ptr,
+ uintptr_t* dyn_vaddr_ptr,
+ size_t* dyn_count_ptr) {
+ uintptr_t phdr_addr = start_addr + ehdr->e_phoff;
+
+ const uintptr_t max_addr = UINTPTR_MAX;
+ uintptr_t min_vaddr = max_addr;
+ uintptr_t dyn_vaddr = 0;
+ size_t dyn_count = 0;
+
+ for (size_t i = 0; i < ehdr->e_phnum; ++i) {
+ ElfW(Phdr) phdr;
+ CopyFromProcess(&phdr, pid_,
+ reinterpret_cast<const void*>(phdr_addr),
+ sizeof(phdr));
+ if (phdr.p_type == PT_LOAD && phdr.p_vaddr < min_vaddr) {
+ min_vaddr = phdr.p_vaddr;
+ }
+ if (phdr.p_type == PT_DYNAMIC) {
+ dyn_vaddr = phdr.p_vaddr;
+ dyn_count = phdr.p_memsz / sizeof(ElfW(Dyn));
+ }
+ phdr_addr += sizeof(phdr);
+ }
+
+ *min_vaddr_ptr = min_vaddr;
+ *dyn_vaddr_ptr = dyn_vaddr;
+ *dyn_count_ptr = dyn_count;
+}
+
+bool LinuxDumper::HasAndroidPackedRelocations(uintptr_t load_bias,
+ uintptr_t dyn_vaddr,
+ size_t dyn_count) {
+ uintptr_t dyn_addr = load_bias + dyn_vaddr;
+ for (size_t i = 0; i < dyn_count; ++i) {
+ ElfW(Dyn) dyn;
+ CopyFromProcess(&dyn, pid_,
+ reinterpret_cast<const void*>(dyn_addr),
+ sizeof(dyn));
+ if (dyn.d_tag == DT_ANDROID_REL || dyn.d_tag == DT_ANDROID_RELA) {
+ return true;
+ }
+ dyn_addr += sizeof(dyn);
+ }
+ return false;
+}
+
+uintptr_t LinuxDumper::GetEffectiveLoadBias(ElfW(Ehdr)* ehdr,
+ uintptr_t start_addr) {
+ uintptr_t min_vaddr = 0;
+ uintptr_t dyn_vaddr = 0;
+ size_t dyn_count = 0;
+ ParseLoadedElfProgramHeaders(ehdr, start_addr,
+ &min_vaddr, &dyn_vaddr, &dyn_count);
+ // If |min_vaddr| is non-zero and we find Android packed relocation tags,
+ // return the effective load bias.
+ if (min_vaddr != 0) {
+ const uintptr_t load_bias = start_addr - min_vaddr;
+ if (HasAndroidPackedRelocations(load_bias, dyn_vaddr, dyn_count)) {
+ return load_bias;
+ }
+ }
+ // Either |min_vaddr| is zero, or it is non-zero but we did not find the
+ // expected Android packed relocations tags.
+ return start_addr;
+}
+
+void LinuxDumper::LatePostprocessMappings() {
+ for (size_t i = 0; i < mappings_.size(); ++i) {
+ // Only consider exec mappings that indicate a file path was mapped, and
+ // where the ELF header indicates a mapped shared library.
+ MappingInfo* mapping = mappings_[i];
+ if (!(mapping->exec && mapping->name[0] == '/')) {
+ continue;
+ }
+ ElfW(Ehdr) ehdr;
+ if (!GetLoadedElfHeader(mapping->start_addr, &ehdr)) {
+ continue;
+ }
+ if (ehdr.e_type == ET_DYN) {
+ // Compute the effective load bias for this mapped library, and update
+ // the mapping to hold that rather than |start_addr|, at the same time
+ // adjusting |size| to account for the change in |start_addr|. Where
+ // the library does not contain Android packed relocations,
+ // GetEffectiveLoadBias() returns |start_addr| and the mapping entry
+ // is not changed.
+ const uintptr_t load_bias = GetEffectiveLoadBias(&ehdr,
+ mapping->start_addr);
+ mapping->size += mapping->start_addr - load_bias;
+ mapping->start_addr = load_bias;
+ }
+ }
+}
+
+#endif // __ANDROID__
+
+// Get information about the stack, given the stack pointer. We don't try to
+// walk the stack since we might not have all the information needed to do
+// unwind. So we just grab, up to, 32k of stack.
+bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
+ uintptr_t int_stack_pointer) {
+ // Move the stack pointer to the bottom of the page that it's in.
+ const uintptr_t page_size = getpagesize();
+
+ uint8_t* const stack_pointer =
+ reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
+
+ // The number of bytes of stack which we try to capture.
+ static const ptrdiff_t kStackToCapture = 32 * 1024;
+
+ const MappingInfo* mapping = FindMapping(stack_pointer);
+ if (!mapping)
+ return false;
+ const ptrdiff_t offset = stack_pointer -
+ reinterpret_cast<uint8_t*>(mapping->start_addr);
+ const ptrdiff_t distance_to_end =
+ static_cast<ptrdiff_t>(mapping->size) - offset;
+ *stack_len = distance_to_end > kStackToCapture ?
+ kStackToCapture : distance_to_end;
+ *stack = stack_pointer;
+ return true;
+}
+
+void LinuxDumper::SanitizeStackCopy(uint8_t* stack_copy, size_t stack_len,
+ uintptr_t stack_pointer,
+ uintptr_t sp_offset) {
+ // We optimize the search for containing mappings in three ways:
+ // 1) We expect that pointers into the stack mapping will be common, so
+ // we cache that address range.
+ // 2) The last referenced mapping is a reasonable predictor for the next
+ // referenced mapping, so we test that first.
+ // 3) We precompute a bitfield based upon bits 32:32-n of the start and
+ // stop addresses, and use that to short circuit any values that can
+ // not be pointers. (n=11)
+ const uintptr_t defaced =
+#if defined(__LP64__)
+ 0x0defaced0defaced;
+#else
+ 0x0defaced;
+#endif
+ // the bitfield length is 2^test_bits long.
+ const unsigned int test_bits = 11;
+ // byte length of the corresponding array.
+ const unsigned int array_size = 1 << (test_bits - 3);
+ const unsigned int array_mask = array_size - 1;
+ // The amount to right shift pointers by. This captures the top bits
+ // on 32 bit architectures. On 64 bit architectures this would be
+ // uninformative so we take the same range of bits.
+ const unsigned int shift = 32 - 11;
+ const MappingInfo* last_hit_mapping = nullptr;
+ const MappingInfo* hit_mapping = nullptr;
+ const MappingInfo* stack_mapping = FindMappingNoBias(stack_pointer);
+ // The magnitude below which integers are considered to be to be
+ // 'small', and not constitute a PII risk. These are included to
+ // avoid eliding useful register values.
+ const ssize_t small_int_magnitude = 4096;
+
+ char could_hit_mapping[array_size];
+ my_memset(could_hit_mapping, 0, array_size);
+
+ // Initialize the bitfield such that if the (pointer >> shift)'th
+ // bit, modulo the bitfield size, is not set then there does not
+ // exist a mapping in mappings_ that would contain that pointer.
+ for (size_t i = 0; i < mappings_.size(); ++i) {
+ if (!mappings_[i]->exec) continue;
+ // For each mapping, work out the (unmodulo'ed) range of bits to
+ // set.
+ uintptr_t start = mappings_[i]->start_addr;
+ uintptr_t end = start + mappings_[i]->size;
+ start >>= shift;
+ end >>= shift;
+ for (size_t bit = start; bit <= end; ++bit) {
+ // Set each bit in the range, applying the modulus.
+ could_hit_mapping[(bit >> 3) & array_mask] |= 1 << (bit & 7);
+ }
+ }
+
+ // Zero memory that is below the current stack pointer.
+ const uintptr_t offset =
+ (sp_offset + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);
+ if (offset) {
+ my_memset(stack_copy, 0, offset);
+ }
+
+ // Apply sanitization to each complete pointer-aligned word in the
+ // stack.
+ uint8_t* sp;
+ for (sp = stack_copy + offset;
+ sp <= stack_copy + stack_len - sizeof(uintptr_t);
+ sp += sizeof(uintptr_t)) {
+ uintptr_t addr;
+ my_memcpy(&addr, sp, sizeof(uintptr_t));
+ if (static_cast<intptr_t>(addr) <= small_int_magnitude &&
+ static_cast<intptr_t>(addr) >= -small_int_magnitude) {
+ continue;
+ }
+ if (stack_mapping && MappingContainsAddress(*stack_mapping, addr)) {
+ continue;
+ }
+ if (last_hit_mapping && MappingContainsAddress(*last_hit_mapping, addr)) {
+ continue;
+ }
+ uintptr_t test = addr >> shift;
+ if (could_hit_mapping[(test >> 3) & array_mask] & (1 << (test & 7)) &&
+ (hit_mapping = FindMappingNoBias(addr)) != nullptr &&
+ hit_mapping->exec) {
+ last_hit_mapping = hit_mapping;
+ continue;
+ }
+ my_memcpy(sp, &defaced, sizeof(uintptr_t));
+ }
+ // Zero any partial word at the top of the stack, if alignment is
+ // such that that is required.
+ if (sp < stack_copy + stack_len) {
+ my_memset(sp, 0, stack_copy + stack_len - sp);
+ }
+}
+
+bool LinuxDumper::StackHasPointerToMapping(const uint8_t* stack_copy,
+ size_t stack_len,
+ uintptr_t sp_offset,
+ const MappingInfo& mapping) {
+ // Loop over all stack words that would have been on the stack in
+ // the target process (i.e. are word aligned, and at addresses >=
+ // the stack pointer). Regardless of the alignment of |stack_copy|,
+ // the memory starting at |stack_copy| + |offset| represents an
+ // aligned word in the target process.
+ const uintptr_t low_addr = mapping.system_mapping_info.start_addr;
+ const uintptr_t high_addr = mapping.system_mapping_info.end_addr;
+ const uintptr_t offset =
+ (sp_offset + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);
+
+ for (const uint8_t* sp = stack_copy + offset;
+ sp <= stack_copy + stack_len - sizeof(uintptr_t);
+ sp += sizeof(uintptr_t)) {
+ uintptr_t addr;
+ my_memcpy(&addr, sp, sizeof(uintptr_t));
+ if (low_addr <= addr && addr <= high_addr)
+ return true;
+ }
+ return false;
+}
+
+// Find the mapping which the given memory address falls in.
+const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
+ const uintptr_t addr = (uintptr_t) address;
+
+ for (size_t i = 0; i < mappings_.size(); ++i) {
+ const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
+ if (addr >= start && addr - start < mappings_[i]->size)
+ return mappings_[i];
+ }
+
+ return NULL;
+}
+
+// Find the mapping which the given memory address falls in. Uses the
+// unadjusted mapping address range from the kernel, rather than the
+// biased range.
+const MappingInfo* LinuxDumper::FindMappingNoBias(uintptr_t address) const {
+ for (size_t i = 0; i < mappings_.size(); ++i) {
+ if (address >= mappings_[i]->system_mapping_info.start_addr &&
+ address < mappings_[i]->system_mapping_info.end_addr) {
+ return mappings_[i];
+ }
+ }
+ return NULL;
+}
+
+bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
+ static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1;
+
+ // Check for ' (deleted)' in |path|.
+ // |path| has to be at least as long as "/x (deleted)".
+ const size_t path_len = my_strlen(path);
+ if (path_len < kDeletedSuffixLen + 2)
+ return false;
+ if (my_strncmp(path + path_len - kDeletedSuffixLen, kDeletedSuffix,
+ kDeletedSuffixLen) != 0) {
+ return false;
+ }
+
+ // Check |path| against the /proc/pid/exe 'symlink'.
+ char exe_link[NAME_MAX];
+ if (!BuildProcPath(exe_link, pid_, "exe"))
+ return false;
+ MappingInfo new_mapping = {0};
+ if (!SafeReadLink(exe_link, new_mapping.name))
+ return false;
+ char new_path[PATH_MAX];
+ if (!GetMappingAbsolutePath(new_mapping, new_path))
+ return false;
+ if (my_strcmp(path, new_path) != 0)
+ return false;
+
+ // Check to see if someone actually named their executable 'foo (deleted)'.
+ struct kernel_stat exe_stat;
+ struct kernel_stat new_path_stat;
+ if (sys_stat(exe_link, &exe_stat) == 0 &&
+ sys_stat(new_path, &new_path_stat) == 0 &&
+ exe_stat.st_dev == new_path_stat.st_dev &&
+ exe_stat.st_ino == new_path_stat.st_ino) {
+ return false;
+ }
+
+ my_memcpy(path, exe_link, NAME_MAX);
+ return true;
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h
new file mode 100644
index 0000000000..7155524ffc
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h
@@ -0,0 +1,333 @@
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// linux_dumper.h: Define the google_breakpad::LinuxDumper class, which
+// is a base class for extracting information of a crashed process. It
+// was originally a complete implementation using the ptrace API, but
+// has been refactored to allow derived implementations supporting both
+// ptrace and core dump. A portion of the original implementation is now
+// in google_breakpad::LinuxPtraceDumper (see linux_ptrace_dumper.h for
+// details).
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
+
+#include <assert.h>
+#include <elf.h>
+#if defined(__ANDROID__)
+#include <link.h>
+#endif
+#include <linux/limits.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/user.h>
+
+#include <vector>
+
+#include "linux/dump_writer_common/mapping_info.h"
+#include "linux/dump_writer_common/thread_info.h"
+#include "common/linux/file_id.h"
+#include "common/memory_allocator.h"
+#include "google_breakpad/common/minidump_format.h"
+
+namespace google_breakpad {
+
+// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
+#if defined(__i386) || defined(__ARM_EABI__) || \
+ (defined(__mips__) && _MIPS_SIM == _ABIO32)
+typedef Elf32_auxv_t elf_aux_entry;
+#elif defined(__x86_64) || defined(__aarch64__) || \
+ (defined(__mips__) && _MIPS_SIM != _ABIO32)
+typedef Elf64_auxv_t elf_aux_entry;
+#endif
+
+typedef __typeof__(((elf_aux_entry*) 0)->a_un.a_val) elf_aux_val_t;
+
+// When we find the VDSO mapping in the process's address space, this
+// is the name we use for it when writing it to the minidump.
+// This should always be less than NAME_MAX!
+const char kLinuxGateLibraryName[] = "linux-gate.so";
+
+class LinuxDumper {
+ public:
+ // The |root_prefix| is prepended to mapping paths before opening them, which
+ // is useful if the crash originates from a chroot.
+ explicit LinuxDumper(pid_t pid, const char* root_prefix = "");
+
+ virtual ~LinuxDumper();
+
+ // Parse the data for |threads| and |mappings|.
+ virtual bool Init();
+
+ // Take any actions that could not be taken in Init(). LateInit() is
+ // called after all other caller's initialization is complete, and in
+ // particular after it has called ThreadsSuspend(), so that ptrace is
+ // available.
+ virtual bool LateInit();
+
+ // Return true if the dumper performs a post-mortem dump.
+ virtual bool IsPostMortem() const = 0;
+
+ // Suspend/resume all threads in the given process.
+ virtual bool ThreadsSuspend() = 0;
+ virtual bool ThreadsResume() = 0;
+
+ // Read information about the |index|-th thread of |threads_|.
+ // Returns true on success. One must have called |ThreadsSuspend| first.
+ virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info) = 0;
+
+ // Read the name ofthe |index|-th thread of |threads_|.
+ // Returns true on success. One must have called |ThreadsSuspend| first.
+ virtual bool GetThreadNameByIndex(size_t index, char* name, size_t size) = 0;
+
+ size_t GetMainThreadIndex() const {
+ for (size_t i = 0; i < threads_.size(); ++i) {
+ if (threads_[i] == pid_) return i;
+ }
+ return -1u;
+ }
+
+ // These are only valid after a call to |Init|.
+ const wasteful_vector<pid_t> &threads() { return threads_; }
+ const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
+ const MappingInfo* FindMapping(const void* address) const;
+ // Find the mapping which the given memory address falls in. Unlike
+ // FindMapping, this method uses the unadjusted mapping address
+ // ranges from the kernel, rather than the ranges that have had the
+ // load bias applied.
+ const MappingInfo* FindMappingNoBias(uintptr_t address) const;
+ const wasteful_vector<elf_aux_val_t>& auxv() { return auxv_; }
+
+ // Find a block of memory to take as the stack given the top of stack pointer.
+ // stack: (output) the lowest address in the memory area
+ // stack_len: (output) the length of the memory area
+ // stack_top: the current top of the stack
+ bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
+
+ // Sanitize a copy of the stack by overwriting words that are not
+ // pointers with a sentinel (0x0defaced).
+ // stack_copy: a copy of the stack to sanitize. |stack_copy| might
+ // not be word aligned, but it represents word aligned
+ // data copied from another location.
+ // stack_len: the length of the allocation pointed to by |stack_copy|.
+ // stack_pointer: the address of the stack pointer (used to locate
+ // the stack mapping, as an optimization).
+ // sp_offset: the offset relative to stack_copy that reflects the
+ // current value of the stack pointer.
+ void SanitizeStackCopy(uint8_t* stack_copy, size_t stack_len,
+ uintptr_t stack_pointer, uintptr_t sp_offset);
+
+ // Test whether |stack_copy| contains a pointer-aligned word that
+ // could be an address within a given mapping.
+ // stack_copy: a copy of the stack to check. |stack_copy| might
+ // not be word aligned, but it represents word aligned
+ // data copied from another location.
+ // stack_len: the length of the allocation pointed to by |stack_copy|.
+ // sp_offset: the offset relative to stack_copy that reflects the
+ // current value of the stack pointer.
+ // mapping: the mapping against which to test stack words.
+ bool StackHasPointerToMapping(const uint8_t* stack_copy, size_t stack_len,
+ uintptr_t sp_offset,
+ const MappingInfo& mapping);
+
+ PageAllocator* allocator() { return &allocator_; }
+
+ // Copy content of |length| bytes from a given process |child|,
+ // starting from |src|, into |dest|. Returns true on success.
+ virtual bool CopyFromProcess(void* dest, pid_t child, const void* src,
+ size_t length) = 0;
+
+ // Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
+ // |path| is a character array of at least NAME_MAX bytes to return the
+ // result.|node| is the final node without any slashes. Returns true on
+ // success.
+ virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const = 0;
+
+ // Generate a File ID from the .text section of a mapped entry.
+ // If not a member, mapping_id is ignored. This method can also manipulate the
+ // |mapping|.name to truncate "(deleted)" from the file name if necessary.
+ bool ElfFileIdentifierForMapping(const MappingInfo& mapping,
+ bool member,
+ unsigned int mapping_id,
+ wasteful_vector<uint8_t>& identifier);
+
+ void SetCrashInfoFromSigInfo(const siginfo_t& siginfo);
+
+ uintptr_t crash_address() const { return crash_address_; }
+ void set_crash_address(uintptr_t crash_address) {
+ crash_address_ = crash_address;
+ }
+
+ int crash_signal() const { return crash_signal_; }
+ void set_crash_signal(int crash_signal) { crash_signal_ = crash_signal; }
+ const char* GetCrashSignalString() const;
+
+ void set_crash_signal_code(int code) { crash_signal_code_ = code; }
+ int crash_signal_code() const { return crash_signal_code_; }
+
+ void set_crash_exception_info(const std::vector<uint64_t>& exception_info) {
+ assert(exception_info.size() <= MD_EXCEPTION_MAXIMUM_PARAMETERS);
+ crash_exception_info_ = exception_info;
+ }
+ const std::vector<uint64_t>& crash_exception_info() const {
+ return crash_exception_info_;
+ }
+
+ pid_t pid() const { return pid_; }
+ pid_t crash_thread() const { return crash_thread_; }
+ void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
+
+ // Concatenates the |root_prefix_| and |mapping| path. Writes into |path| and
+ // returns true unless the string is too long.
+ bool GetMappingAbsolutePath(const MappingInfo& mapping,
+ char path[PATH_MAX]) const;
+
+ // Extracts the effective path and file name of from |mapping|. In most cases
+ // the effective name/path are just the mapping's path and basename. In some
+ // other cases, however, a library can be mapped from an archive (e.g., when
+ // loading .so libs from an apk on Android) and this method is able to
+ // reconstruct the original file name.
+ void GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
+ char* file_path,
+ size_t file_path_size,
+ char* file_name,
+ size_t file_name_size);
+
+ protected:
+ bool ReadAuxv();
+
+ virtual bool EnumerateMappings();
+
+ virtual bool EnumerateThreads() = 0;
+
+ // For the case where a running program has been deleted, it'll show up in
+ // /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then
+ // see if '/path/to/program (deleted)' matches /proc/pid/exe and return
+ // /proc/pid/exe in |path| so ELF identifier generation works correctly. This
+ // also checks to see if '/path/to/program (deleted)' exists, so it does not
+ // get fooled by a poorly named binary.
+ // For programs that don't end with ' (deleted)', this is a no-op.
+ // This assumes |path| is a buffer with length NAME_MAX.
+ // Returns true if |path| is modified.
+ bool HandleDeletedFileInMapping(char* path) const;
+
+ // ID of the crashed process.
+ const pid_t pid_;
+
+ // Path of the root directory to which mapping paths are relative.
+ const char* const root_prefix_;
+
+ // Virtual address at which the process crashed.
+ uintptr_t crash_address_;
+
+ // Signal that terminated the crashed process.
+ int crash_signal_;
+
+ // The code associated with |crash_signal_|.
+ int crash_signal_code_;
+
+ // The additional fields associated with |crash_signal_|.
+ std::vector<uint64_t> crash_exception_info_;
+
+ // ID of the crashed thread.
+ pid_t crash_thread_;
+
+ mutable PageAllocator allocator_;
+
+ // IDs of all the threads.
+ wasteful_vector<pid_t> threads_;
+
+ // Info from /proc/<pid>/maps.
+ wasteful_vector<MappingInfo*> mappings_;
+
+ // Info from /proc/<pid>/auxv
+ wasteful_vector<elf_aux_val_t> auxv_;
+
+private:
+ bool IsIPCSharedMemorySegment(const char* name);
+
+#if defined(__ANDROID__)
+ // Android M and later support packed ELF relocations in shared libraries.
+ // Packing relocations changes the vaddr of the LOAD segments, such that
+ // the effective load bias is no longer the same as the start address of
+ // the memory mapping containing the executable parts of the library. The
+ // packing is applied to the stripped library run on the target, but not to
+ // any other library, and in particular not to the library used to generate
+ // breakpad symbols. As a result, we need to adjust the |start_addr| for
+ // any mapping that results from a shared library that contains Android
+ // packed relocations, so that it properly represents the effective library
+ // load bias. The following functions support this adjustment.
+
+ // Check that a given mapping at |start_addr| is for an ELF shared library.
+ // If it is, place the ELF header in |ehdr| and return true.
+ // The first LOAD segment in an ELF shared library has offset zero, so the
+ // ELF file header is at the start of this map entry, and in already mapped
+ // memory.
+ bool GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr);
+
+ // For the ELF file mapped at |start_addr|, iterate ELF program headers to
+ // find the min vaddr of all program header LOAD segments, the vaddr for
+ // the DYNAMIC segment, and a count of DYNAMIC entries. Return values in
+ // |min_vaddr_ptr|, |dyn_vaddr_ptr|, and |dyn_count_ptr|.
+ // The program header table is also in already mapped memory.
+ void ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr,
+ uintptr_t start_addr,
+ uintptr_t* min_vaddr_ptr,
+ uintptr_t* dyn_vaddr_ptr,
+ size_t* dyn_count_ptr);
+
+ // Search the DYNAMIC tags for the ELF file with the given |load_bias|, and
+ // return true if the tags indicate that the file contains Android packed
+ // relocations. Dynamic tags are found at |dyn_vaddr| past the |load_bias|.
+ bool HasAndroidPackedRelocations(uintptr_t load_bias,
+ uintptr_t dyn_vaddr,
+ size_t dyn_count);
+
+ // If the ELF file mapped at |start_addr| contained Android packed
+ // relocations, return the load bias that the system linker (or Chromium
+ // crazy linker) will have used. If the file did not contain Android
+ // packed relocations, returns |start_addr|, indicating that no adjustment
+ // is necessary.
+ // The effective load bias is |start_addr| adjusted downwards by the
+ // min vaddr in the library LOAD segments.
+ uintptr_t GetEffectiveLoadBias(ElfW(Ehdr)* ehdr, uintptr_t start_addr);
+
+ // Called from LateInit(). Iterates |mappings_| and rewrites the |start_addr|
+ // field of any that represent ELF shared libraries with Android packed
+ // relocations, so that |start_addr| is the load bias that the system linker
+ // (or Chromium crazy linker) used. This value matches the addresses produced
+ // when the non-relocation-packed library is used for breakpad symbol
+ // generation.
+ void LatePostprocessMappings();
+#endif // __ANDROID__
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper_unittest_helper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper_unittest_helper.cc
new file mode 100644
index 0000000000..3ad48e5015
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper_unittest_helper.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Helper program for the linux_dumper class, which creates a bunch of
+// threads. The first word of each thread's stack is set to the thread
+// id.
+
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "common/scoped_ptr.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+#if defined(__ARM_EABI__)
+#define TID_PTR_REGISTER "r3"
+#elif defined(__aarch64__)
+#define TID_PTR_REGISTER "x3"
+#elif defined(__i386)
+#define TID_PTR_REGISTER "ecx"
+#elif defined(__x86_64)
+#define TID_PTR_REGISTER "rcx"
+#elif defined(__mips__)
+#define TID_PTR_REGISTER "$1"
+#else
+#error This test has not been ported to this platform.
+#endif
+
+void *thread_function(void *data) {
+ int pipefd = *static_cast<int *>(data);
+ volatile pid_t* thread_id = new pid_t;
+ *thread_id = syscall(__NR_gettid);
+ // Signal parent that a thread has started.
+ uint8_t byte = 1;
+ if (write(pipefd, &byte, sizeof(byte)) != sizeof(byte)) {
+ perror("ERROR: parent notification failed");
+ return NULL;
+ }
+ register volatile pid_t *thread_id_ptr asm(TID_PTR_REGISTER) = thread_id;
+ while (true)
+ asm volatile ("" : : "r" (thread_id_ptr));
+ return NULL;
+}
+
+int main(int argc, char *argv[]) {
+ if (argc < 3) {
+ fprintf(stderr,
+ "usage: linux_dumper_unittest_helper <pipe fd> <# of threads>\n");
+ return 1;
+ }
+ int pipefd = atoi(argv[1]);
+ int num_threads = atoi(argv[2]);
+ if (num_threads < 1) {
+ fprintf(stderr, "ERROR: number of threads is 0");
+ return 1;
+ }
+ google_breakpad::scoped_array<pthread_t> threads(new pthread_t[num_threads]);
+ pthread_attr_t thread_attributes;
+ pthread_attr_init(&thread_attributes);
+ pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED);
+ for (int i = 1; i < num_threads; i++) {
+ pthread_create(&threads[i], &thread_attributes, &thread_function, &pipefd);
+ }
+ thread_function(&pipefd);
+ return 0;
+}
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.cc
new file mode 100644
index 0000000000..6ed70eeb61
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.cc
@@ -0,0 +1,403 @@
+// Copyright (c) 2012, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// linux_ptrace_dumper.cc: Implement google_breakpad::LinuxPtraceDumper.
+// See linux_ptrace_dumper.h for detals.
+// This class was originally splitted from google_breakpad::LinuxDumper.
+
+// This code deals with the mechanics of getting information about a crashed
+// process. Since this code may run in a compromised address space, the same
+// rules apply as detailed at the top of minidump_writer.h: no libc calls and
+// use the alternative allocator.
+
+#include "linux/minidump_writer/linux_ptrace_dumper.h"
+
+#include <asm/ptrace.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ptrace.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#if defined(__i386)
+#include <cpuid.h>
+#endif
+
+#include "linux/minidump_writer/directory_reader.h"
+#include "linux/minidump_writer/line_reader.h"
+#include "common/linux/linux_libc_support.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+// Suspends a thread by attaching to it.
+static bool SuspendThread(pid_t pid) {
+ // This may fail if the thread has just died or debugged.
+ errno = 0;
+ if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
+ errno != 0) {
+ return false;
+ }
+ while (sys_waitpid(pid, NULL, __WALL) < 0) {
+ if (errno != EINTR) {
+ sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
+ return false;
+ }
+ }
+#if defined(__i386) || defined(__x86_64)
+ // On x86, the stack pointer is NULL or -1, when executing trusted code in
+ // the seccomp sandbox. Not only does this cause difficulties down the line
+ // when trying to dump the thread's stack, it also results in the minidumps
+ // containing information about the trusted threads. This information is
+ // generally completely meaningless and just pollutes the minidumps.
+ // We thus test the stack pointer and exclude any threads that are part of
+ // the seccomp sandbox's trusted code.
+ user_regs_struct regs;
+ if (sys_ptrace(PTRACE_GETREGS, pid, NULL, &regs) == -1 ||
+#if defined(__i386)
+ !regs.esp
+#elif defined(__x86_64)
+ !regs.rsp
+#endif
+ ) {
+ sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
+ return false;
+ }
+#endif
+ return true;
+}
+
+// Resumes a thread by detaching from it.
+static bool ResumeThread(pid_t pid) {
+ return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
+}
+
+namespace google_breakpad {
+
+LinuxPtraceDumper::LinuxPtraceDumper(pid_t pid)
+ : LinuxDumper(pid),
+ threads_suspended_(false) {
+}
+
+bool LinuxPtraceDumper::BuildProcPath(char* path, pid_t pid,
+ const char* node) const {
+ if (!path || !node || pid <= 0)
+ return false;
+
+ size_t node_len = my_strlen(node);
+ if (node_len == 0)
+ return false;
+
+ const unsigned pid_len = my_uint_len(pid);
+ const size_t total_length = 6 + pid_len + 1 + node_len;
+ if (total_length >= NAME_MAX)
+ return false;
+
+ my_memcpy(path, "/proc/", 6);
+ my_uitos(path + 6, pid, pid_len);
+ path[6 + pid_len] = '/';
+ my_memcpy(path + 6 + pid_len + 1, node, node_len);
+ path[total_length] = '\0';
+ return true;
+}
+
+bool LinuxPtraceDumper::CopyFromProcess(void* dest, pid_t child,
+ const void* src, size_t length) {
+ unsigned long tmp = 55;
+ size_t done = 0;
+ static const size_t word_size = sizeof(tmp);
+ uint8_t* const local = (uint8_t*) dest;
+ uint8_t* const remote = (uint8_t*) src;
+
+ while (done < length) {
+ const size_t l = (length - done > word_size) ? word_size : (length - done);
+ if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) {
+ tmp = 0;
+ }
+ my_memcpy(local + done, &tmp, l);
+ done += l;
+ }
+ return true;
+}
+
+bool LinuxPtraceDumper::ReadRegisterSet(ThreadInfo* info, pid_t tid)
+{
+#ifdef PTRACE_GETREGSET
+ struct iovec io;
+ info->GetGeneralPurposeRegisters(&io.iov_base, &io.iov_len);
+ if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, (void*)&io) == -1) {
+ return false;
+ }
+
+ info->GetFloatingPointRegisters(&io.iov_base, &io.iov_len);
+ if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, (void*)&io) == -1) {
+ return false;
+ }
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool LinuxPtraceDumper::ReadRegisters(ThreadInfo* info, pid_t tid) {
+#ifdef PTRACE_GETREGS
+ void* gp_addr;
+ info->GetGeneralPurposeRegisters(&gp_addr, NULL);
+ if (sys_ptrace(PTRACE_GETREGS, tid, NULL, gp_addr) == -1) {
+ return false;
+ }
+
+#if !(defined(__ANDROID__) && defined(__ARM_EABI__))
+ // When running an arm build on an arm64 device, attempting to get the
+ // floating point registers fails. On Android, the floating point registers
+ // aren't written to the cpu context anyway, so just don't get them here.
+ // See http://crbug.com/508324
+ void* fp_addr;
+ info->GetFloatingPointRegisters(&fp_addr, NULL);
+ if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, fp_addr) == -1) {
+ return false;
+ }
+#endif // !(defined(__ANDROID__) && defined(__ARM_EABI__))
+ return true;
+#else // PTRACE_GETREGS
+ return false;
+#endif
+}
+
+// Read thread info from /proc/$pid/status.
+// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
+// these members are set to -1. Returns true iff all three members are
+// available.
+bool LinuxPtraceDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
+ if (index >= threads_.size())
+ return false;
+
+ pid_t tid = threads_[index];
+
+ assert(info != NULL);
+ char status_path[NAME_MAX];
+ if (!BuildProcPath(status_path, tid, "status"))
+ return false;
+
+ const int fd = sys_open(status_path, O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+
+ LineReader* const line_reader = new(allocator_) LineReader(fd);
+ const char* line;
+ unsigned line_len;
+
+ info->ppid = info->tgid = -1;
+
+ while (line_reader->GetNextLine(&line, &line_len)) {
+ if (my_strncmp("Tgid:\t", line, 6) == 0) {
+ my_strtoui(&info->tgid, line + 6);
+ } else if (my_strncmp("PPid:\t", line, 6) == 0) {
+ my_strtoui(&info->ppid, line + 6);
+ }
+
+ line_reader->PopLine(line_len);
+ }
+ sys_close(fd);
+
+ if (info->ppid == -1 || info->tgid == -1)
+ return false;
+
+ if (!ReadRegisterSet(info, tid)) {
+ if (!ReadRegisters(info, tid)) {
+ return false;
+ }
+ }
+
+#if defined(__i386)
+#if !defined(bit_FXSAVE) // e.g. Clang
+#define bit_FXSAVE bit_FXSR
+#endif
+ // Detect if the CPU supports the FXSAVE/FXRSTOR instructions
+ int eax, ebx, ecx, edx;
+ __cpuid(1, eax, ebx, ecx, edx);
+ if (edx & bit_FXSAVE) {
+ if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) {
+ return false;
+ }
+ } else {
+ memset(&info->fpxregs, 0, sizeof(info->fpxregs));
+ }
+#endif // defined(__i386)
+
+#if defined(__i386) || defined(__x86_64)
+ for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
+ if (sys_ptrace(
+ PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*> (offsetof(struct user,
+ u_debugreg[0]) + i *
+ sizeof(debugreg_t)),
+ &info->dregs[i]) == -1) {
+ return false;
+ }
+ }
+#endif
+
+#if defined(__mips__)
+ sys_ptrace(PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*>(PC), &info->mcontext.pc);
+ sys_ptrace(PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*>(DSP_BASE), &info->mcontext.hi1);
+ sys_ptrace(PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*>(DSP_BASE + 1), &info->mcontext.lo1);
+ sys_ptrace(PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*>(DSP_BASE + 2), &info->mcontext.hi2);
+ sys_ptrace(PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*>(DSP_BASE + 3), &info->mcontext.lo2);
+ sys_ptrace(PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*>(DSP_BASE + 4), &info->mcontext.hi3);
+ sys_ptrace(PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*>(DSP_BASE + 5), &info->mcontext.lo3);
+ sys_ptrace(PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*>(DSP_CONTROL), &info->mcontext.dsp);
+#endif
+
+ const uint8_t* stack_pointer;
+#if defined(__i386)
+ my_memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
+#elif defined(__x86_64)
+ my_memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
+#elif defined(__ARM_EABI__)
+ my_memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
+#elif defined(__aarch64__)
+ my_memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp));
+#elif defined(__mips__)
+ stack_pointer =
+ reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]);
+#else
+#error "This code hasn't been ported to your platform yet."
+#endif
+ info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer);
+
+ return true;
+}
+
+bool LinuxPtraceDumper::GetThreadNameByIndex(size_t index, char* name,
+ size_t size) {
+ if (index >= threads_.size())
+ return false;
+
+ pid_t tid = threads_[index];
+
+ assert(name != NULL);
+ char path[NAME_MAX];
+
+ // Read the thread name (aka comm entry in /proc)
+ if (!BuildProcPath(path, tid, "comm"))
+ return false;
+
+ const int fd = sys_open(path, O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+
+ const int len = sys_read(fd, name, size);
+ if (len > 0)
+ name[len - 1] = '\0'; // Get rid of the newline
+
+ sys_close(fd);
+
+ return len > 0;
+}
+
+bool LinuxPtraceDumper::IsPostMortem() const {
+ return false;
+}
+
+bool LinuxPtraceDumper::ThreadsSuspend() {
+ if (threads_suspended_)
+ return true;
+ for (size_t i = 0; i < threads_.size(); ++i) {
+ if (!SuspendThread(threads_[i])) {
+ // If the thread either disappeared before we could attach to it, or if
+ // it was part of the seccomp sandbox's trusted code, it is OK to
+ // silently drop it from the minidump.
+ if (i < threads_.size() - 1) {
+ my_memmove(&threads_[i], &threads_[i + 1],
+ (threads_.size() - i - 1) * sizeof(threads_[i]));
+ }
+ threads_.resize(threads_.size() - 1);
+ --i;
+ }
+ }
+ threads_suspended_ = true;
+ return !threads_.empty();
+}
+
+bool LinuxPtraceDumper::ThreadsResume() {
+ if (!threads_suspended_)
+ return false;
+ bool good = true;
+ for (size_t i = 0; i < threads_.size(); ++i)
+ good &= ResumeThread(threads_[i]);
+ threads_suspended_ = false;
+ return good;
+}
+
+// Parse /proc/$pid/task to list all the threads of the process identified by
+// pid.
+bool LinuxPtraceDumper::EnumerateThreads() {
+ char task_path[NAME_MAX];
+ if (!BuildProcPath(task_path, pid_, "task"))
+ return false;
+
+ const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
+ if (fd < 0)
+ return false;
+ DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
+
+ // The directory may contain duplicate entries which we filter by assuming
+ // that they are consecutive.
+ int last_tid = -1;
+ const char* dent_name;
+ while (dir_reader->GetNextEntry(&dent_name)) {
+ if (my_strcmp(dent_name, ".") &&
+ my_strcmp(dent_name, "..")) {
+ int tid = 0;
+ if (my_strtoui(&tid, dent_name) &&
+ last_tid != tid) {
+ last_tid = tid;
+ threads_.push_back(tid);
+ }
+ }
+ dir_reader->PopEntry();
+ }
+
+ sys_close(fd);
+ return true;
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.h
new file mode 100644
index 0000000000..576098c349
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2012, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// linux_ptrace_dumper.h: Define the google_breakpad::LinuxPtraceDumper
+// class, which is derived from google_breakpad::LinuxDumper to extract
+// information from a crashed process via ptrace.
+// This class was originally splitted from google_breakpad::LinuxDumper.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_
+
+#include "linux/minidump_writer/linux_dumper.h"
+
+namespace google_breakpad {
+
+class LinuxPtraceDumper : public LinuxDumper {
+ public:
+ // Constructs a dumper for extracting information of a given process
+ // with a process ID of |pid|.
+ explicit LinuxPtraceDumper(pid_t pid);
+
+ // Implements LinuxDumper::BuildProcPath().
+ // Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
+ // |path| is a character array of at least NAME_MAX bytes to return the
+ // result. |node| is the final node without any slashes. Returns true on
+ // success.
+ virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const;
+
+ // Implements LinuxDumper::CopyFromProcess().
+ // Copies content of |length| bytes from a given process |child|,
+ // starting from |src|, into |dest|. This method uses ptrace to extract
+ // the content from the target process. Always returns true.
+ virtual bool CopyFromProcess(void* dest, pid_t child, const void* src,
+ size_t length);
+
+ // Implements LinuxDumper::GetThreadInfoByIndex().
+ // Reads information about the |index|-th thread of |threads_|.
+ // Returns true on success. One must have called |ThreadsSuspend| first.
+ virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info);
+
+ // Implements LinuxDumper::GetThreadNameByIndex().
+ // Reads the name of the |index|-th thread of |threads_|.
+ // Returns true on success. One must have called |ThreadsSuspend| first.
+ virtual bool GetThreadNameByIndex(size_t index, char* name, size_t size);
+
+ // Implements LinuxDumper::IsPostMortem().
+ // Always returns false to indicate this dumper performs a dump of
+ // a crashed process via ptrace.
+ virtual bool IsPostMortem() const;
+
+ // Implements LinuxDumper::ThreadsSuspend().
+ // Suspends all threads in the given process. Returns true on success.
+ virtual bool ThreadsSuspend();
+
+ // Implements LinuxDumper::ThreadsResume().
+ // Resumes all threads in the given process. Returns true on success.
+ virtual bool ThreadsResume();
+
+ protected:
+ // Implements LinuxDumper::EnumerateThreads().
+ // Enumerates all threads of the given process into |threads_|.
+ virtual bool EnumerateThreads();
+
+ private:
+ // Set to true if all threads of the crashed process are suspended.
+ bool threads_suspended_;
+
+ // Read the tracee's registers on kernel with PTRACE_GETREGSET support.
+ // Returns false if PTRACE_GETREGSET is not defined.
+ // Returns true on success.
+ bool ReadRegisterSet(ThreadInfo* info, pid_t tid);
+
+ // Read the tracee's registers on kernel with PTRACE_GETREGS support.
+ // Returns true on success.
+ bool ReadRegisters(ThreadInfo* info, pid_t tid);
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_LINUX_PTRACE_DUMPER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
new file mode 100644
index 0000000000..79d26a1e31
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
@@ -0,0 +1,580 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// linux_ptrace_dumper_unittest.cc:
+// Unit tests for google_breakpad::LinuxPtraceDumper.
+//
+// This file was renamed from linux_dumper_unittest.cc and modified due
+// to LinuxDumper being splitted into two classes.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "linux/minidump_writer/linux_ptrace_dumper.h"
+#include "linux/minidump_writer/minidump_writer_unittest_utils.h"
+#include "common/linux/eintr_wrapper.h"
+#include "common/linux/file_id.h"
+#include "common/linux/ignore_ret.h"
+#include "common/linux/safe_readlink.h"
+#include "common/memory_allocator.h"
+#include "common/using_std_string.h"
+
+#ifndef PR_SET_PTRACER
+#define PR_SET_PTRACER 0x59616d61
+#endif
+
+using namespace google_breakpad;
+
+namespace {
+
+pid_t SetupChildProcess(int number_of_threads) {
+ char kNumberOfThreadsArgument[2];
+ sprintf(kNumberOfThreadsArgument, "%d", number_of_threads);
+
+ int fds[2];
+ EXPECT_NE(-1, pipe(fds));
+
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+ close(fds[0]);
+
+ string helper_path(GetHelperBinary());
+ if (helper_path.empty()) {
+ fprintf(stderr, "Couldn't find helper binary\n");
+ _exit(1);
+ }
+
+ // Pass the pipe fd and the number of threads as arguments.
+ char pipe_fd_string[8];
+ sprintf(pipe_fd_string, "%d", fds[1]);
+ execl(helper_path.c_str(),
+ "linux_dumper_unittest_helper",
+ pipe_fd_string,
+ kNumberOfThreadsArgument,
+ NULL);
+ // Kill if we get here.
+ printf("Errno from exec: %d", errno);
+ std::string err_str = "Exec of " + helper_path + " failed";
+ perror(err_str.c_str());
+ _exit(1);
+ }
+ close(fds[1]);
+
+ // Wait for all child threads to indicate that they have started
+ for (int threads = 0; threads < number_of_threads; threads++) {
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
+ EXPECT_EQ(1, r);
+ EXPECT_TRUE(pfd.revents & POLLIN);
+ uint8_t junk;
+ EXPECT_EQ(read(fds[0], &junk, sizeof(junk)),
+ static_cast<ssize_t>(sizeof(junk)));
+ }
+ close(fds[0]);
+
+ // There is a race here because we may stop a child thread before
+ // it is actually running the busy loop. Empirically this sleep
+ // is sufficient to avoid the race.
+ usleep(100000);
+ return child_pid;
+}
+
+typedef wasteful_vector<uint8_t> id_vector;
+typedef testing::Test LinuxPtraceDumperTest;
+
+/* Fixture for running tests in a child process. */
+class LinuxPtraceDumperChildTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ child_pid_ = fork();
+#ifndef __ANDROID__
+ prctl(PR_SET_PTRACER, child_pid_);
+#endif
+ }
+
+ /* Gtest is calling TestBody from this class, which sets up a child
+ * process in which the RealTestBody virtual member is called.
+ * As such, TestBody is not supposed to be overridden in derived classes.
+ */
+ virtual void TestBody() /* final */ {
+ if (child_pid_ == 0) {
+ // child process
+ RealTestBody();
+ _exit(HasFatalFailure() ? kFatalFailure :
+ (HasNonfatalFailure() ? kNonFatalFailure : 0));
+ }
+
+ ASSERT_TRUE(child_pid_ > 0);
+ int status;
+ waitpid(child_pid_, &status, 0);
+ if (WEXITSTATUS(status) == kFatalFailure) {
+ GTEST_FATAL_FAILURE_("Test failed in child process");
+ } else if (WEXITSTATUS(status) == kNonFatalFailure) {
+ GTEST_NONFATAL_FAILURE_("Test failed in child process");
+ }
+ }
+
+ /* Gtest defines TestBody functions through its macros, but classes
+ * derived from this one need to define RealTestBody instead.
+ * This is achieved by defining a TestBody macro further below.
+ */
+ virtual void RealTestBody() = 0;
+
+ id_vector make_vector() {
+ return id_vector(&allocator, kDefaultBuildIdSize);
+ }
+
+ private:
+ static const int kFatalFailure = 1;
+ static const int kNonFatalFailure = 2;
+
+ pid_t child_pid_;
+ PageAllocator allocator;
+};
+
+} // namespace
+
+/* Replace TestBody declarations within TEST*() with RealTestBody
+ * declarations */
+#define TestBody RealTestBody
+
+TEST_F(LinuxPtraceDumperChildTest, Setup) {
+ LinuxPtraceDumper dumper(getppid());
+}
+
+TEST_F(LinuxPtraceDumperChildTest, FindMappings) {
+ LinuxPtraceDumper dumper(getppid());
+ ASSERT_TRUE(dumper.Init());
+
+ ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
+ ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
+ ASSERT_FALSE(dumper.FindMapping(NULL));
+}
+
+TEST_F(LinuxPtraceDumperChildTest, ThreadList) {
+ LinuxPtraceDumper dumper(getppid());
+ ASSERT_TRUE(dumper.Init());
+
+ ASSERT_GE(dumper.threads().size(), (size_t)1);
+ bool found = false;
+ for (size_t i = 0; i < dumper.threads().size(); ++i) {
+ if (dumper.threads()[i] == getppid()) {
+ ASSERT_FALSE(found);
+ found = true;
+ }
+ }
+ ASSERT_TRUE(found);
+}
+
+// Helper stack class to close a file descriptor and unmap
+// a mmap'ed mapping.
+class StackHelper {
+ public:
+ StackHelper()
+ : fd_(-1), mapping_(NULL), size_(0) {}
+ ~StackHelper() {
+ if (size_)
+ munmap(mapping_, size_);
+ if (fd_ >= 0)
+ close(fd_);
+ }
+ void Init(int fd, char* mapping, size_t size) {
+ fd_ = fd;
+ mapping_ = mapping;
+ size_ = size;
+ }
+
+ char* mapping() const { return mapping_; }
+ size_t size() const { return size_; }
+
+ private:
+ int fd_;
+ char* mapping_;
+ size_t size_;
+};
+
+class LinuxPtraceDumperMappingsTest : public LinuxPtraceDumperChildTest {
+ protected:
+ virtual void SetUp();
+
+ string helper_path_;
+ size_t page_size_;
+ StackHelper helper_;
+};
+
+void LinuxPtraceDumperMappingsTest::SetUp() {
+ helper_path_ = GetHelperBinary();
+ if (helper_path_.empty()) {
+ FAIL() << "Couldn't find helper binary";
+ _exit(1);
+ }
+
+ // mmap two segments out of the helper binary, one
+ // enclosed in the other, but with different protections.
+ page_size_ = sysconf(_SC_PAGESIZE);
+ const size_t kMappingSize = 3 * page_size_;
+ int fd = open(helper_path_.c_str(), O_RDONLY);
+ ASSERT_NE(-1, fd) << "Failed to open file: " << helper_path_
+ << ", Error: " << strerror(errno);
+ char* mapping =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMappingSize,
+ PROT_READ,
+ MAP_SHARED,
+ fd,
+ 0));
+ ASSERT_TRUE(mapping);
+
+ // Ensure that things get cleaned up.
+ helper_.Init(fd, mapping, kMappingSize);
+
+ // Carve a page out of the first mapping with different permissions.
+ char* inside_mapping = reinterpret_cast<char*>(
+ mmap(mapping + 2 * page_size_,
+ page_size_,
+ PROT_NONE,
+ MAP_SHARED | MAP_FIXED,
+ fd,
+ // Map a different offset just to
+ // better test real-world conditions.
+ page_size_));
+ ASSERT_TRUE(inside_mapping);
+
+ LinuxPtraceDumperChildTest::SetUp();
+}
+
+TEST_F(LinuxPtraceDumperMappingsTest, MergedMappings) {
+ // Now check that LinuxPtraceDumper interpreted the mappings properly.
+ LinuxPtraceDumper dumper(getppid());
+ ASSERT_TRUE(dumper.Init());
+ int mapping_count = 0;
+ for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
+ const MappingInfo& mapping = *dumper.mappings()[i];
+ if (strcmp(mapping.name, this->helper_path_.c_str()) == 0) {
+ // This mapping should encompass the entire original mapped
+ // range.
+ EXPECT_EQ(reinterpret_cast<uintptr_t>(this->helper_.mapping()),
+ mapping.start_addr);
+ EXPECT_EQ(this->helper_.size(), mapping.size);
+ EXPECT_EQ(0U, mapping.offset);
+ mapping_count++;
+ }
+ }
+ EXPECT_EQ(1, mapping_count);
+}
+
+TEST_F(LinuxPtraceDumperChildTest, BuildProcPath) {
+ const pid_t pid = getppid();
+ LinuxPtraceDumper dumper(pid);
+
+ char maps_path[NAME_MAX] = "";
+ char maps_path_expected[NAME_MAX];
+ snprintf(maps_path_expected, sizeof(maps_path_expected),
+ "/proc/%d/maps", pid);
+ EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
+ EXPECT_STREQ(maps_path_expected, maps_path);
+
+ EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
+ EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps"));
+ EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
+ EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
+
+ char long_node[NAME_MAX];
+ size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1;
+ memset(long_node, 'a', long_node_len);
+ long_node[long_node_len] = '\0';
+ EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node));
+}
+
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+// Ensure that the linux-gate VDSO is included in the mapping list.
+TEST_F(LinuxPtraceDumperChildTest, MappingsIncludeLinuxGate) {
+ LinuxPtraceDumper dumper(getppid());
+ ASSERT_TRUE(dumper.Init());
+
+ void* linux_gate_loc =
+ reinterpret_cast<void *>(dumper.auxv()[AT_SYSINFO_EHDR]);
+ ASSERT_TRUE(linux_gate_loc);
+ bool found_linux_gate = false;
+
+ const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
+ const MappingInfo* mapping;
+ for (unsigned i = 0; i < mappings.size(); ++i) {
+ mapping = mappings[i];
+ if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
+ found_linux_gate = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found_linux_gate);
+ EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
+ EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
+}
+
+// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
+TEST_F(LinuxPtraceDumperChildTest, LinuxGateMappingID) {
+ LinuxPtraceDumper dumper(getppid());
+ ASSERT_TRUE(dumper.Init());
+
+ bool found_linux_gate = false;
+ const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
+ unsigned index = 0;
+ for (unsigned i = 0; i < mappings.size(); ++i) {
+ if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
+ found_linux_gate = true;
+ index = i;
+ break;
+ }
+ }
+ ASSERT_TRUE(found_linux_gate);
+
+ // Need to suspend the child so ptrace actually works.
+ ASSERT_TRUE(dumper.ThreadsSuspend());
+ id_vector identifier(make_vector());
+ ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
+ true,
+ index,
+ identifier));
+
+ id_vector empty_identifier(make_vector());
+ empty_identifier.resize(kDefaultBuildIdSize, 0);
+ EXPECT_NE(empty_identifier, identifier);
+ EXPECT_TRUE(dumper.ThreadsResume());
+}
+#endif
+
+TEST_F(LinuxPtraceDumperChildTest, FileIDsMatch) {
+ // Calculate the File ID of our binary using both
+ // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
+ // and ensure that we get the same result from both.
+ char exe_name[PATH_MAX];
+ ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
+
+ LinuxPtraceDumper dumper(getppid());
+ ASSERT_TRUE(dumper.Init());
+ const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
+ bool found_exe = false;
+ unsigned i;
+ for (i = 0; i < mappings.size(); ++i) {
+ const MappingInfo* mapping = mappings[i];
+ if (!strcmp(mapping->name, exe_name)) {
+ found_exe = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(found_exe);
+
+ id_vector identifier1(make_vector());
+ id_vector identifier2(make_vector());
+ EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
+ identifier1));
+ FileID fileid(exe_name);
+ EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
+
+ string identifier_string1 =
+ FileID::ConvertIdentifierToUUIDString(identifier1);
+ string identifier_string2 =
+ FileID::ConvertIdentifierToUUIDString(identifier2);
+ EXPECT_EQ(identifier_string1, identifier_string2);
+}
+
+/* Get back to normal behavior of TEST*() macros wrt TestBody. */
+#undef TestBody
+
+TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
+ static const size_t kNumberOfThreadsInHelperProgram = 5;
+
+ pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
+ ASSERT_NE(child_pid, -1);
+
+ // Children are ready now.
+ LinuxPtraceDumper dumper(child_pid);
+ ASSERT_TRUE(dumper.Init());
+#if defined(THREAD_SANITIZER)
+ EXPECT_GE(dumper.threads().size(), (size_t)kNumberOfThreadsInHelperProgram);
+#else
+ EXPECT_EQ(dumper.threads().size(), (size_t)kNumberOfThreadsInHelperProgram);
+#endif
+ EXPECT_TRUE(dumper.ThreadsSuspend());
+
+ ThreadInfo one_thread;
+ size_t matching_threads = 0;
+ for (size_t i = 0; i < dumper.threads().size(); ++i) {
+ EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
+ const void* stack;
+ size_t stack_len;
+ EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len,
+ one_thread.stack_pointer));
+ // In the helper program, we stored a pointer to the thread id in a
+ // specific register. Check that we can recover its value.
+#if defined(__ARM_EABI__)
+ pid_t* process_tid_location = (pid_t*)(one_thread.regs.uregs[3]);
+#elif defined(__aarch64__)
+ pid_t* process_tid_location = (pid_t*)(one_thread.regs.regs[3]);
+#elif defined(__i386)
+ pid_t* process_tid_location = (pid_t*)(one_thread.regs.ecx);
+#elif defined(__x86_64)
+ pid_t* process_tid_location = (pid_t*)(one_thread.regs.rcx);
+#elif defined(__mips__)
+ pid_t* process_tid_location =
+ reinterpret_cast<pid_t*>(one_thread.mcontext.gregs[1]);
+#else
+#error This test has not been ported to this platform.
+#endif
+ pid_t one_thread_id;
+ dumper.CopyFromProcess(&one_thread_id,
+ dumper.threads()[i],
+ process_tid_location,
+ 4);
+ matching_threads += (dumper.threads()[i] == one_thread_id) ? 1 : 0;
+ }
+ EXPECT_EQ(matching_threads, kNumberOfThreadsInHelperProgram);
+ EXPECT_TRUE(dumper.ThreadsResume());
+ kill(child_pid, SIGKILL);
+
+ // Reap child
+ int status;
+ ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_EQ(SIGKILL, WTERMSIG(status));
+}
+
+TEST_F(LinuxPtraceDumperTest, SanitizeStackCopy) {
+ static const size_t kNumberOfThreadsInHelperProgram = 1;
+
+ pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
+ ASSERT_NE(child_pid, -1);
+
+ LinuxPtraceDumper dumper(child_pid);
+ ASSERT_TRUE(dumper.Init());
+ EXPECT_TRUE(dumper.ThreadsSuspend());
+
+ ThreadInfo thread_info;
+ EXPECT_TRUE(dumper.GetThreadInfoByIndex(0, &thread_info));
+
+ const uintptr_t defaced =
+#if defined(__LP64__)
+ 0x0defaced0defaced;
+#else
+ 0x0defaced;
+#endif
+
+ uintptr_t simulated_stack[2];
+
+ // Pointers into the stack shouldn't be sanitized.
+ memset(simulated_stack, 0xff, sizeof(simulated_stack));
+ simulated_stack[1] = thread_info.stack_pointer;
+ dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
+ sizeof(simulated_stack), thread_info.stack_pointer,
+ sizeof(uintptr_t));
+ ASSERT_NE(simulated_stack[1], defaced);
+
+ // Memory prior to the stack pointer should be cleared.
+ ASSERT_EQ(simulated_stack[0], 0u);
+
+ // Small integers should not be sanitized.
+ for (int i = -4096; i <= 4096; ++i) {
+ memset(simulated_stack, 0, sizeof(simulated_stack));
+ simulated_stack[0] = static_cast<uintptr_t>(i);
+ dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
+ sizeof(simulated_stack), thread_info.stack_pointer,
+ 0u);
+ ASSERT_NE(simulated_stack[0], defaced);
+ }
+
+ // The instruction pointer definitely should point into an executable mapping.
+ const MappingInfo* mapping_info = dumper.FindMappingNoBias(
+ reinterpret_cast<uintptr_t>(thread_info.GetInstructionPointer()));
+ ASSERT_NE(mapping_info, nullptr);
+ ASSERT_TRUE(mapping_info->exec);
+
+ // Pointers to code shouldn't be sanitized.
+ memset(simulated_stack, 0, sizeof(simulated_stack));
+ simulated_stack[1] = thread_info.GetInstructionPointer();
+ dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
+ sizeof(simulated_stack), thread_info.stack_pointer,
+ 0u);
+ ASSERT_NE(simulated_stack[0], defaced);
+
+ // String fragments should be sanitized.
+ memcpy(simulated_stack, "abcdefghijklmnop", sizeof(simulated_stack));
+ dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
+ sizeof(simulated_stack), thread_info.stack_pointer,
+ 0u);
+ ASSERT_EQ(simulated_stack[0], defaced);
+ ASSERT_EQ(simulated_stack[1], defaced);
+
+ // Heap pointers should be sanititzed.
+#if defined(__ARM_EABI__)
+ uintptr_t heap_addr = thread_info.regs.uregs[3];
+#elif defined(__aarch64__)
+ uintptr_t heap_addr = thread_info.regs.regs[3];
+#elif defined(__i386)
+ uintptr_t heap_addr = thread_info.regs.ecx;
+#elif defined(__x86_64)
+ uintptr_t heap_addr = thread_info.regs.rcx;
+#elif defined(__mips__)
+ uintptr_t heap_addr = thread_info.mcontext.gregs[1];
+#else
+#error This test has not been ported to this platform.
+#endif
+ memset(simulated_stack, 0, sizeof(simulated_stack));
+ simulated_stack[0] = heap_addr;
+ dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
+ sizeof(simulated_stack), thread_info.stack_pointer,
+ 0u);
+ ASSERT_EQ(simulated_stack[0], defaced);
+
+ EXPECT_TRUE(dumper.ThreadsResume());
+ kill(child_pid, SIGKILL);
+
+ // Reap child.
+ int status;
+ ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_EQ(SIGKILL, WTERMSIG(status));
+}
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc
new file mode 100644
index 0000000000..03066e9110
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc
@@ -0,0 +1,1562 @@
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This code writes out minidump files:
+// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx
+//
+// Minidumps are a Microsoft format which Breakpad uses for recording crash
+// dumps. This code has to run in a compromised environment (the address space
+// may have received SIGSEGV), thus the following rules apply:
+// * You may not enter the dynamic linker. This means that we cannot call
+// any symbols in a shared library (inc libc). Because of this we replace
+// libc functions in linux_libc_support.h.
+// * You may not call syscalls via the libc wrappers. This rule is a subset
+// of the first rule but it bears repeating. We have direct wrappers
+// around the system calls in linux_syscall_support.h.
+// * You may not malloc. There's an alternative allocator in memory.h and
+// a canonical instance in the LinuxDumper object. We use the placement
+// new form to allocate objects and we don't delete them.
+
+#include "linux/handler/minidump_descriptor.h"
+#include "linux/minidump_writer/minidump_writer.h"
+#include "minidump_file_writer-inl.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <link.h>
+#include <stdio.h>
+#if defined(__ANDROID__)
+#include <sys/system_properties.h>
+#endif
+#include <sys/types.h>
+#include <sys/ucontext.h>
+#include <sys/user.h>
+#include <sys/utsname.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "linux/dump_writer_common/thread_info.h"
+#include "linux/dump_writer_common/ucontext_reader.h"
+#include "linux/handler/exception_handler.h"
+#include "linux/minidump_writer/cpu_set.h"
+#include "linux/minidump_writer/line_reader.h"
+#include "linux/minidump_writer/linux_dumper.h"
+#include "linux/minidump_writer/linux_ptrace_dumper.h"
+#include "linux/minidump_writer/proc_cpuinfo_reader.h"
+#include "minidump_file_writer.h"
+#include "common/linux/file_id.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/minidump_type_helper.h"
+#include "google_breakpad/common/minidump_format.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+namespace {
+
+using google_breakpad::AppMemoryList;
+using google_breakpad::auto_wasteful_vector;
+using google_breakpad::ExceptionHandler;
+using google_breakpad::CpuSet;
+using google_breakpad::kDefaultBuildIdSize;
+using google_breakpad::LineReader;
+using google_breakpad::LinuxDumper;
+using google_breakpad::LinuxPtraceDumper;
+using google_breakpad::MDTypeHelper;
+using google_breakpad::MappingEntry;
+using google_breakpad::MappingInfo;
+using google_breakpad::MappingList;
+using google_breakpad::MinidumpFileWriter;
+using google_breakpad::PageAllocator;
+using google_breakpad::ProcCpuInfoReader;
+using google_breakpad::RawContextCPU;
+using google_breakpad::ThreadInfo;
+using google_breakpad::TypedMDRVA;
+using google_breakpad::UContextReader;
+using google_breakpad::UntypedMDRVA;
+using google_breakpad::wasteful_vector;
+
+typedef MDTypeHelper<sizeof(void*)>::MDRawDebug MDRawDebug;
+typedef MDTypeHelper<sizeof(void*)>::MDRawLinkMap MDRawLinkMap;
+
+class MinidumpWriter {
+ public:
+ // The following kLimit* constants are for when minidump_size_limit_ is set
+ // and the minidump size might exceed it.
+ //
+ // Estimate for how big each thread's stack will be (in bytes).
+ static const unsigned kLimitAverageThreadStackLength = 8 * 1024;
+ // Number of threads whose stack size we don't want to limit. These base
+ // threads will simply be the first N threads returned by the dumper (although
+ // the crashing thread will never be limited). Threads beyond this count are
+ // the extra threads.
+ static const unsigned kLimitBaseThreadCount = 20;
+ // Maximum stack size to dump for any extra thread (in bytes).
+ static const unsigned kLimitMaxExtraThreadStackLen = 2 * 1024;
+ // Make sure this number of additional bytes can fit in the minidump
+ // (exclude the stack data).
+ static const unsigned kLimitMinidumpFudgeFactor = 64 * 1024;
+
+ MinidumpWriter(const char* minidump_path,
+ int minidump_fd,
+ const ExceptionHandler::CrashContext* context,
+ const MappingList& mappings,
+ const AppMemoryList& appmem,
+ bool skip_stacks_if_mapping_unreferenced,
+ uintptr_t principal_mapping_address,
+ bool sanitize_stacks,
+ LinuxDumper* dumper)
+ : fd_(minidump_fd),
+ path_(minidump_path),
+ ucontext_(context ? &context->context : NULL),
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ float_state_(context ? &context->float_state : NULL),
+#endif
+ dumper_(dumper),
+ minidump_size_limit_(-1),
+ memory_blocks_(dumper_->allocator()),
+ mapping_list_(mappings),
+ app_memory_list_(appmem),
+ skip_stacks_if_mapping_unreferenced_(
+ skip_stacks_if_mapping_unreferenced),
+ principal_mapping_address_(principal_mapping_address),
+ principal_mapping_(nullptr),
+ sanitize_stacks_(sanitize_stacks) {
+ // Assert there should be either a valid fd or a valid path, not both.
+ assert(fd_ != -1 || minidump_path);
+ assert(fd_ == -1 || !minidump_path);
+ }
+
+ bool Init() {
+ if (!dumper_->Init())
+ return false;
+
+ if (!dumper_->ThreadsSuspend() || !dumper_->LateInit())
+ return false;
+
+ if (skip_stacks_if_mapping_unreferenced_) {
+ principal_mapping_ =
+ dumper_->FindMappingNoBias(principal_mapping_address_);
+ if (!CrashingThreadReferencesPrincipalMapping())
+ return false;
+ }
+
+ if (fd_ != -1)
+ minidump_writer_.SetFile(fd_);
+ else if (!minidump_writer_.Open(path_))
+ return false;
+
+ return true;
+ }
+
+ ~MinidumpWriter() {
+ // Don't close the file descriptor when it's been provided explicitly.
+ // Callers might still need to use it.
+ if (fd_ == -1)
+ minidump_writer_.Close();
+ dumper_->ThreadsResume();
+ }
+
+ bool CrashingThreadReferencesPrincipalMapping() {
+ if (!ucontext_ || !principal_mapping_)
+ return false;
+
+ const uintptr_t low_addr =
+ principal_mapping_->system_mapping_info.start_addr;
+ const uintptr_t high_addr =
+ principal_mapping_->system_mapping_info.end_addr;
+
+ const uintptr_t stack_pointer = UContextReader::GetStackPointer(ucontext_);
+ const uintptr_t pc = UContextReader::GetInstructionPointer(ucontext_);
+
+ if (pc >= low_addr && pc < high_addr)
+ return true;
+
+ uint8_t* stack_copy;
+ const void* stack;
+ size_t stack_len;
+
+ if (!dumper_->GetStackInfo(&stack, &stack_len, stack_pointer))
+ return false;
+
+ stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len));
+ dumper_->CopyFromProcess(stack_copy, GetCrashThread(), stack, stack_len);
+
+ uintptr_t stack_pointer_offset =
+ stack_pointer - reinterpret_cast<uintptr_t>(stack);
+
+ return dumper_->StackHasPointerToMapping(
+ stack_copy, stack_len, stack_pointer_offset, *principal_mapping_);
+ }
+
+ bool Dump() {
+ // A minidump file contains a number of tagged streams. This is the number
+ // of stream which we write.
+ unsigned kNumWriters = 14;
+
+ TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
+ {
+ // Ensure the header gets flushed, as that happens in the destructor.
+ // If we crash somewhere below, we should have a mostly-intact dump
+ TypedMDRVA<MDRawHeader> header(&minidump_writer_);
+ if (!header.Allocate())
+ return false;
+
+ if (!dir.AllocateArray(kNumWriters))
+ return false;
+
+ my_memset(header.get(), 0, sizeof(MDRawHeader));
+
+ header.get()->signature = MD_HEADER_SIGNATURE;
+ header.get()->version = MD_HEADER_VERSION;
+ header.get()->time_date_stamp = time(NULL);
+ header.get()->stream_count = kNumWriters;
+ header.get()->stream_directory_rva = dir.position();
+ }
+
+ unsigned dir_index = 0;
+ MDRawDirectory dirent;
+
+ if (!WriteThreadListStream(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ if (!WriteThreadNamesStream(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ if (!WriteMappings(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ if (!WriteAppMemory())
+ return false;
+
+ if (!WriteMemoryListStream(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ if (!WriteExceptionStream(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ if (!WriteSystemInfoStream(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_CPU_INFO;
+ if (!WriteFile(&dirent.location, "/proc/cpuinfo"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_PROC_STATUS;
+ if (!WriteProcFile(&dirent.location, GetCrashThread(), "status"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_LSB_RELEASE;
+ if (!WriteFile(&dirent.location, "/etc/lsb-release") &&
+ !WriteFile(&dirent.location, "/etc/os-release")) {
+ NullifyDirectoryEntry(&dirent);
+ }
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_CMD_LINE;
+ if (!WriteProcFile(&dirent.location, GetCrashThread(), "cmdline"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_ENVIRON;
+ if (!WriteProcFile(&dirent.location, GetCrashThread(), "environ"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_AUXV;
+ if (!WriteProcFile(&dirent.location, GetCrashThread(), "auxv"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_MAPS;
+ if (!WriteProcFile(&dirent.location, GetCrashThread(), "maps"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_DSO_DEBUG;
+ if (!WriteDSODebugStream(&dirent))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ // If you add more directory entries, don't forget to update kNumWriters,
+ // above.
+
+ dumper_->ThreadsResume();
+ return true;
+ }
+
+ bool FillThreadStack(MDRawThread* thread, uintptr_t stack_pointer,
+ uintptr_t pc, int max_stack_len, uint8_t** stack_copy) {
+ *stack_copy = NULL;
+ const void* stack;
+ size_t stack_len;
+
+ thread->stack.start_of_memory_range = stack_pointer;
+ thread->stack.memory.data_size = 0;
+ thread->stack.memory.rva = minidump_writer_.position();
+
+ if (dumper_->GetStackInfo(&stack, &stack_len, stack_pointer)) {
+ if (max_stack_len >= 0 &&
+ stack_len > static_cast<unsigned int>(max_stack_len)) {
+ stack_len = max_stack_len;
+ // Skip empty chunks of length max_stack_len.
+ uintptr_t int_stack = reinterpret_cast<uintptr_t>(stack);
+ if (max_stack_len > 0) {
+ while (int_stack + max_stack_len < stack_pointer) {
+ int_stack += max_stack_len;
+ }
+ }
+ stack = reinterpret_cast<const void*>(int_stack);
+ }
+ *stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len));
+ dumper_->CopyFromProcess(*stack_copy, thread->thread_id, stack,
+ stack_len);
+
+ uintptr_t stack_pointer_offset =
+ stack_pointer - reinterpret_cast<uintptr_t>(stack);
+ if (skip_stacks_if_mapping_unreferenced_) {
+ if (!principal_mapping_) {
+ return true;
+ }
+ uintptr_t low_addr = principal_mapping_->system_mapping_info.start_addr;
+ uintptr_t high_addr = principal_mapping_->system_mapping_info.end_addr;
+ if ((pc < low_addr || pc > high_addr) &&
+ !dumper_->StackHasPointerToMapping(*stack_copy, stack_len,
+ stack_pointer_offset,
+ *principal_mapping_)) {
+ return true;
+ }
+ }
+
+ if (sanitize_stacks_) {
+ dumper_->SanitizeStackCopy(*stack_copy, stack_len, stack_pointer,
+ stack_pointer_offset);
+ }
+
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(stack_len))
+ return false;
+ memory.Copy(*stack_copy, stack_len);
+ thread->stack.start_of_memory_range = reinterpret_cast<uintptr_t>(stack);
+ thread->stack.memory = memory.location();
+ memory_blocks_.push_back(thread->stack);
+ }
+ return true;
+ }
+
+ // Write information about the threads.
+ bool WriteThreadListStream(MDRawDirectory* dirent) {
+ const unsigned num_threads = dumper_->threads().size();
+
+ TypedMDRVA<uint32_t> list(&minidump_writer_);
+ if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread)))
+ return false;
+
+ dirent->stream_type = MD_THREAD_LIST_STREAM;
+ dirent->location = list.location();
+
+ *list.get() = num_threads;
+
+ // If there's a minidump size limit, check if it might be exceeded. Since
+ // most of the space is filled with stack data, just check against that.
+ // If this expects to exceed the limit, set extra_thread_stack_len such
+ // that any thread beyond the first kLimitBaseThreadCount threads will
+ // have only kLimitMaxExtraThreadStackLen bytes dumped.
+ int extra_thread_stack_len = -1; // default to no maximum
+ if (minidump_size_limit_ >= 0) {
+ const unsigned estimated_total_stack_size = num_threads *
+ kLimitAverageThreadStackLength;
+ const off_t estimated_minidump_size = minidump_writer_.position() +
+ estimated_total_stack_size + kLimitMinidumpFudgeFactor;
+ if (estimated_minidump_size > minidump_size_limit_)
+ extra_thread_stack_len = kLimitMaxExtraThreadStackLen;
+ }
+
+ for (unsigned i = 0; i < num_threads; ++i) {
+ MDRawThread thread;
+ my_memset(&thread, 0, sizeof(thread));
+ thread.thread_id = dumper_->threads()[i];
+
+ // We have a different source of information for the crashing thread. If
+ // we used the actual state of the thread we would find it running in the
+ // signal handler with the alternative stack, which would be deeply
+ // unhelpful.
+ if (static_cast<pid_t>(thread.thread_id) == GetCrashThread() &&
+ ucontext_ &&
+ !dumper_->IsPostMortem()) {
+ uint8_t* stack_copy;
+ const uintptr_t stack_ptr = UContextReader::GetStackPointer(ucontext_);
+ if (!FillThreadStack(&thread, stack_ptr,
+ UContextReader::GetInstructionPointer(ucontext_),
+ -1, &stack_copy))
+ return false;
+
+ // Copy 256 bytes around crashing instruction pointer to minidump.
+ const size_t kIPMemorySize = 256;
+ uint64_t ip = UContextReader::GetInstructionPointer(ucontext_);
+ // Bound it to the upper and lower bounds of the memory map
+ // it's contained within. If it's not in mapped memory,
+ // don't bother trying to write it.
+ bool ip_is_mapped = false;
+ MDMemoryDescriptor ip_memory_d;
+ for (unsigned j = 0; j < dumper_->mappings().size(); ++j) {
+ const MappingInfo& mapping = *dumper_->mappings()[j];
+ if (ip >= mapping.start_addr &&
+ ip < mapping.start_addr + mapping.size) {
+ ip_is_mapped = true;
+ // Try to get 128 bytes before and after the IP, but
+ // settle for whatever's available.
+ ip_memory_d.start_of_memory_range =
+ std::max(mapping.start_addr,
+ uintptr_t(ip - (kIPMemorySize / 2)));
+ uintptr_t end_of_range =
+ std::min(uintptr_t(ip + (kIPMemorySize / 2)),
+ uintptr_t(mapping.start_addr + mapping.size));
+ ip_memory_d.memory.data_size =
+ end_of_range - ip_memory_d.start_of_memory_range;
+ break;
+ }
+ }
+
+ if (ip_is_mapped) {
+ UntypedMDRVA ip_memory(&minidump_writer_);
+ if (!ip_memory.Allocate(ip_memory_d.memory.data_size))
+ return false;
+ uint8_t* memory_copy =
+ reinterpret_cast<uint8_t*>(Alloc(ip_memory_d.memory.data_size));
+ dumper_->CopyFromProcess(
+ memory_copy,
+ thread.thread_id,
+ reinterpret_cast<void*>(ip_memory_d.start_of_memory_range),
+ ip_memory_d.memory.data_size);
+ ip_memory.Copy(memory_copy, ip_memory_d.memory.data_size);
+ ip_memory_d.memory = ip_memory.location();
+ memory_blocks_.push_back(ip_memory_d);
+ }
+
+ TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
+ if (!cpu.Allocate())
+ return false;
+ my_memset(cpu.get(), 0, sizeof(RawContextCPU));
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ UContextReader::FillCPUContext(cpu.get(), ucontext_, float_state_);
+#else
+ UContextReader::FillCPUContext(cpu.get(), ucontext_);
+#endif
+ thread.thread_context = cpu.location();
+ crashing_thread_context_ = cpu.location();
+ } else {
+ ThreadInfo info;
+ if (!dumper_->GetThreadInfoByIndex(i, &info))
+ return false;
+
+ uint8_t* stack_copy;
+ int max_stack_len = -1; // default to no maximum for this thread
+ if (minidump_size_limit_ >= 0 && i >= kLimitBaseThreadCount)
+ max_stack_len = extra_thread_stack_len;
+ if (!FillThreadStack(&thread, info.stack_pointer,
+ info.GetInstructionPointer(), max_stack_len,
+ &stack_copy))
+ return false;
+
+ TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
+ if (!cpu.Allocate())
+ return false;
+ my_memset(cpu.get(), 0, sizeof(RawContextCPU));
+ info.FillCPUContext(cpu.get());
+ thread.thread_context = cpu.location();
+ if (dumper_->threads()[i] == GetCrashThread()) {
+ crashing_thread_context_ = cpu.location();
+ if (!dumper_->IsPostMortem()) {
+ // This is the crashing thread of a live process, but
+ // no context was provided, so set the crash address
+ // while the instruction pointer is already here.
+ dumper_->set_crash_address(info.GetInstructionPointer());
+ }
+ }
+ }
+
+ list.CopyIndexAfterObject(i, &thread, sizeof(thread));
+ }
+
+ return true;
+ }
+
+ bool WriteThreadName(pid_t tid, char* name, MDRawThreadName *thread_name) {
+ MDLocationDescriptor string_location;
+
+ if (!minidump_writer_.WriteString(name, 0, &string_location))
+ return false;
+
+ thread_name->thread_id = tid;
+ thread_name->rva_of_thread_name = string_location.rva;
+ return true;
+ }
+
+ // Write the threads' names.
+ bool WriteThreadNamesStream(MDRawDirectory* thread_names_stream) {
+ TypedMDRVA<MDRawThreadNamesList> list(&minidump_writer_);
+ const unsigned num_threads = dumper_->threads().size();
+
+ if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThreadName))) {
+ return false;
+ }
+
+ thread_names_stream->stream_type = MD_THREAD_NAMES_STREAM;
+ thread_names_stream->location = list.location();
+ list.get()->number_of_thread_names = num_threads;
+
+ MDRawThreadName thread_name;
+ int thread_idx = 0;
+
+ for (unsigned int i = 0; i < num_threads; ++i) {
+ const pid_t tid = dumper_->threads()[i];
+ // This is a constant from the Linux kernel, documented in man 5 proc.
+ // The comm entries in /proc are no longer than this.
+ static const size_t TASK_COMM_LEN = 16;
+ char name[TASK_COMM_LEN];
+ memset(&thread_name, 0, sizeof(MDRawThreadName));
+
+ if (dumper_->GetThreadNameByIndex(i, name, sizeof(name))) {
+ if (WriteThreadName(tid, name, &thread_name)) {
+ list.CopyIndexAfterObject(thread_idx++, &thread_name,
+ sizeof(MDRawThreadName));
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // Write application-provided memory regions.
+ bool WriteAppMemory() {
+ for (AppMemoryList::const_iterator iter = app_memory_list_.begin();
+ iter != app_memory_list_.end();
+ ++iter) {
+ uint8_t* data_copy =
+ reinterpret_cast<uint8_t*>(dumper_->allocator()->Alloc(iter->length));
+ dumper_->CopyFromProcess(data_copy, GetCrashThread(), iter->ptr,
+ iter->length);
+
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(iter->length)) {
+ return false;
+ }
+ memory.Copy(data_copy, iter->length);
+ MDMemoryDescriptor desc;
+ desc.start_of_memory_range = reinterpret_cast<uintptr_t>(iter->ptr);
+ desc.memory = memory.location();
+ memory_blocks_.push_back(desc);
+ }
+
+ return true;
+ }
+
+ static bool ShouldIncludeMapping(const MappingInfo& mapping) {
+ if (mapping.name[0] == 0 || // only want modules with filenames.
+ // Only want to include one mapping per shared lib.
+ // Avoid filtering executable mappings.
+ (mapping.offset != 0 && !mapping.exec) ||
+ mapping.size < 4096) { // too small to get a signature for.
+ return false;
+ }
+
+ return true;
+ }
+
+ // If there is caller-provided information about this mapping
+ // in the mapping_list_ list, return true. Otherwise, return false.
+ bool HaveMappingInfo(const MappingInfo& mapping) {
+ for (MappingList::const_iterator iter = mapping_list_.begin();
+ iter != mapping_list_.end();
+ ++iter) {
+ // Ignore any mappings that are wholly contained within
+ // mappings in the mapping_info_ list.
+ if (mapping.start_addr >= iter->first.start_addr &&
+ (mapping.start_addr + mapping.size) <=
+ (iter->first.start_addr + iter->first.size)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Write information about the mappings in effect. Because we are using the
+ // minidump format, the information about the mappings is pretty limited.
+ // Because of this, we also include the full, unparsed, /proc/$x/maps file in
+ // another stream in the file.
+ bool WriteMappings(MDRawDirectory* dirent) {
+ const unsigned num_mappings = dumper_->mappings().size();
+ unsigned num_output_mappings = mapping_list_.size();
+
+ for (unsigned i = 0; i < dumper_->mappings().size(); ++i) {
+ const MappingInfo& mapping = *dumper_->mappings()[i];
+ if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping))
+ num_output_mappings++;
+ }
+
+ TypedMDRVA<uint32_t> list(&minidump_writer_);
+ if (num_output_mappings) {
+ if (!list.AllocateObjectAndArray(num_output_mappings, MD_MODULE_SIZE))
+ return false;
+ } else {
+ // Still create the module list stream, although it will have zero
+ // modules.
+ if (!list.Allocate())
+ return false;
+ }
+
+ dirent->stream_type = MD_MODULE_LIST_STREAM;
+ dirent->location = list.location();
+ *list.get() = num_output_mappings;
+
+ // First write all the mappings from the dumper
+ unsigned int j = 0;
+ for (unsigned i = 0; i < num_mappings; ++i) {
+ const MappingInfo& mapping = *dumper_->mappings()[i];
+ if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping))
+ continue;
+
+ MDRawModule mod;
+ if (!FillRawModule(mapping, true, i, &mod, NULL))
+ return false;
+ list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
+ }
+ // Next write all the mappings provided by the caller
+ for (MappingList::const_iterator iter = mapping_list_.begin();
+ iter != mapping_list_.end();
+ ++iter) {
+ MDRawModule mod;
+ if (!FillRawModule(iter->first, false, 0, &mod, &iter->second)) {
+ return false;
+ }
+ list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
+ }
+
+ return true;
+ }
+
+ // Fill the MDRawModule |mod| with information about the provided
+ // |mapping|. If |identifier| is non-NULL, use it instead of calculating
+ // a file ID from the mapping.
+ bool FillRawModule(const MappingInfo& mapping,
+ bool member,
+ unsigned int mapping_id,
+ MDRawModule* mod,
+ const std::vector<uint8_t>* identifier) {
+ my_memset(mod, 0, MD_MODULE_SIZE);
+
+ mod->base_of_image = mapping.start_addr;
+ mod->size_of_image = mapping.size;
+
+ auto_wasteful_vector<uint8_t, kDefaultBuildIdSize> identifier_bytes(
+ dumper_->allocator());
+
+ if (identifier) {
+ // GUID was provided by caller.
+ identifier_bytes.insert(identifier_bytes.end(),
+ identifier->begin(),
+ identifier->end());
+ } else {
+ // Note: ElfFileIdentifierForMapping() can manipulate the |mapping.name|.
+ if (!dumper_->ElfFileIdentifierForMapping(mapping,
+ member,
+ mapping_id,
+ identifier_bytes)) {
+ identifier_bytes.clear();
+ }
+ }
+
+ if (!identifier_bytes.empty()) {
+ UntypedMDRVA cv(&minidump_writer_);
+ if (!cv.Allocate(MDCVInfoELF_minsize + identifier_bytes.size()))
+ return false;
+
+ const uint32_t cv_signature = MD_CVINFOELF_SIGNATURE;
+ cv.Copy(&cv_signature, sizeof(cv_signature));
+ cv.Copy(cv.position() + sizeof(cv_signature), &identifier_bytes[0],
+ identifier_bytes.size());
+
+ mod->cv_record = cv.location();
+ }
+
+ char file_name[NAME_MAX];
+ char file_path[NAME_MAX];
+ dumper_->GetMappingEffectiveNameAndPath(
+ mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
+
+ MDLocationDescriptor ld;
+ if (!minidump_writer_.WriteString(file_path, my_strlen(file_path), &ld))
+ return false;
+ mod->module_name_rva = ld.rva;
+ return true;
+ }
+
+ bool WriteMemoryListStream(MDRawDirectory* dirent) {
+ TypedMDRVA<uint32_t> list(&minidump_writer_);
+ if (!memory_blocks_.empty()) {
+ if (!list.AllocateObjectAndArray(memory_blocks_.size(),
+ sizeof(MDMemoryDescriptor)))
+ return false;
+ } else {
+ // Still create the memory list stream, although it will have zero
+ // memory blocks.
+ if (!list.Allocate())
+ return false;
+ }
+
+ dirent->stream_type = MD_MEMORY_LIST_STREAM;
+ dirent->location = list.location();
+
+ *list.get() = memory_blocks_.size();
+
+ for (size_t i = 0; i < memory_blocks_.size(); ++i) {
+ list.CopyIndexAfterObject(i, &memory_blocks_[i],
+ sizeof(MDMemoryDescriptor));
+ }
+ return true;
+ }
+
+ bool WriteExceptionStream(MDRawDirectory* dirent) {
+ TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_);
+ if (!exc.Allocate())
+ return false;
+
+ MDRawExceptionStream* stream = exc.get();
+ my_memset(stream, 0, sizeof(MDRawExceptionStream));
+
+ dirent->stream_type = MD_EXCEPTION_STREAM;
+ dirent->location = exc.location();
+
+ stream->thread_id = GetCrashThread();
+ stream->exception_record.exception_code = dumper_->crash_signal();
+ stream->exception_record.exception_flags = dumper_->crash_signal_code();
+ stream->exception_record.exception_address = dumper_->crash_address();
+ const std::vector<uint64_t> crash_exception_info =
+ dumper_->crash_exception_info();
+ stream->exception_record.number_parameters = crash_exception_info.size();
+ memcpy(stream->exception_record.exception_information,
+ crash_exception_info.data(),
+ sizeof(uint64_t) * crash_exception_info.size());
+ stream->thread_context = crashing_thread_context_;
+
+ return true;
+ }
+
+ bool WriteSystemInfoStream(MDRawDirectory* dirent) {
+ TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_);
+ if (!si.Allocate())
+ return false;
+ my_memset(si.get(), 0, sizeof(MDRawSystemInfo));
+
+ dirent->stream_type = MD_SYSTEM_INFO_STREAM;
+ dirent->location = si.location();
+
+ WriteCPUInformation(si.get());
+ WriteOSInformation(si.get());
+
+ return true;
+ }
+
+ bool WriteDSODebugStream(MDRawDirectory* dirent) {
+ ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr) *>(dumper_->auxv()[AT_PHDR]);
+ char* base;
+ int phnum = dumper_->auxv()[AT_PHNUM];
+ if (!phnum || !phdr)
+ return false;
+
+ // Assume the program base is at the beginning of the same page as the PHDR
+ base = reinterpret_cast<char *>(reinterpret_cast<uintptr_t>(phdr) & ~0xfff);
+
+ // Search for the program PT_DYNAMIC segment
+ ElfW(Addr) dyn_addr = 0;
+ for (; phnum >= 0; phnum--, phdr++) {
+ ElfW(Phdr) ph;
+ if (!dumper_->CopyFromProcess(&ph, dumper_->pid(), phdr, sizeof(ph)))
+ return false;
+
+ // Adjust base address with the virtual address of the PT_LOAD segment
+ // corresponding to offset 0
+ if (ph.p_type == PT_LOAD && ph.p_offset == 0) {
+ base -= ph.p_vaddr;
+ }
+ if (ph.p_type == PT_DYNAMIC) {
+ dyn_addr = ph.p_vaddr;
+ }
+ }
+ if (!dyn_addr)
+ return false;
+
+ ElfW(Dyn) *dynamic = reinterpret_cast<ElfW(Dyn) *>(dyn_addr + base);
+
+ // The dynamic linker makes information available that helps gdb find all
+ // DSOs loaded into the program. If this information is indeed available,
+ // dump it to a MD_LINUX_DSO_DEBUG stream.
+ struct r_debug* r_debug = NULL;
+ uint32_t dynamic_length = 0;
+
+ for (int i = 0; ; ++i) {
+ ElfW(Dyn) dyn;
+ dynamic_length += sizeof(dyn);
+ if (!dumper_->CopyFromProcess(&dyn, dumper_->pid(), dynamic + i, sizeof(dyn))) {
+ return false;
+ }
+
+#ifdef __mips__
+ const int32_t debug_tag = DT_MIPS_RLD_MAP;
+#else
+ const int32_t debug_tag = DT_DEBUG;
+#endif
+ if (dyn.d_tag == debug_tag) {
+ r_debug = reinterpret_cast<struct r_debug*>(dyn.d_un.d_ptr);
+ continue;
+ } else if (dyn.d_tag == DT_NULL) {
+ break;
+ }
+ }
+
+ // The "r_map" field of that r_debug struct contains a linked list of all
+ // loaded DSOs.
+ // Our list of DSOs potentially is different from the ones in the crashing
+ // process. So, we have to be careful to never dereference pointers
+ // directly. Instead, we use CopyFromProcess() everywhere.
+ // See <link.h> for a more detailed discussion of the how the dynamic
+ // loader communicates with debuggers.
+
+ // Count the number of loaded DSOs
+ int dso_count = 0;
+ struct r_debug debug_entry;
+ if (!dumper_->CopyFromProcess(&debug_entry, dumper_->pid(), r_debug,
+ sizeof(debug_entry))) {
+ return false;
+ }
+ for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
+ struct link_map map;
+ if (!dumper_->CopyFromProcess(&map, dumper_->pid(), ptr, sizeof(map)))
+ return false;
+
+ ptr = map.l_next;
+ dso_count++;
+ }
+
+ MDRVA linkmap_rva = MinidumpFileWriter::kInvalidMDRVA;
+ if (dso_count > 0) {
+ // If we have at least one DSO, create an array of MDRawLinkMap
+ // entries in the minidump file.
+ TypedMDRVA<MDRawLinkMap> linkmap(&minidump_writer_);
+ if (!linkmap.AllocateArray(dso_count))
+ return false;
+ linkmap_rva = linkmap.location().rva;
+ int idx = 0;
+
+ // Iterate over DSOs and write their information to mini dump
+ for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
+ struct link_map map;
+ if (!dumper_->CopyFromProcess(&map, dumper_->pid(), ptr, sizeof(map)))
+ return false;
+
+ ptr = map.l_next;
+ char filename[257] = { 0 };
+ if (map.l_name) {
+ dumper_->CopyFromProcess(filename, dumper_->pid(), map.l_name,
+ sizeof(filename) - 1);
+ }
+ MDLocationDescriptor location;
+ if (!minidump_writer_.WriteString(filename, 0, &location))
+ return false;
+ MDRawLinkMap entry;
+ entry.name = location.rva;
+ entry.addr = map.l_addr;
+ entry.ld = reinterpret_cast<uintptr_t>(map.l_ld);
+ linkmap.CopyIndex(idx++, &entry);
+ }
+ }
+
+ // Write MD_LINUX_DSO_DEBUG record
+ TypedMDRVA<MDRawDebug> debug(&minidump_writer_);
+ if (!debug.AllocateObjectAndArray(1, dynamic_length))
+ return false;
+ my_memset(debug.get(), 0, sizeof(MDRawDebug));
+ dirent->stream_type = MD_LINUX_DSO_DEBUG;
+ dirent->location = debug.location();
+
+ debug.get()->version = debug_entry.r_version;
+ debug.get()->map = linkmap_rva;
+ debug.get()->dso_count = dso_count;
+ debug.get()->brk = debug_entry.r_brk;
+ debug.get()->ldbase = debug_entry.r_ldbase;
+ debug.get()->dynamic = reinterpret_cast<uintptr_t>(dynamic);
+
+ wasteful_vector<char> dso_debug_data(dumper_->allocator(), dynamic_length);
+ // The passed-in size to the constructor (above) is only a hint.
+ // Must call .resize() to do actual initialization of the elements.
+ dso_debug_data.resize(dynamic_length);
+ dumper_->CopyFromProcess(&dso_debug_data[0], dumper_->pid(), dynamic,
+ dynamic_length);
+ debug.CopyIndexAfterObject(0, &dso_debug_data[0], dynamic_length);
+
+ return true;
+ }
+
+ void set_minidump_size_limit(off_t limit) { minidump_size_limit_ = limit; }
+
+ private:
+ void* Alloc(unsigned bytes) {
+ return dumper_->allocator()->Alloc(bytes);
+ }
+
+ pid_t GetCrashThread() const {
+ return dumper_->crash_thread();
+ }
+
+ void NullifyDirectoryEntry(MDRawDirectory* dirent) {
+ dirent->stream_type = 0;
+ dirent->location.data_size = 0;
+ dirent->location.rva = 0;
+ }
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__mips__)
+ bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
+ char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0};
+ static const char vendor_id_name[] = "vendor_id";
+
+ struct CpuInfoEntry {
+ const char* info_name;
+ int value;
+ bool found;
+ } cpu_info_table[] = {
+ { "processor", -1, false },
+#if defined(__i386__) || defined(__x86_64__)
+ { "model", 0, false },
+ { "stepping", 0, false },
+ { "cpu family", 0, false },
+#endif
+ };
+
+ // processor_architecture should always be set, do this first
+ sys_info->processor_architecture =
+#if defined(__mips__)
+# if _MIPS_SIM == _ABIO32
+ MD_CPU_ARCHITECTURE_MIPS;
+# elif _MIPS_SIM == _ABI64
+ MD_CPU_ARCHITECTURE_MIPS64;
+# else
+# error "This mips ABI is currently not supported (n32)"
+#endif
+#elif defined(__i386__)
+ MD_CPU_ARCHITECTURE_X86;
+#else
+ MD_CPU_ARCHITECTURE_AMD64;
+#endif
+
+ const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+
+ {
+ PageAllocator allocator;
+ ProcCpuInfoReader* const reader = new(allocator) ProcCpuInfoReader(fd);
+ const char* field;
+ while (reader->GetNextField(&field)) {
+ bool is_first_entry = true;
+ for (CpuInfoEntry& entry : cpu_info_table) {
+ if (!is_first_entry && entry.found) {
+ // except for the 'processor' field, ignore repeated values.
+ continue;
+ }
+ is_first_entry = false;
+ if (!my_strcmp(field, entry.info_name)) {
+ size_t value_len;
+ const char* value = reader->GetValueAndLen(&value_len);
+ if (value_len == 0)
+ continue;
+
+ uintptr_t val;
+ if (my_read_decimal_ptr(&val, value) == value)
+ continue;
+
+ entry.value = static_cast<int>(val);
+ entry.found = true;
+ }
+ }
+
+ // special case for vendor_id
+ if (!my_strcmp(field, vendor_id_name)) {
+ size_t value_len;
+ const char* value = reader->GetValueAndLen(&value_len);
+ if (value_len > 0)
+ my_strlcpy(vendor_id, value, sizeof(vendor_id));
+ }
+ }
+ sys_close(fd);
+ }
+
+ // make sure we got everything we wanted
+ for (const CpuInfoEntry& entry : cpu_info_table) {
+ if (!entry.found) {
+ return false;
+ }
+ }
+ // cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo,
+ // assuming this is the highest id, change it to the number of CPUs
+ // by adding one.
+ cpu_info_table[0].value++;
+
+ sys_info->number_of_processors = cpu_info_table[0].value;
+#if defined(__i386__) || defined(__x86_64__)
+ sys_info->processor_level = cpu_info_table[3].value;
+ sys_info->processor_revision = cpu_info_table[1].value << 8 |
+ cpu_info_table[2].value;
+#endif
+
+ if (vendor_id[0] != '\0') {
+ my_memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
+ sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
+ }
+ return true;
+ }
+#elif defined(__arm__) || defined(__aarch64__)
+ bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
+ // The CPUID value is broken up in several entries in /proc/cpuinfo.
+ // This table is used to rebuild it from the entries.
+ const struct CpuIdEntry {
+ const char* field;
+ char format;
+ char bit_lshift;
+ char bit_length;
+ } cpu_id_entries[] = {
+ { "CPU implementer", 'x', 24, 8 },
+ { "CPU variant", 'x', 20, 4 },
+ { "CPU part", 'x', 4, 12 },
+ { "CPU revision", 'd', 0, 4 },
+ };
+
+ // The ELF hwcaps are listed in the "Features" entry as textual tags.
+ // This table is used to rebuild them.
+ const struct CpuFeaturesEntry {
+ const char* tag;
+ uint32_t hwcaps;
+ } cpu_features_entries[] = {
+#if defined(__arm__)
+ { "swp", MD_CPU_ARM_ELF_HWCAP_SWP },
+ { "half", MD_CPU_ARM_ELF_HWCAP_HALF },
+ { "thumb", MD_CPU_ARM_ELF_HWCAP_THUMB },
+ { "26bit", MD_CPU_ARM_ELF_HWCAP_26BIT },
+ { "fastmult", MD_CPU_ARM_ELF_HWCAP_FAST_MULT },
+ { "fpa", MD_CPU_ARM_ELF_HWCAP_FPA },
+ { "vfp", MD_CPU_ARM_ELF_HWCAP_VFP },
+ { "edsp", MD_CPU_ARM_ELF_HWCAP_EDSP },
+ { "java", MD_CPU_ARM_ELF_HWCAP_JAVA },
+ { "iwmmxt", MD_CPU_ARM_ELF_HWCAP_IWMMXT },
+ { "crunch", MD_CPU_ARM_ELF_HWCAP_CRUNCH },
+ { "thumbee", MD_CPU_ARM_ELF_HWCAP_THUMBEE },
+ { "neon", MD_CPU_ARM_ELF_HWCAP_NEON },
+ { "vfpv3", MD_CPU_ARM_ELF_HWCAP_VFPv3 },
+ { "vfpv3d16", MD_CPU_ARM_ELF_HWCAP_VFPv3D16 },
+ { "tls", MD_CPU_ARM_ELF_HWCAP_TLS },
+ { "vfpv4", MD_CPU_ARM_ELF_HWCAP_VFPv4 },
+ { "idiva", MD_CPU_ARM_ELF_HWCAP_IDIVA },
+ { "idivt", MD_CPU_ARM_ELF_HWCAP_IDIVT },
+ { "idiv", MD_CPU_ARM_ELF_HWCAP_IDIVA | MD_CPU_ARM_ELF_HWCAP_IDIVT },
+#elif defined(__aarch64__)
+ // No hwcaps on aarch64.
+#endif
+ };
+
+ // processor_architecture should always be set, do this first
+ sys_info->processor_architecture =
+#if defined(__aarch64__)
+ MD_CPU_ARCHITECTURE_ARM64_OLD;
+#else
+ MD_CPU_ARCHITECTURE_ARM;
+#endif
+
+ // /proc/cpuinfo is not readable under various sandboxed environments
+ // (e.g. Android services with the android:isolatedProcess attribute)
+ // prepare for this by setting default values now, which will be
+ // returned when this happens.
+ //
+ // Note: Bogus values are used to distinguish between failures (to
+ // read /sys and /proc files) and really badly configured kernels.
+ sys_info->number_of_processors = 0;
+ sys_info->processor_level = 1U; // There is no ARMv1
+ sys_info->processor_revision = 42;
+ sys_info->cpu.arm_cpu_info.cpuid = 0;
+ sys_info->cpu.arm_cpu_info.elf_hwcaps = 0;
+
+ // Counting the number of CPUs involves parsing two sysfs files,
+ // because the content of /proc/cpuinfo will only mirror the number
+ // of 'online' cores, and thus will vary with time.
+ // See http://www.kernel.org/doc/Documentation/cputopology.txt
+ {
+ CpuSet cpus_present;
+ CpuSet cpus_possible;
+
+ int fd = sys_open("/sys/devices/system/cpu/present", O_RDONLY, 0);
+ if (fd >= 0) {
+ cpus_present.ParseSysFile(fd);
+ sys_close(fd);
+
+ fd = sys_open("/sys/devices/system/cpu/possible", O_RDONLY, 0);
+ if (fd >= 0) {
+ cpus_possible.ParseSysFile(fd);
+ sys_close(fd);
+
+ cpus_present.IntersectWith(cpus_possible);
+ int cpu_count = cpus_present.GetCount();
+ if (cpu_count > 255)
+ cpu_count = 255;
+ sys_info->number_of_processors = static_cast<uint8_t>(cpu_count);
+ }
+ }
+ }
+
+ // Parse /proc/cpuinfo to reconstruct the CPUID value, as well
+ // as the ELF hwcaps field. For the latter, it would be easier to
+ // read /proc/self/auxv but unfortunately, this file is not always
+ // readable from regular Android applications on later versions
+ // (>= 4.1) of the Android platform.
+ const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
+ if (fd < 0) {
+ // Do not return false here to allow the minidump generation
+ // to happen properly.
+ return true;
+ }
+
+ {
+ PageAllocator allocator;
+ ProcCpuInfoReader* const reader =
+ new(allocator) ProcCpuInfoReader(fd);
+ const char* field;
+ while (reader->GetNextField(&field)) {
+ for (const CpuIdEntry& entry : cpu_id_entries) {
+ if (my_strcmp(entry.field, field) != 0)
+ continue;
+ uintptr_t result = 0;
+ const char* value = reader->GetValue();
+ const char* p = value;
+ if (value[0] == '0' && value[1] == 'x') {
+ p = my_read_hex_ptr(&result, value+2);
+ } else if (entry.format == 'x') {
+ p = my_read_hex_ptr(&result, value);
+ } else {
+ p = my_read_decimal_ptr(&result, value);
+ }
+ if (p == value)
+ continue;
+
+ result &= (1U << entry.bit_length)-1;
+ result <<= entry.bit_lshift;
+ sys_info->cpu.arm_cpu_info.cpuid |=
+ static_cast<uint32_t>(result);
+ }
+#if defined(__arm__)
+ // Get the architecture version from the "Processor" field.
+ // Note that it is also available in the "CPU architecture" field,
+ // however, some existing kernels are misconfigured and will report
+ // invalid values here (e.g. 6, while the CPU is ARMv7-A based).
+ // The "Processor" field doesn't have this issue.
+ if (!my_strcmp(field, "Processor")) {
+ size_t value_len;
+ const char* value = reader->GetValueAndLen(&value_len);
+ // Expected format: <text> (v<level><endian>)
+ // Where <text> is some text like "ARMv7 Processor rev 2"
+ // and <level> is a decimal corresponding to the ARM
+ // architecture number. <endian> is either 'l' or 'b'
+ // and corresponds to the endianess, it is ignored here.
+ while (value_len > 0 && my_isspace(value[value_len-1]))
+ value_len--;
+
+ size_t nn = value_len;
+ while (nn > 0 && value[nn-1] != '(')
+ nn--;
+ if (nn > 0 && value[nn] == 'v') {
+ uintptr_t arch_level = 5;
+ my_read_decimal_ptr(&arch_level, value + nn + 1);
+ sys_info->processor_level = static_cast<uint16_t>(arch_level);
+ }
+ }
+#elif defined(__aarch64__)
+ // The aarch64 architecture does not provide the architecture level
+ // in the Processor field, so we instead check the "CPU architecture"
+ // field.
+ if (!my_strcmp(field, "CPU architecture")) {
+ uintptr_t arch_level = 0;
+ const char* value = reader->GetValue();
+ const char* p = value;
+ p = my_read_decimal_ptr(&arch_level, value);
+ if (p == value)
+ continue;
+ sys_info->processor_level = static_cast<uint16_t>(arch_level);
+ }
+#endif
+ // Rebuild the ELF hwcaps from the 'Features' field.
+ if (!my_strcmp(field, "Features")) {
+ size_t value_len;
+ const char* value = reader->GetValueAndLen(&value_len);
+
+ // Parse each space-separated tag.
+ while (value_len > 0) {
+ const char* tag = value;
+ size_t tag_len = value_len;
+ const char* p = my_strchr(tag, ' ');
+ if (p) {
+ tag_len = static_cast<size_t>(p - tag);
+ value += tag_len + 1;
+ value_len -= tag_len + 1;
+ } else {
+ tag_len = strlen(tag);
+ value_len = 0;
+ }
+ for (const CpuFeaturesEntry& entry : cpu_features_entries) {
+ if (tag_len == strlen(entry.tag) &&
+ !memcmp(tag, entry.tag, tag_len)) {
+ sys_info->cpu.arm_cpu_info.elf_hwcaps |= entry.hwcaps;
+ break;
+ }
+ }
+ }
+ }
+ }
+ sys_close(fd);
+ }
+
+ return true;
+ }
+#else
+# error "Unsupported CPU"
+#endif
+
+ bool WriteFile(MDLocationDescriptor* result, const char* filename) {
+ const int fd = sys_open(filename, O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+
+ // We can't stat the files because several of the files that we want to
+ // read are kernel seqfiles, which always have a length of zero. So we have
+ // to read as much as we can into a buffer.
+ static const unsigned kBufSize = 1024 - 2*sizeof(void*);
+ struct Buffers {
+ Buffers* next;
+ size_t len;
+ uint8_t data[kBufSize];
+ } *buffers = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers)));
+ buffers->next = NULL;
+ buffers->len = 0;
+
+ size_t total = 0;
+ for (Buffers* bufptr = buffers;;) {
+ ssize_t r;
+ do {
+ r = sys_read(fd, &bufptr->data[bufptr->len], kBufSize - bufptr->len);
+ } while (r == -1 && errno == EINTR);
+
+ if (r < 1)
+ break;
+
+ total += r;
+ bufptr->len += r;
+ if (bufptr->len == kBufSize) {
+ bufptr->next = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers)));
+ bufptr = bufptr->next;
+ bufptr->next = NULL;
+ bufptr->len = 0;
+ }
+ }
+ sys_close(fd);
+
+ if (!total)
+ return false;
+
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(total))
+ return false;
+ for (MDRVA pos = memory.position(); buffers; buffers = buffers->next) {
+ // Check for special case of a zero-length buffer. This should only
+ // occur if a file's size happens to be a multiple of the buffer's
+ // size, in which case the final sys_read() will have resulted in
+ // zero bytes being read after the final buffer was just allocated.
+ if (buffers->len == 0) {
+ // This can only occur with final buffer.
+ assert(buffers->next == NULL);
+ continue;
+ }
+ memory.Copy(pos, &buffers->data, buffers->len);
+ pos += buffers->len;
+ }
+ *result = memory.location();
+ return true;
+ }
+
+ bool WriteOSInformation(MDRawSystemInfo* sys_info) {
+#if defined(__ANDROID__)
+ sys_info->platform_id = MD_OS_ANDROID;
+#else
+ sys_info->platform_id = MD_OS_LINUX;
+#endif
+
+ struct utsname uts;
+ if (uname(&uts))
+ return false;
+
+ static const size_t buf_len = 512;
+ char buf[buf_len] = {0};
+ size_t space_left = buf_len - 1;
+ const char* info_table[] = {
+ uts.sysname,
+ uts.release,
+ uts.version,
+ uts.machine,
+ NULL
+ };
+ bool first_item = true;
+ for (const char** cur_info = info_table; *cur_info; cur_info++) {
+ static const char separator[] = " ";
+ size_t separator_len = sizeof(separator) - 1;
+ size_t info_len = my_strlen(*cur_info);
+ if (info_len == 0)
+ continue;
+
+ if (space_left < info_len + (first_item ? 0 : separator_len))
+ break;
+
+ if (!first_item) {
+ my_strlcat(buf, separator, sizeof(buf));
+ space_left -= separator_len;
+ }
+
+ first_item = false;
+ my_strlcat(buf, *cur_info, sizeof(buf));
+ space_left -= info_len;
+ }
+
+ MDLocationDescriptor location;
+ if (!minidump_writer_.WriteString(buf, 0, &location))
+ return false;
+ sys_info->csd_version_rva = location.rva;
+
+ return true;
+ }
+
+ bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
+ const char* filename) {
+ char buf[NAME_MAX];
+ if (!dumper_->BuildProcPath(buf, pid, filename))
+ return false;
+ return WriteFile(result, buf);
+ }
+
+ // Only one of the 2 member variables below should be set to a valid value.
+ const int fd_; // File descriptor where the minidum should be written.
+ const char* path_; // Path to the file where the minidum should be written.
+
+ const ucontext_t* const ucontext_; // also from the signal handler
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ const google_breakpad::fpstate_t* const float_state_; // ditto
+#endif
+ LinuxDumper* dumper_;
+ MinidumpFileWriter minidump_writer_;
+ off_t minidump_size_limit_;
+ MDLocationDescriptor crashing_thread_context_;
+ // Blocks of memory written to the dump. These are all currently
+ // written while writing the thread list stream, but saved here
+ // so a memory list stream can be written afterwards.
+ wasteful_vector<MDMemoryDescriptor> memory_blocks_;
+ // Additional information about some mappings provided by the caller.
+ const MappingList& mapping_list_;
+ // Additional memory regions to be included in the dump,
+ // provided by the caller.
+ const AppMemoryList& app_memory_list_;
+ // If set, skip recording any threads that do not reference the
+ // mapping containing principal_mapping_address_.
+ bool skip_stacks_if_mapping_unreferenced_;
+ uintptr_t principal_mapping_address_;
+ const MappingInfo* principal_mapping_;
+ // If true, apply stack sanitization to stored stack data.
+ bool sanitize_stacks_;
+};
+
+
+bool WriteMinidumpImpl(const char* minidump_path,
+ int minidump_fd,
+ off_t minidump_size_limit,
+ pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appmem,
+ bool skip_stacks_if_mapping_unreferenced,
+ uintptr_t principal_mapping_address,
+ bool sanitize_stacks) {
+ LinuxPtraceDumper dumper(crashing_process);
+ const ExceptionHandler::CrashContext* context = NULL;
+ if (blob) {
+ if (blob_size != sizeof(ExceptionHandler::CrashContext))
+ return false;
+ context = reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
+ dumper.SetCrashInfoFromSigInfo(context->siginfo);
+ dumper.set_crash_thread(context->tid);
+ }
+ MinidumpWriter writer(minidump_path, minidump_fd, context, mappings,
+ appmem, skip_stacks_if_mapping_unreferenced,
+ principal_mapping_address, sanitize_stacks, &dumper);
+ // Set desired limit for file size of minidump (-1 means no limit).
+ writer.set_minidump_size_limit(minidump_size_limit);
+ if (!writer.Init())
+ return false;
+ return writer.Dump();
+}
+
+} // namespace
+
+namespace google_breakpad {
+
+bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ bool skip_stacks_if_mapping_unreferenced,
+ uintptr_t principal_mapping_address,
+ bool sanitize_stacks) {
+ return WriteMinidumpImpl(minidump_path, -1, -1,
+ crashing_process, blob, blob_size,
+ MappingList(), AppMemoryList(),
+ skip_stacks_if_mapping_unreferenced,
+ principal_mapping_address,
+ sanitize_stacks);
+}
+
+bool WriteMinidump(int minidump_fd, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ bool skip_stacks_if_mapping_unreferenced,
+ uintptr_t principal_mapping_address,
+ bool sanitize_stacks) {
+ return WriteMinidumpImpl(NULL, minidump_fd, -1,
+ crashing_process, blob, blob_size,
+ MappingList(), AppMemoryList(),
+ skip_stacks_if_mapping_unreferenced,
+ principal_mapping_address,
+ sanitize_stacks);
+}
+
+bool WriteMinidump(const char* minidump_path, pid_t process,
+ pid_t process_blamed_thread) {
+ LinuxPtraceDumper dumper(process);
+ // MinidumpWriter will set crash address
+ dumper.set_crash_signal(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED);
+ dumper.set_crash_thread(process_blamed_thread);
+ MappingList mapping_list;
+ AppMemoryList app_memory_list;
+ MinidumpWriter writer(minidump_path, -1, NULL, mapping_list,
+ app_memory_list, false, 0, false, &dumper);
+ if (!writer.Init())
+ return false;
+ return writer.Dump();
+}
+
+bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appmem,
+ bool skip_stacks_if_mapping_unreferenced,
+ uintptr_t principal_mapping_address,
+ bool sanitize_stacks) {
+ return WriteMinidumpImpl(minidump_path, -1, -1, crashing_process,
+ blob, blob_size,
+ mappings, appmem,
+ skip_stacks_if_mapping_unreferenced,
+ principal_mapping_address,
+ sanitize_stacks);
+}
+
+bool WriteMinidump(int minidump_fd, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appmem,
+ bool skip_stacks_if_mapping_unreferenced,
+ uintptr_t principal_mapping_address,
+ bool sanitize_stacks) {
+ return WriteMinidumpImpl(NULL, minidump_fd, -1, crashing_process,
+ blob, blob_size,
+ mappings, appmem,
+ skip_stacks_if_mapping_unreferenced,
+ principal_mapping_address,
+ sanitize_stacks);
+}
+
+bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit,
+ pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appmem,
+ bool skip_stacks_if_mapping_unreferenced,
+ uintptr_t principal_mapping_address,
+ bool sanitize_stacks) {
+ return WriteMinidumpImpl(minidump_path, -1, minidump_size_limit,
+ crashing_process, blob, blob_size,
+ mappings, appmem,
+ skip_stacks_if_mapping_unreferenced,
+ principal_mapping_address,
+ sanitize_stacks);
+}
+
+bool WriteMinidump(int minidump_fd, off_t minidump_size_limit,
+ pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appmem,
+ bool skip_stacks_if_mapping_unreferenced,
+ uintptr_t principal_mapping_address,
+ bool sanitize_stacks) {
+ return WriteMinidumpImpl(NULL, minidump_fd, minidump_size_limit,
+ crashing_process, blob, blob_size,
+ mappings, appmem,
+ skip_stacks_if_mapping_unreferenced,
+ principal_mapping_address,
+ sanitize_stacks);
+}
+
+bool WriteMinidump(const char* filename,
+ const MappingList& mappings,
+ const AppMemoryList& appmem,
+ LinuxDumper* dumper) {
+ MinidumpWriter writer(filename, -1, NULL, mappings, appmem,
+ false, 0, false, dumper);
+ if (!writer.Init())
+ return false;
+ return writer.Dump();
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.h
new file mode 100644
index 0000000000..1d02ec8d7d
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.h
@@ -0,0 +1,143 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/ucontext.h>
+#include <unistd.h>
+
+#include <list>
+#include <type_traits>
+#include <utility>
+
+#include "linux/minidump_writer/linux_dumper.h"
+#include "google_breakpad/common/minidump_format.h"
+
+namespace google_breakpad {
+
+class ExceptionHandler;
+
+#if defined(__aarch64__)
+typedef struct fpsimd_context fpstate_t;
+#elif !defined(__ARM_EABI__) && !defined(__mips__)
+typedef std::remove_pointer<fpregset_t>::type fpstate_t;
+#endif
+
+// These entries store a list of memory regions that the client wants included
+// in the minidump.
+struct AppMemory {
+ void* ptr;
+ size_t length;
+
+ bool operator==(const struct AppMemory& other) const {
+ return ptr == other.ptr;
+ }
+
+ bool operator==(const void* other) const {
+ return ptr == other;
+ }
+};
+typedef std::list<AppMemory> AppMemoryList;
+
+// Writes a minidump to the filesystem. These functions do not malloc nor use
+// libc functions which may. Thus, it can be used in contexts where the state
+// of the heap may be corrupt.
+// minidump_path: the path to the file to write to. This is opened O_EXCL and
+// fails open fails.
+// crashing_process: the pid of the crashing process. This must be trusted.
+// blob: a blob of data from the crashing process. See exception_handler.h
+// blob_size: the length of |blob|, in bytes
+//
+// Returns true iff successful.
+bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ bool skip_stacks_if_mapping_unreferenced = false,
+ uintptr_t principal_mapping_address = 0,
+ bool sanitize_stacks = false);
+// Same as above but takes an open file descriptor instead of a path.
+bool WriteMinidump(int minidump_fd, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ bool skip_stacks_if_mapping_unreferenced = false,
+ uintptr_t principal_mapping_address = 0,
+ bool sanitize_stacks = false);
+
+// Alternate form of WriteMinidump() that works with processes that
+// are not expected to have crashed. If |process_blamed_thread| is
+// meaningful, it will be the one from which a crash signature is
+// extracted. It is not expected that this function will be called
+// from a compromised context, but it is safe to do so.
+bool WriteMinidump(const char* minidump_path, pid_t process,
+ pid_t process_blamed_thread);
+
+// These overloads also allow passing a list of known mappings and
+// a list of additional memory regions to be included in the minidump.
+bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appdata,
+ bool skip_stacks_if_mapping_unreferenced = false,
+ uintptr_t principal_mapping_address = 0,
+ bool sanitize_stacks = false);
+bool WriteMinidump(int minidump_fd, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appdata,
+ bool skip_stacks_if_mapping_unreferenced = false,
+ uintptr_t principal_mapping_address = 0,
+ bool sanitize_stacks = false);
+
+// These overloads also allow passing a file size limit for the minidump.
+bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit,
+ pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appdata,
+ bool skip_stacks_if_mapping_unreferenced = false,
+ uintptr_t principal_mapping_address = 0,
+ bool sanitize_stacks = false);
+bool WriteMinidump(int minidump_fd, off_t minidump_size_limit,
+ pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings,
+ const AppMemoryList& appdata,
+ bool skip_stacks_if_mapping_unreferenced = false,
+ uintptr_t principal_mapping_address = 0,
+ bool sanitize_stacks = false);
+
+bool WriteMinidump(const char* filename,
+ const MappingList& mappings,
+ const AppMemoryList& appdata,
+ LinuxDumper* dumper);
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest.cc
new file mode 100644
index 0000000000..4e49a4e577
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest.cc
@@ -0,0 +1,934 @@
+// Copyright (c) 2011 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <ucontext.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "linux/handler/exception_handler.h"
+#include "linux/minidump_writer/linux_dumper.h"
+#include "linux/minidump_writer/minidump_writer.h"
+#include "linux/minidump_writer/minidump_writer_unittest_utils.h"
+#include "common/linux/breakpad_getcontext.h"
+#include "common/linux/eintr_wrapper.h"
+#include "common/linux/file_id.h"
+#include "common/linux/ignore_ret.h"
+#include "common/linux/safe_readlink.h"
+#include "common/scoped_ptr.h"
+#include "common/tests/auto_tempdir.h"
+#include "common/tests/file_utils.h"
+#include "common/using_std_string.h"
+#include "google_breakpad/processor/minidump.h"
+
+using namespace google_breakpad;
+
+namespace {
+
+typedef testing::Test MinidumpWriterTest;
+
+const char kMDWriterUnitTestFileName[] = "/minidump-writer-unittest";
+
+TEST(MinidumpWriterTest, SetupWithPath) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+ // Set a non-zero tid to avoid tripping asserts.
+ context.tid = child;
+ ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context)));
+ struct stat st;
+ ASSERT_EQ(0, stat(templ.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+TEST(MinidumpWriterTest, SetupWithFD) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+ int fd = open(templ.c_str(), O_CREAT | O_WRONLY, S_IRWXU);
+ // Set a non-zero tid to avoid tripping asserts.
+ context.tid = child;
+ ASSERT_TRUE(WriteMinidump(fd, child, &context, sizeof(context)));
+ struct stat st;
+ ASSERT_EQ(0, stat(templ.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+// Test that mapping info can be specified when writing a minidump,
+// and that it ends up in the module list of the minidump.
+TEST(MinidumpWriterTest, MappingInfo) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const uint32_t memory_size = sysconf(_SC_PAGESIZE);
+ const char* kMemoryName = "a fake module";
+ const uint8_t kModuleGUID[sizeof(MDGUID)] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+ };
+ const string module_identifier = "33221100554477668899AABBCCDDEEFF0";
+
+ // Get some memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ memory_size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
+ ASSERT_TRUE(memory);
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+ ASSERT_EQ(0, getcontext(&context.context));
+ context.tid = child;
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+
+ // Add information about the mapped memory.
+ MappingInfo info;
+ info.start_addr = kMemoryAddress;
+ info.size = memory_size;
+ info.offset = 0;
+ info.exec = false;
+ strcpy(info.name, kMemoryName);
+
+ MappingList mappings;
+ AppMemoryList memory_list;
+ MappingEntry mapping;
+ mapping.first = info;
+ mapping.second.assign(std::begin(kModuleGUID), std::end(kModuleGUID));
+ mappings.push_back(mapping);
+ ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
+ mappings, memory_list, false, 0, false));
+
+ // Read the minidump. Load the module list, and ensure that
+ // the mmap'ed |memory| is listed with the given module name
+ // and debug ID.
+ Minidump minidump(templ);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module =
+ module_list->GetModuleForAddress(kMemoryAddress);
+ ASSERT_TRUE(module);
+
+ EXPECT_EQ(kMemoryAddress, module->base_address());
+ EXPECT_EQ(memory_size, module->size());
+ EXPECT_EQ(kMemoryName, module->code_file());
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+
+ uint32_t len;
+ // These streams are expected to be there
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_THREAD_LIST_STREAM, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_MEMORY_LIST_STREAM, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_EXCEPTION_STREAM, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_SYSTEM_INFO_STREAM, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CPU_INFO, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_PROC_STATUS, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CMD_LINE, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_ENVIRON, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_AUXV, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_MAPS, &len));
+ EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_DSO_DEBUG, &len));
+
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+// Test that minidumping is skipped while writing minidumps if principal mapping
+// is not referenced.
+TEST(MinidumpWriterTest, MinidumpSkippedIfRequested) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+ ASSERT_EQ(0, getcontext(&context.context));
+ context.tid = child;
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+
+ // pass an invalid principal mapping address, which will force
+ // WriteMinidump to not write a minidump.
+ ASSERT_FALSE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
+ true, static_cast<uintptr_t>(0x0102030405060708ull),
+ false));
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+// Test that minidumping is skipped while writing minidumps if principal mapping
+// is not referenced.
+TEST(MinidumpWriterTest, MinidumpStacksSkippedIfRequested) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+
+ // Create a thread that does not return, and only references libc (not the
+ // current executable). This thread should not be captured in the minidump.
+ pthread_t thread;
+ pthread_attr_t thread_attributes;
+ pthread_attr_init(&thread_attributes);
+ pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED);
+ sigset_t sigset;
+ sigemptyset(&sigset);
+ pthread_create(&thread, &thread_attributes,
+ reinterpret_cast<void* (*)(void*)>(&sigsuspend), &sigset);
+
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+ ASSERT_EQ(0, getcontext(&context.context));
+ context.tid = child;
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+
+ // Pass an invalid principal mapping address, which will force
+ // WriteMinidump to not dump any thread stacks.
+ ASSERT_TRUE(WriteMinidump(
+ templ.c_str(), child, &context, sizeof(context), true,
+ reinterpret_cast<uintptr_t>(google_breakpad::WriteFile), false));
+
+ // Read the minidump. And ensure that thread memory was dumped only for the
+ // main thread.
+ Minidump minidump(templ);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpThreadList *threads = minidump.GetThreadList();
+ int threads_with_stacks = 0;
+ for (unsigned int i = 0; i < threads->thread_count(); ++i) {
+ MinidumpThread *thread = threads->GetThreadAtIndex(i);
+ if (thread->GetMemory()) {
+ ++threads_with_stacks;
+ }
+ }
+#if defined(THREAD_SANITIZER) || defined(ADDRESS_SANITIZER)
+ ASSERT_GE(threads_with_stacks, 1);
+#else
+ ASSERT_EQ(threads_with_stacks, 1);
+#endif
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+// Test that stacks can be sanitized while writing minidumps.
+TEST(MinidumpWriterTest, StacksAreSanitizedIfRequested) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+ ASSERT_EQ(0, getcontext(&context.context));
+ context.tid = child;
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+ // pass an invalid principal mapping address, which will force
+ // WriteMinidump to not dump any thread stacks.
+ ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
+ false, 0, true));
+
+ // Read the minidump. And ensure that thread memory contains a defaced value.
+ Minidump minidump(templ);
+ ASSERT_TRUE(minidump.Read());
+
+ const uintptr_t defaced =
+#if defined(__LP64__)
+ 0x0defaced0defaced;
+#else
+ 0x0defaced;
+#endif
+ MinidumpThreadList *threads = minidump.GetThreadList();
+ for (unsigned int i = 0; i < threads->thread_count(); ++i) {
+ MinidumpThread *thread = threads->GetThreadAtIndex(i);
+ MinidumpMemoryRegion *mem = thread->GetMemory();
+ ASSERT_TRUE(mem != nullptr);
+ uint32_t sz = mem->GetSize();
+ const uint8_t *data = mem->GetMemory();
+ ASSERT_TRUE(memmem(data, sz, &defaced, sizeof(defaced)) != nullptr);
+ }
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+// Test that a binary with a longer-than-usual build id note
+// makes its way all the way through to the minidump unscathed.
+// The linux_client_unittest is linked with an explicit --build-id
+// in Makefile.am.
+TEST(MinidumpWriterTest, BuildIDLong) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+ ASSERT_EQ(0, getcontext(&context.context));
+ context.tid = child;
+
+ AutoTempDir temp_dir;
+ const string dump_path = temp_dir.path() + kMDWriterUnitTestFileName;
+
+ EXPECT_TRUE(WriteMinidump(dump_path.c_str(),
+ child, &context, sizeof(context)));
+ close(fds[1]);
+
+ // Read the minidump. Load the module list, and ensure that
+ // the main module has the correct debug id and code id.
+ Minidump minidump(dump_path);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module = module_list->GetMainModule();
+ ASSERT_TRUE(module);
+ const string module_identifier = "030201000504070608090A0B0C0D0E0F0";
+ // This is passed explicitly to the linker in Makefile.am
+ const string build_id =
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+ EXPECT_EQ(build_id, module->code_identifier());
+
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+// Test that mapping info can be specified, and that it overrides
+// existing mappings that are wholly contained within the specified
+// range.
+TEST(MinidumpWriterTest, MappingInfoContained) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const int32_t memory_size = sysconf(_SC_PAGESIZE);
+ const char* kMemoryName = "a fake module";
+ const uint8_t kModuleGUID[sizeof(MDGUID)] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+ };
+ const string module_identifier = "33221100554477668899AABBCCDDEEFF0";
+
+ // mmap a file
+ AutoTempDir temp_dir;
+ string tempfile = temp_dir.path() + "/minidump-writer-unittest-temp";
+ int fd = open(tempfile.c_str(), O_RDWR | O_CREAT, 0);
+ ASSERT_NE(-1, fd);
+ unlink(tempfile.c_str());
+ // fill with zeros
+ google_breakpad::scoped_array<char> buffer(new char[memory_size]);
+ memset(buffer.get(), 0, memory_size);
+ ASSERT_EQ(memory_size, write(fd, buffer.get(), memory_size));
+ lseek(fd, 0, SEEK_SET);
+
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ memory_size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
+ fd,
+ 0));
+ const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
+ ASSERT_TRUE(memory);
+ close(fd);
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+ context.tid = 1;
+
+ string dumpfile = temp_dir.path() + kMDWriterUnitTestFileName;
+
+ // Add information about the mapped memory. Report it as being larger than
+ // it actually is.
+ MappingInfo info;
+ info.start_addr = kMemoryAddress - memory_size;
+ info.size = memory_size * 3;
+ info.offset = 0;
+ info.exec = false;
+ strcpy(info.name, kMemoryName);
+
+ MappingList mappings;
+ AppMemoryList memory_list;
+ MappingEntry mapping;
+ mapping.first = info;
+ mapping.second.assign(std::begin(kModuleGUID), std::end(kModuleGUID));
+ mappings.push_back(mapping);
+ ASSERT_TRUE(WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context),
+ mappings, memory_list));
+
+ // Read the minidump. Load the module list, and ensure that
+ // the mmap'ed |memory| is listed with the given module name
+ // and debug ID.
+ Minidump minidump(dumpfile);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module =
+ module_list->GetModuleForAddress(kMemoryAddress);
+ ASSERT_TRUE(module);
+
+ EXPECT_EQ(info.start_addr, module->base_address());
+ EXPECT_EQ(info.size, module->size());
+ EXPECT_EQ(kMemoryName, module->code_file());
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+TEST(MinidumpWriterTest, DeletedBinary) {
+ const string kNumberOfThreadsArgument = "1";
+ const string helper_path(GetHelperBinary());
+ if (helper_path.empty()) {
+ FAIL() << "Couldn't find helper binary";
+ exit(1);
+ }
+
+ // Copy binary to a temp file.
+ AutoTempDir temp_dir;
+ string binpath = temp_dir.path() + "/linux-dumper-unittest-helper";
+ ASSERT_TRUE(CopyFile(helper_path.c_str(), binpath.c_str()))
+ << "Failed to copy " << helper_path << " to " << binpath;
+ ASSERT_EQ(0, chmod(binpath.c_str(), 0755));
+
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+ close(fds[0]);
+
+ // Pass the pipe fd and the number of threads as arguments.
+ char pipe_fd_string[8];
+ sprintf(pipe_fd_string, "%d", fds[1]);
+ execl(binpath.c_str(),
+ binpath.c_str(),
+ pipe_fd_string,
+ kNumberOfThreadsArgument.c_str(),
+ NULL);
+ }
+ close(fds[1]);
+ // Wait for the child process to signal that it's ready.
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
+ ASSERT_EQ(1, r);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+ uint8_t junk;
+ const int nr = HANDLE_EINTR(read(fds[0], &junk, sizeof(junk)));
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(junk)), nr);
+ close(fds[0]);
+
+ // Child is ready now.
+ // Unlink the test binary.
+ unlink(binpath.c_str());
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+ // Set a non-zero tid to avoid tripping asserts.
+ context.tid = child_pid;
+ ASSERT_TRUE(WriteMinidump(templ.c_str(), child_pid, &context,
+ sizeof(context)));
+ kill(child_pid, SIGKILL);
+
+ struct stat st;
+ ASSERT_EQ(0, stat(templ.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+
+ Minidump minidump(templ);
+ ASSERT_TRUE(minidump.Read());
+
+ // Check that the main module filename is correct.
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module = module_list->GetMainModule();
+ EXPECT_STREQ(binpath.c_str(), module->code_file().c_str());
+ // Check that the file ID is correct.
+ FileID fileid(helper_path.c_str());
+ PageAllocator allocator;
+ wasteful_vector<uint8_t> identifier(&allocator, kDefaultBuildIdSize);
+ EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
+ string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier);
+ string module_identifier(identifier_string);
+ // Strip out dashes
+ size_t pos;
+ while ((pos = module_identifier.find('-')) != string::npos) {
+ module_identifier.erase(pos, 1);
+ }
+ // And append a zero, because module IDs include an "age" field
+ // which is always zero on Linux.
+ module_identifier += "0";
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+
+ IGNORE_EINTR(waitpid(child_pid, nullptr, 0));
+}
+
+// Test that an additional memory region can be added to the minidump.
+TEST(MinidumpWriterTest, AdditionalMemory) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const uint32_t kMemorySize = sysconf(_SC_PAGESIZE);
+
+ // Get some heap memory.
+ uint8_t* memory = new uint8_t[kMemorySize];
+ const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
+ ASSERT_TRUE(memory);
+
+ // Stick some data into the memory so the contents can be verified.
+ for (uint32_t i = 0; i < kMemorySize; ++i) {
+ memory[i] = i % 255;
+ }
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+
+ // This needs a valid context for minidump writing to work, but getting
+ // a useful one from the child is too much work, so just use one from
+ // the parent since the child is just a forked copy anyway.
+ ASSERT_EQ(0, getcontext(&context.context));
+ context.tid = child;
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+ unlink(templ.c_str());
+
+ MappingList mappings;
+ AppMemoryList memory_list;
+
+ // Add the memory region to the list of memory to be included.
+ AppMemory app_memory;
+ app_memory.ptr = memory;
+ app_memory.length = kMemorySize;
+ memory_list.push_back(app_memory);
+ ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
+ mappings, memory_list));
+
+ // Read the minidump. Ensure that the memory region is present
+ Minidump minidump(templ);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(dump_memory_list);
+ const MinidumpMemoryRegion* region =
+ dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
+ ASSERT_TRUE(region);
+
+ EXPECT_EQ(kMemoryAddress, region->GetBase());
+ EXPECT_EQ(kMemorySize, region->GetSize());
+
+ // Verify memory contents.
+ EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
+
+ delete[] memory;
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+// Test that an invalid thread stack pointer still results in a minidump.
+TEST(MinidumpWriterTest, InvalidStackPointer) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
+ close(fds[0]);
+ syscall(__NR_exit_group);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+
+ // This needs a valid context for minidump writing to work, but getting
+ // a useful one from the child is too much work, so just use one from
+ // the parent since the child is just a forked copy anyway.
+ ASSERT_EQ(0, getcontext(&context.context));
+ context.tid = child;
+
+ // Fake the child's stack pointer for its crashing thread. NOTE: This must
+ // be an invalid memory address for the child process (stack or otherwise).
+ // Try 1MB below the current stack.
+ uintptr_t invalid_stack_pointer =
+ reinterpret_cast<uintptr_t>(&context) - 1024*1024;
+#if defined(__i386)
+ context.context.uc_mcontext.gregs[REG_ESP] = invalid_stack_pointer;
+#elif defined(__x86_64)
+ context.context.uc_mcontext.gregs[REG_RSP] = invalid_stack_pointer;
+#elif defined(__ARM_EABI__)
+ context.context.uc_mcontext.arm_sp = invalid_stack_pointer;
+#elif defined(__aarch64__)
+ context.context.uc_mcontext.sp = invalid_stack_pointer;
+#elif defined(__mips__)
+ context.context.uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP] =
+ invalid_stack_pointer;
+#else
+# error "This code has not been ported to your platform yet."
+#endif
+
+ AutoTempDir temp_dir;
+ string templ = temp_dir.path() + kMDWriterUnitTestFileName;
+ // NOTE: In previous versions of Breakpad, WriteMinidump() would fail if
+ // presented with an invalid stack pointer.
+ ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context)));
+
+ // Read the minidump. Ensure that the memory region is present
+ Minidump minidump(templ);
+ ASSERT_TRUE(minidump.Read());
+
+ // TODO(ted.mielczarek,mkrebs): Enable this part of the test once
+ // https://breakpad.appspot.com/413002/ is committed.
+#if 0
+ // Make sure there's a thread without a stack. NOTE: It's okay if
+ // GetThreadList() shows the error: "ERROR: MinidumpThread has a memory
+ // region problem".
+ MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
+ ASSERT_TRUE(dump_thread_list);
+ bool found_empty_stack = false;
+ for (int i = 0; i < dump_thread_list->thread_count(); i++) {
+ MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
+ ASSERT_TRUE(thread->thread() != NULL);
+ // When the stack size is zero bytes, GetMemory() returns NULL.
+ if (thread->GetMemory() == NULL) {
+ found_empty_stack = true;
+ break;
+ }
+ }
+ // NOTE: If you fail this, first make sure that "invalid_stack_pointer"
+ // above is indeed set to an invalid address.
+ ASSERT_TRUE(found_empty_stack);
+#endif
+
+ close(fds[1]);
+ IGNORE_EINTR(waitpid(child, nullptr, 0));
+}
+
+// Test that limiting the size of the minidump works.
+TEST(MinidumpWriterTest, MinidumpSizeLimit) {
+ static const int kNumberOfThreadsInHelperProgram = 40;
+
+ char number_of_threads_arg[3];
+ sprintf(number_of_threads_arg, "%d", kNumberOfThreadsInHelperProgram);
+
+ string helper_path(GetHelperBinary());
+ if (helper_path.empty()) {
+ FAIL() << "Couldn't find helper binary";
+ exit(1);
+ }
+
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+ close(fds[0]);
+
+ // Pass the pipe fd and the number of threads as arguments.
+ char pipe_fd_string[8];
+ sprintf(pipe_fd_string, "%d", fds[1]);
+ execl(helper_path.c_str(),
+ helper_path.c_str(),
+ pipe_fd_string,
+ number_of_threads_arg,
+ NULL);
+ }
+ close(fds[1]);
+
+ // Wait for all child threads to indicate that they have started
+ for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) {
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
+ ASSERT_EQ(1, r);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+ uint8_t junk;
+ ASSERT_EQ(read(fds[0], &junk, sizeof(junk)),
+ static_cast<ssize_t>(sizeof(junk)));
+ }
+ close(fds[0]);
+
+ // There is a race here because we may stop a child thread before
+ // it is actually running the busy loop. Empirically this sleep
+ // is sufficient to avoid the race.
+ usleep(100000);
+
+ // Child and its threads are ready now.
+
+
+ off_t normal_file_size;
+ int total_normal_stack_size = 0;
+ AutoTempDir temp_dir;
+
+ // First, write a minidump with no size limit.
+ {
+ string normal_dump = temp_dir.path() +
+ "/minidump-writer-unittest.dmp";
+ ASSERT_TRUE(WriteMinidump(normal_dump.c_str(), -1,
+ child_pid, NULL, 0,
+ MappingList(), AppMemoryList()));
+ struct stat st;
+ ASSERT_EQ(0, stat(normal_dump.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+ normal_file_size = st.st_size;
+
+ Minidump minidump(normal_dump);
+ ASSERT_TRUE(minidump.Read());
+ MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
+ ASSERT_TRUE(dump_thread_list);
+ for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) {
+ MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
+ ASSERT_TRUE(thread->thread() != NULL);
+ // When the stack size is zero bytes, GetMemory() returns NULL.
+ MinidumpMemoryRegion* memory = thread->GetMemory();
+ ASSERT_TRUE(memory != NULL);
+ total_normal_stack_size += memory->GetSize();
+ }
+ }
+
+ // Second, write a minidump with a size limit big enough to not trigger
+ // anything.
+ {
+ // Set size limit arbitrarily 1MB larger than the normal file size -- such
+ // that the limiting code will not kick in.
+ const off_t minidump_size_limit = normal_file_size + 1024*1024;
+
+ string same_dump = temp_dir.path() +
+ "/minidump-writer-unittest-same.dmp";
+ ASSERT_TRUE(WriteMinidump(same_dump.c_str(), minidump_size_limit,
+ child_pid, NULL, 0,
+ MappingList(), AppMemoryList()));
+ struct stat st;
+ ASSERT_EQ(0, stat(same_dump.c_str(), &st));
+ // Make sure limiting wasn't actually triggered. NOTE: If you fail this,
+ // first make sure that "minidump_size_limit" above is indeed set to a
+ // large enough value -- the limit-checking code in minidump_writer.cc
+ // does just a rough estimate.
+ ASSERT_EQ(normal_file_size, st.st_size);
+ }
+
+ // Third, write a minidump with a size limit small enough to be triggered.
+ {
+ // Set size limit to some arbitrary amount, such that the limiting code
+ // will kick in. The equation used to set this value was determined by
+ // simply reversing the size-limit logic a little bit in order to pick a
+ // size we know will trigger it. The definition of
+ // kLimitAverageThreadStackLength here was copied from class
+ // MinidumpWriter in minidump_writer.cc.
+ static const unsigned kLimitAverageThreadStackLength = 8 * 1024;
+ off_t minidump_size_limit = kNumberOfThreadsInHelperProgram *
+ kLimitAverageThreadStackLength;
+ // If, in reality, each of the threads' stack is *smaller* than
+ // kLimitAverageThreadStackLength, the normal file size could very well be
+ // smaller than the arbitrary limit that was just set. In that case,
+ // either of these numbers should trigger the size-limiting code, but we
+ // might as well pick the smallest.
+ if (normal_file_size < minidump_size_limit)
+ minidump_size_limit = normal_file_size;
+
+ string limit_dump = temp_dir.path() +
+ "/minidump-writer-unittest-limit.dmp";
+ ASSERT_TRUE(WriteMinidump(limit_dump.c_str(), minidump_size_limit,
+ child_pid, NULL, 0,
+ MappingList(), AppMemoryList()));
+ struct stat st;
+ ASSERT_EQ(0, stat(limit_dump.c_str(), &st));
+ ASSERT_GT(st.st_size, 0);
+ // Make sure the file size is at least smaller than the original. If this
+ // fails because it's the same size, then the size-limit logic didn't kick
+ // in like it was supposed to.
+ EXPECT_LT(st.st_size, normal_file_size);
+
+ Minidump minidump(limit_dump);
+ ASSERT_TRUE(minidump.Read());
+ MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
+ ASSERT_TRUE(dump_thread_list);
+ int total_limit_stack_size = 0;
+ for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) {
+ MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
+ ASSERT_TRUE(thread->thread() != NULL);
+ // When the stack size is zero bytes, GetMemory() returns NULL.
+ MinidumpMemoryRegion* memory = thread->GetMemory();
+ ASSERT_TRUE(memory != NULL);
+ total_limit_stack_size += memory->GetSize();
+ }
+
+ // Make sure stack size shrunk by at least 1KB per extra thread. The
+ // definition of kLimitBaseThreadCount here was copied from class
+ // MinidumpWriter in minidump_writer.cc.
+ // Note: The 1KB is arbitrary, and assumes that the thread stacks are big
+ // enough to shrink by that much. For example, if each thread stack was
+ // originally only 2KB, the current size-limit logic wouldn't actually
+ // shrink them because that's the size to which it tries to shrink. If
+ // you fail this part of the test due to something like that, the test
+ // logic should probably be improved to account for your situation.
+ const unsigned kLimitBaseThreadCount = 20;
+ const unsigned kMinPerExtraThreadStackReduction = 1024;
+ const int min_expected_reduction = (kNumberOfThreadsInHelperProgram -
+ kLimitBaseThreadCount) * kMinPerExtraThreadStackReduction;
+ EXPECT_LT(total_limit_stack_size,
+ total_normal_stack_size - min_expected_reduction);
+ }
+
+ // Kill the helper program.
+ kill(child_pid, SIGKILL);
+ IGNORE_EINTR(waitpid(child_pid, nullptr, 0));
+}
+
+} // namespace
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.cc
new file mode 100644
index 0000000000..3e27be73cc
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2011 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// minidump_writer_unittest_utils.cc:
+// Shared routines used by unittests under linux/minidump_writer.
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include "linux/minidump_writer/minidump_writer_unittest_utils.h"
+#include "common/linux/safe_readlink.h"
+#include "common/using_std_string.h"
+
+namespace google_breakpad {
+
+string GetHelperBinary() {
+ string helper_path;
+ char *bindir = getenv("bindir");
+ if (bindir) {
+ helper_path = string(bindir) + "/";
+ } else {
+ // Locate helper binary next to the current binary.
+ char self_path[PATH_MAX];
+ if (!SafeReadLink("/proc/self/exe", self_path)) {
+ return "";
+ }
+ helper_path = string(self_path);
+ size_t pos = helper_path.rfind('/');
+ if (pos == string::npos) {
+ return "";
+ }
+ helper_path.erase(pos + 1);
+ }
+
+ helper_path += "linux_dumper_unittest_helper";
+
+ return helper_path;
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.h
new file mode 100644
index 0000000000..bc7b7c83f4
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2012, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// minidump_writer_unittest_utils.h:
+// Shared routines used by unittests under linux/minidump_writer.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
+
+#include <string>
+
+#include "common/using_std_string.h"
+
+namespace google_breakpad {
+
+// Returns the full path to linux_dumper_unittest_helper. The full path is
+// discovered either by using the environment variable "bindir" or by using
+// the location of the main module of the currently running process.
+string GetHelperBinary();
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader.h
new file mode 100644
index 0000000000..94a637fc15
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
+
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include "linux/minidump_writer/line_reader.h"
+#include "common/linux/linux_libc_support.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// A class for reading /proc/cpuinfo without using fopen/fgets or other
+// functions which may allocate memory.
+class ProcCpuInfoReader {
+public:
+ ProcCpuInfoReader(int fd)
+ : line_reader_(fd), pop_count_(-1) {
+ }
+
+ // Return the next field name, or NULL in case of EOF.
+ // field: (output) Pointer to zero-terminated field name.
+ // Returns true on success, or false on EOF or error (line too long).
+ bool GetNextField(const char** field) {
+ for (;;) {
+ const char* line;
+ unsigned line_len;
+
+ // Try to read next line.
+ if (pop_count_ >= 0) {
+ line_reader_.PopLine(pop_count_);
+ pop_count_ = -1;
+ }
+
+ if (!line_reader_.GetNextLine(&line, &line_len))
+ return false;
+
+ pop_count_ = static_cast<int>(line_len);
+
+ const char* line_end = line + line_len;
+
+ // Expected format: <field-name> <space>+ ':' <space> <value>
+ // Note that:
+ // - empty lines happen.
+ // - <field-name> can contain spaces.
+ // - some fields have an empty <value>
+ char* sep = static_cast<char*>(my_memchr(line, ':', line_len));
+ if (sep == NULL)
+ continue;
+
+ // Record the value. Skip leading space after the column to get
+ // its start.
+ const char* val = sep+1;
+ while (val < line_end && my_isspace(*val))
+ val++;
+
+ value_ = val;
+ value_len_ = static_cast<size_t>(line_end - val);
+
+ // Remove trailing spaces before the column to properly 0-terminate
+ // the field name.
+ while (sep > line && my_isspace(sep[-1]))
+ sep--;
+
+ if (sep == line)
+ continue;
+
+ // zero-terminate field name.
+ *sep = '\0';
+
+ *field = line;
+ return true;
+ }
+ }
+
+ // Return the field value. This must be called after a succesful
+ // call to GetNextField().
+ const char* GetValue() {
+ assert(value_);
+ return value_;
+ }
+
+ // Same as GetValue(), but also returns the length in characters of
+ // the value.
+ const char* GetValueAndLen(size_t* length) {
+ assert(value_);
+ *length = value_len_;
+ return value_;
+ }
+
+private:
+ LineReader line_reader_;
+ int pop_count_;
+ const char* value_;
+ size_t value_len_;
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
new file mode 100644
index 0000000000..26fe9e3a38
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
@@ -0,0 +1,199 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "linux/minidump_writer/proc_cpuinfo_reader.h"
+#include "breakpad_googletest_includes.h"
+#include "common/linux/tests/auto_testfile.h"
+
+using namespace google_breakpad;
+
+#if !defined(__ANDROID__)
+#define TEMPDIR "/tmp"
+#else
+#define TEMPDIR "/data/local/tmp"
+#endif
+
+
+namespace {
+
+typedef testing::Test ProcCpuInfoReaderTest;
+
+class ScopedTestFile : public AutoTestFile {
+public:
+ explicit ScopedTestFile(const char* text)
+ : AutoTestFile("proc_cpuinfo_reader", text) {
+ }
+};
+
+}
+
+TEST(ProcCpuInfoReaderTest, EmptyFile) {
+ ScopedTestFile file("");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char *field;
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, OneLineTerminated) {
+ ScopedTestFile file("foo : bar\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char *field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, OneLine) {
+ ScopedTestFile file("foo : bar");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char *field;
+ size_t value_len;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValueAndLen(&value_len));
+ ASSERT_EQ(3U, value_len);
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, TwoLinesTerminated) {
+ ScopedTestFile file("foo : bar\nzoo : tut\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("zoo", field);
+ ASSERT_STREQ("tut", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, SkipMalformedLine) {
+ ScopedTestFile file("this line should have a column\nfoo : bar\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, SkipOneEmptyLine) {
+ ScopedTestFile file("\n\nfoo : bar\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, SkipEmptyField) {
+ ScopedTestFile file(" : bar\nzoo : tut\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("zoo", field);
+ ASSERT_STREQ("tut", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, SkipTwoEmptyLines) {
+ ScopedTestFile file("foo : bar\n\n\nfoo : bar\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, FieldWithSpaces) {
+ ScopedTestFile file("foo bar : zoo\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo bar", field);
+ ASSERT_STREQ("zoo", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, EmptyValue) {
+ ScopedTestFile file("foo :\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ size_t value_len;
+ ASSERT_STREQ("", reader.GetValueAndLen(&value_len));
+ ASSERT_EQ(0U, value_len);
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
diff --git a/toolkit/crashreporter/breakpad-client/linux/moz.build b/toolkit/crashreporter/breakpad-client/linux/moz.build
new file mode 100644
index 0000000000..ac4fb1e230
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/linux/moz.build
@@ -0,0 +1,47 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ 'crash_generation/crash_generation_client.cc',
+ 'crash_generation/crash_generation_server.cc',
+ 'dump_writer_common/thread_info.cc',
+ 'dump_writer_common/ucontext_reader.cc',
+ 'handler/exception_handler.cc',
+ 'handler/guid_generator.cc',
+ 'handler/minidump_descriptor.cc',
+ 'log/log.cc',
+ 'microdump_writer/microdump_writer.cc',
+ 'minidump_writer/linux_dumper.cc',
+ 'minidump_writer/linux_ptrace_dumper.cc',
+]
+
+if CONFIG["CC_TYPE"] == "gcc":
+ # Workaround for an ICE in the hazard detection GCC plug-in, see
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1756505#c7
+ SOURCES += ['minidump_writer/minidump_writer.cc']
+else:
+ UNIFIED_SOURCES += ['minidump_writer/minidump_writer.cc']
+
+# On Linux we override the guid_creator.h header and use our own instead
+if CONFIG['OS_TARGET'] == 'Linux' or CONFIG['OS_TARGET'] == 'Android':
+ DEFINES['COMMON_LINUX_GUID_CREATOR_H__'] = 1
+
+if CONFIG['OS_TARGET'] == 'Android':
+ DEFINES['ANDROID_NDK_MAJOR_VERSION'] = CONFIG['ANDROID_NDK_MAJOR_VERSION']
+ DEFINES['ANDROID_NDK_MINOR_VERSION'] = CONFIG['ANDROID_NDK_MINOR_VERSION']
+ LOCAL_INCLUDES += [
+ '/toolkit/crashreporter/google-breakpad/src/common/android/include',
+ ]
+
+FINAL_LIBRARY = 'breakpad_client'
+
+include('/toolkit/crashreporter/crashreporter.mozbuild')
+
+if CONFIG['MOZ_PHC']:
+ DEFINES['MOZ_PHC'] = True
+
+if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
+ CXXFLAGS += ['-Wno-error=stack-protector']