aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbenchan@chromium.org <benchan@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2012-01-19 07:14:51 +0000
committerbenchan@chromium.org <benchan@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2012-01-19 07:14:51 +0000
commit30566abed8d95b752f31f67fde94c0ae0a1502d2 (patch)
tree90f98f074cebf21102ad8f8717a2e70e9f4438a9 /src
parentSkip ElfCoreDumpTest.ValidCoreFile test if no core dump is generated. (diff)
downloadbreakpad-30566abed8d95b752f31f67fde94c0ae0a1502d2.tar.xz
Implement core dump to minidump conversion.
This patch is part of a bigger patch that helps merging the breakpad code with the modified version in Chromium OS. Specifically, this patch makes the following changes: 1. Turn the LinuxDumper class into a base class and move ptrace related code into a new derived class, LinuxPtraceDumper. 2. Add a LinuxCoreDumper class, which is derived from LinuxDumper, to extract information from a crashed process via a core dump file instead of ptrace. 3. Add a WriteMinidumpFromCore function to src/client/linux/minidump_writer/minidump_writer.h, which uses LinuxCoreDumper to extract information from a core dump file. 4. Add a core2md utility, which simply wraps WriteMinidumpFromCore, for converting a core dump to a minidump. BUG=455 TEST=Tested the following: 1. Build on 32-bit and 64-bit Linux with gcc 4.4.3 and gcc 4.6. 2. Build on Mac OS X 10.6.8 with gcc 4.2 and clang 3.0 (with latest gmock). 3. All unit tests pass. 4. Run Chromium OS tests to test core2md. Review URL: http://breakpad.appspot.com/343001 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@905 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src')
-rw-r--r--src/client/linux/minidump_writer/linux_core_dumper.cc234
-rw-r--r--src/client/linux/minidump_writer/linux_core_dumper.h122
-rw-r--r--src/client/linux/minidump_writer/linux_core_dumper_unittest.cc117
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.cc275
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.h47
-rw-r--r--src/client/linux/minidump_writer/linux_ptrace_dumper.cc292
-rw-r--r--src/client/linux/minidump_writer/linux_ptrace_dumper.h92
-rw-r--r--src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc (renamed from src/client/linux/minidump_writer/linux_dumper_unittest.cc)116
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.cc60
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.h6
-rw-r--r--src/common/linux/elf_core_dump_unittest.cc2
-rw-r--r--src/common/linux/tests/crash_generator.cc6
-rw-r--r--src/common/linux/tests/crash_generator.h2
-rw-r--r--src/tools/linux/core2md/core2md.cc57
14 files changed, 1069 insertions, 359 deletions
diff --git a/src/client/linux/minidump_writer/linux_core_dumper.cc b/src/client/linux/minidump_writer/linux_core_dumper.cc
new file mode 100644
index 00000000..3e8c92fe
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_core_dumper.cc
@@ -0,0 +1,234 @@
+// 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 "client/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>
+
+#include "common/linux/linux_libc_support.h"
+
+namespace google_breakpad {
+
+LinuxCoreDumper::LinuxCoreDumper(pid_t pid,
+ const char* core_path,
+ const char* procfs_path)
+ : LinuxDumper(pid),
+ 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;
+}
+
+void 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);
+ }
+}
+
+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));
+#else
+#error "This code hasn't been ported to your platform yet."
+#endif
+
+ return GetStackInfo(&info->stack, &info->stack_len,
+ reinterpret_cast<uintptr_t>(stack_pointer));
+}
+
+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_)) {
+ 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_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;
+ memcpy(&info.regs, status->pr_reg, sizeof(info.regs));
+ if (first_thread) {
+ crash_thread_ = pid;
+ crash_signal_ = status->pr_info.si_signo;
+ }
+ first_thread = false;
+ threads_.push_back(pid);
+ thread_infos_.push_back(info);
+ 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/src/client/linux/minidump_writer/linux_core_dumper.h b/src/client/linux/minidump_writer/linux_core_dumper.h
new file mode 100644
index 00000000..edb9e738
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_core_dumper.h
@@ -0,0 +1,122 @@
+// 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 "client/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
+ LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path);
+
+ // 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.
+ virtual void 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::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/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc
new file mode 100644
index 00000000..e335413b
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc
@@ -0,0 +1,117 @@
+// 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 "breakpad_googletest_includes.h"
+#include "client/linux/minidump_writer/linux_core_dumper.h"
+#include "common/linux/tests/crash_generator.h"
+
+using std::string;
+using namespace google_breakpad;
+
+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;
+ // TODO(benchan): Revert to use ASSERT_TRUE once the flakiness in
+ // CrashGenerator is identified and fixed.
+ if (!crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
+ kCrashSignal, &child_pid)) {
+ fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test"
+ "is skipped due to no core dump generated\n");
+ return;
+ }
+
+ pid_t pid = getpid();
+ const char* core_file = crash_generator.GetCoreFilePath().c_str();
+
+ // Since CrashGenerator::CreateChildCrash() simply crashed a fork of
+ // this process, we expect that those proc files, which are used by
+ // LinuxCoreDumper, of crashed child process have the same content of
+ // this process. So we simply pass the proc files of this process to
+ // LinuxCoreDumper.
+ char procfs_path[NAME_MAX];
+ snprintf(procfs_path, NAME_MAX, "/proc/%d", pid);
+
+ LinuxCoreDumper dumper(child_pid, core_file, procfs_path);
+ dumper.Init();
+
+ EXPECT_TRUE(dumper.IsPostMortem());
+
+ // These are no-ops and should always return true.
+ EXPECT_TRUE(dumper.ThreadsSuspend());
+ EXPECT_TRUE(dumper.ThreadsResume());
+
+ // LinuxCoreDumper cannot determine the crash address and thus it always
+ // sets the crash address to 0.
+ EXPECT_EQ(0, dumper.crash_address());
+ EXPECT_EQ(kCrashSignal, dumper.crash_signal());
+ EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
+ dumper.crash_thread());
+
+ EXPECT_EQ(kNumOfThreads, dumper.threads().size());
+ for (unsigned i = 0; i < kNumOfThreads; ++i) {
+ ThreadInfo info;
+ EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &info));
+ EXPECT_EQ(getpid(), info.ppid);
+ }
+}
diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc
index e1eb9847..2cf1b40f 100644
--- a/src/client/linux/minidump_writer/linux_dumper.cc
+++ b/src/client/linux/minidump_writer/linux_dumper.cc
@@ -27,6 +27,9 @@
// (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
@@ -34,25 +37,12 @@
#include "client/linux/minidump_writer/linux_dumper.h"
-#include <asm/ptrace.h>
#include <assert.h>
-#include <errno.h>
#include <fcntl.h>
#include <limits.h>
-#if !defined(__ANDROID__)
-#include <link.h>
-#endif
#include <stddef.h>
-#include <stdlib.h>
-#include <stdio.h>
#include <string.h>
-#include <sys/ptrace.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include "client/linux/minidump_writer/directory_reader.h"
#include "client/linux/minidump_writer/line_reader.h"
#include "common/linux/file_id.h"
#include "common/linux/linux_libc_support.h"
@@ -63,48 +53,6 @@
static const char kMappedFileUnsafePrefix[] = "/dev/";
static const char kDeletedSuffix[] = " (deleted)";
-// Suspend 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;
-}
-
-// Resume a thread by detaching from it.
-static bool ResumeThread(pid_t pid) {
- return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
-}
-
inline static bool IsMappedFileOpenUnsafe(
const google_breakpad::MappingInfo& mapping) {
// It is unsafe to attempt to open a mapped file that lives under /dev,
@@ -123,7 +71,6 @@ LinuxDumper::LinuxDumper(pid_t pid)
crash_address_(0),
crash_signal_(0),
crash_thread_(0),
- threads_suspended_(false),
threads_(&allocator_, 8),
mappings_(&allocator_) {
}
@@ -135,80 +82,6 @@ bool LinuxDumper::Init() {
return EnumerateThreads() && EnumerateMappings();
}
-bool LinuxDumper::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.
- memmove(&threads_[i], &threads_[i+1],
- (threads_.size() - i - 1) * sizeof(threads_[i]));
- threads_.resize(threads_.size() - 1);
- --i;
- }
- }
- threads_suspended_ = true;
- return threads_.size() > 0;
-}
-
-bool LinuxDumper::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;
-}
-
-void
-LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
- assert(path);
- if (!path) {
- return;
- }
-
- path[0] = '\0';
-
- const unsigned pid_len = my_int_len(pid);
-
- assert(node);
- if (!node) {
- return;
- }
-
- size_t node_len = my_strlen(node);
- assert(node_len < NAME_MAX);
- if (node_len >= NAME_MAX) {
- return;
- }
-
- assert(node_len > 0);
- if (node_len == 0) {
- return;
- }
-
- assert(pid > 0);
- if (pid <= 0) {
- return;
- }
-
- const size_t total_length = 6 + pid_len + 1 + node_len;
-
- assert(total_length < NAME_MAX);
- if (total_length >= NAME_MAX) {
- return;
- }
-
- memcpy(path, "/proc/", 6);
- my_itos(path + 6, pid, pid_len);
- memcpy(path + 6 + pid_len, "/", 1);
- memcpy(path + 6 + pid_len + 1, node, node_len);
- path[total_length] = '\0';
-}
-
bool
LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
bool member,
@@ -261,10 +134,8 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
void*
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
char auxv_path[NAME_MAX];
- BuildProcPath(auxv_path, pid, "auxv");
-
- // If BuildProcPath errors out due to invalid input, we'll handle it when
- // we try to sys_open the file.
+ if (!BuildProcPath(auxv_path, pid, "auxv"))
+ return NULL;
// Find the AT_SYSINFO_EHDR entry for linux-gate.so
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
@@ -290,7 +161,8 @@ LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
bool LinuxDumper::EnumerateMappings() {
char maps_path[NAME_MAX];
- BuildProcPath(maps_path, pid_, "maps");
+ 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
@@ -359,118 +231,6 @@ bool LinuxDumper::EnumerateMappings() {
return !mappings_.empty();
}
-// Parse /proc/$pid/task to list all the threads of the process identified by
-// pid.
-bool LinuxDumper::EnumerateThreads() {
- char task_path[NAME_MAX];
- BuildProcPath(task_path, pid_, "task");
-
- 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;
-}
-
-// 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 LinuxDumper::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];
- BuildProcPath(status_path, tid, "status");
-
- 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);
- }
-
- if (info->ppid == -1 || info->tgid == -1)
- return false;
-
- if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) {
- return false;
- }
-
-#if !defined(__ANDROID__)
- if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
- return false;
- }
-#endif
-
-#if defined(__i386)
- if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
- return false;
-#endif
-
-#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
-
- 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));
-#else
-#error "This code hasn't been ported to your platform yet."
-#endif
-
- return GetStackInfo(&info->stack, &info->stack_len,
- (uintptr_t) stack_pointer);
-}
-
// 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.
@@ -497,24 +257,6 @@ bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
return true;
}
-void LinuxDumper::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;
- }
- memcpy(local + done, &tmp, l);
- done += l;
- }
-}
-
// 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;
@@ -544,7 +286,8 @@ bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
// Check |path| against the /proc/pid/exe 'symlink'.
char exe_link[NAME_MAX];
char new_path[NAME_MAX];
- BuildProcPath(exe_link, pid_, "exe");
+ if (!BuildProcPath(exe_link, pid_, "exe"))
+ return false;
if (!SafeReadLink(exe_link, new_path))
return false;
if (my_strcmp(path, new_path) != 0)
diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h
index b0e4f855..45779699 100644
--- a/src/client/linux/minidump_writer/linux_dumper.h
+++ b/src/client/linux/minidump_writer/linux_dumper.h
@@ -27,6 +27,14 @@
// (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_
@@ -121,15 +129,18 @@ class LinuxDumper {
virtual ~LinuxDumper();
// Parse the data for |threads| and |mappings|.
- bool Init();
+ virtual bool Init();
+
+ // Return true if the dumper performs a post-mortem dump.
+ virtual bool IsPostMortem() const = 0;
// Suspend/resume all threads in the given process.
- bool ThreadsSuspend();
- bool ThreadsResume();
+ 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);
+ virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info) = 0;
// These are only valid after a call to |Init|.
const wasteful_vector<pid_t> &threads() { return threads_; }
@@ -146,13 +157,14 @@ class LinuxDumper {
// Copy content of |length| bytes from a given process |child|,
// starting from |src|, into |dest|.
- void CopyFromProcess(void* dest, pid_t child, const void* src,
- size_t length);
+ virtual void CopyFromProcess(void* dest, pid_t child, const void* src,
+ size_t length) = 0;
- // Builds a proc path for a certain pid for a node. path is a
- // character array that is overwritten, and node is the final node
- // without any slashes.
- void BuildProcPath(char* path, pid_t pid, const char* node) const;
+ // 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.
@@ -179,9 +191,10 @@ class LinuxDumper {
pid_t crash_thread() const { return crash_thread_; }
void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
- private:
- bool EnumerateMappings();
- bool EnumerateThreads();
+ protected:
+ 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
@@ -208,9 +221,11 @@ class LinuxDumper {
mutable PageAllocator allocator_;
- bool threads_suspended_;
- wasteful_vector<pid_t> threads_; // the ids of all the threads
- wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
+ // IDs of all the threads.
+ wasteful_vector<pid_t> threads_;
+
+ // Info from /proc/<pid>/maps.
+ wasteful_vector<MappingInfo*> mappings_;
};
} // namespace google_breakpad
diff --git a/src/client/linux/minidump_writer/linux_ptrace_dumper.cc b/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
new file mode 100644
index 00000000..ea58dd11
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
@@ -0,0 +1,292 @@
+// 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 "client/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/wait.h>
+
+#include "client/linux/minidump_writer/directory_reader.h"
+#include "client/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_int_len(pid);
+ const size_t total_length = 6 + pid_len + 1 + node_len;
+ if (total_length >= NAME_MAX)
+ return false;
+
+ memcpy(path, "/proc/", 6);
+ my_itos(path + 6, pid, pid_len);
+ path[6 + pid_len] = '/';
+ memcpy(path + 6 + pid_len + 1, node, node_len);
+ path[total_length] = '\0';
+ return true;
+}
+
+void 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;
+ }
+ memcpy(local + done, &tmp, l);
+ done += l;
+ }
+}
+
+// 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);
+ }
+
+ if (info->ppid == -1 || info->tgid == -1)
+ return false;
+
+ if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) {
+ return false;
+ }
+
+#if !defined(__ANDROID__)
+ if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
+ return false;
+ }
+#endif
+
+#if defined(__i386)
+ if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
+ return false;
+#endif
+
+#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
+
+ 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));
+#else
+#error "This code hasn't been ported to your platform yet."
+#endif
+
+ return GetStackInfo(&info->stack, &info->stack_len,
+ (uintptr_t) stack_pointer);
+}
+
+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.
+ memmove(&threads_[i], &threads_[i+1],
+ (threads_.size() - i - 1) * sizeof(threads_[i]));
+ threads_.resize(threads_.size() - 1);
+ --i;
+ }
+ }
+ threads_suspended_ = true;
+ return threads_.size() > 0;
+}
+
+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/src/client/linux/minidump_writer/linux_ptrace_dumper.h b/src/client/linux/minidump_writer/linux_ptrace_dumper.h
new file mode 100644
index 00000000..1e9bcfdf
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_ptrace_dumper.h
@@ -0,0 +1,92 @@
+// 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 "client/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.
+ virtual void 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::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_;
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_LINUX_PTRACE_DUMPER_H_
diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
index cf389b57..84e1974f 100644
--- a/src/client/linux/minidump_writer/linux_dumper_unittest.cc
+++ b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
@@ -27,7 +27,11 @@
// (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 <string>
+// linux_ptrace_dumper_unittest.cc:
+// Unit tests for google_breakpad::LinuxPtraceDumoer.
+//
+// This file was renamed from linux_dumper_unittest.cc and modified due
+// to LinuxDumper being splitted into two classes.
#include <fcntl.h>
#include <limits.h>
@@ -39,8 +43,10 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include <string>
+
#include "breakpad_googletest_includes.h"
-#include "client/linux/minidump_writer/linux_dumper.h"
+#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/file_id.h"
#include "common/linux/safe_readlink.h"
@@ -50,7 +56,8 @@ using std::string;
using namespace google_breakpad;
namespace {
-typedef testing::Test LinuxDumperTest;
+
+typedef testing::Test LinuxPtraceDumperTest;
string GetHelperBinary() {
// Locate helper binary next to the current binary.
@@ -69,14 +76,14 @@ string GetHelperBinary() {
return helper_path;
}
-}
+} // namespace
-TEST(LinuxDumperTest, Setup) {
- LinuxDumper dumper(getpid());
+TEST(LinuxPtraceDumperTest, Setup) {
+ LinuxPtraceDumper dumper(getpid());
}
-TEST(LinuxDumperTest, FindMappings) {
- LinuxDumper dumper(getpid());
+TEST(LinuxPtraceDumperTest, FindMappings) {
+ LinuxPtraceDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
@@ -84,8 +91,8 @@ TEST(LinuxDumperTest, FindMappings) {
ASSERT_FALSE(dumper.FindMapping(NULL));
}
-TEST(LinuxDumperTest, ThreadList) {
- LinuxDumper dumper(getpid());
+TEST(LinuxPtraceDumperTest, ThreadList) {
+ LinuxPtraceDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
ASSERT_GE(dumper.threads().size(), (size_t)1);
@@ -101,7 +108,7 @@ TEST(LinuxDumperTest, ThreadList) {
// Helper stack class to close a file descriptor and unmap
// a mmap'ed mapping.
class StackHelper {
-public:
+ public:
StackHelper(int fd, char* mapping, size_t size)
: fd_(fd), mapping_(mapping), size_(size) {}
~StackHelper() {
@@ -109,13 +116,13 @@ public:
close(fd_);
}
-private:
+ private:
int fd_;
char* mapping_;
size_t size_;
};
-TEST(LinuxDumperTest, MergedMappings) {
+TEST(LinuxPtraceDumperTest, MergedMappings) {
string helper_path(GetHelperBinary());
if (helper_path.empty()) {
FAIL() << "Couldn't find helper binary";
@@ -143,18 +150,19 @@ TEST(LinuxDumperTest, MergedMappings) {
StackHelper helper(fd, mapping, kMappingSize);
// Carve a page out of the first mapping with different permissions.
- char* inside_mapping = reinterpret_cast<char*>(mmap(mapping + 2 *kPageSize,
- kPageSize,
- PROT_NONE,
- MAP_SHARED | MAP_FIXED,
- fd,
- // Map a different offset just to
- // better test real-world conditions.
- kPageSize));
+ char* inside_mapping = reinterpret_cast<char*>(
+ mmap(mapping + 2 *kPageSize,
+ kPageSize,
+ PROT_NONE,
+ MAP_SHARED | MAP_FIXED,
+ fd,
+ // Map a different offset just to
+ // better test real-world conditions.
+ kPageSize));
ASSERT_TRUE(inside_mapping);
- // Now check that LinuxDumper interpreted the mappings properly.
- LinuxDumper dumper(getpid());
+ // Now check that LinuxPtraceDumper interpreted the mappings properly.
+ LinuxPtraceDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
int mapping_count = 0;
for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
@@ -171,7 +179,7 @@ TEST(LinuxDumperTest, MergedMappings) {
EXPECT_EQ(1, mapping_count);
}
-TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
+TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
static const int kNumberOfThreadsInHelperProgram = 5;
char kNumberOfThreadsArgument[2];
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
@@ -218,13 +226,13 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
close(fds[0]);
// Child is ready now.
- LinuxDumper dumper(child_pid);
+ LinuxPtraceDumper dumper(child_pid);
ASSERT_TRUE(dumper.Init());
EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
EXPECT_TRUE(dumper.ThreadsSuspend());
ThreadInfo one_thread;
- for(size_t i = 0; i < dumper.threads().size(); ++i) {
+ for (size_t i = 0; i < dumper.threads().size(); ++i) {
EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
// In the helper program, we stored a pointer to the thread id in a
// specific register. Check that we can recover its value.
@@ -247,39 +255,33 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
kill(child_pid, SIGKILL);
}
-TEST(LinuxDumperTest, BuildProcPath) {
+TEST(LinuxPtraceDumperTest, BuildProcPath) {
const pid_t pid = getpid();
- LinuxDumper dumper(pid);
+ LinuxPtraceDumper dumper(pid);
- char maps_path[256] = "dummymappath";
- char maps_path_expected[256];
+ char maps_path[NAME_MAX] = "";
+ char maps_path_expected[NAME_MAX];
snprintf(maps_path_expected, sizeof(maps_path_expected),
"/proc/%d/maps", pid);
- dumper.BuildProcPath(maps_path, pid, "maps");
- ASSERT_STREQ(maps_path, maps_path_expected);
-
- // In release mode, we expect BuildProcPath to handle the invalid
- // parameters correctly and fill map_path with an empty
- // NULL-terminated string.
-#ifdef NDEBUG
- snprintf(maps_path, sizeof(maps_path), "dummymappath");
- dumper.BuildProcPath(maps_path, 0, "maps");
- EXPECT_STREQ(maps_path, "");
-
- snprintf(maps_path, sizeof(maps_path), "dummymappath");
- dumper.BuildProcPath(maps_path, getpid(), "");
- EXPECT_STREQ(maps_path, "");
-
- snprintf(maps_path, sizeof(maps_path), "dummymappath");
- dumper.BuildProcPath(maps_path, getpid(), NULL);
- EXPECT_STREQ(maps_path, "");
-#endif
+ 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__)
// Ensure that the linux-gate VDSO is included in the mapping list.
-TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
- LinuxDumper dumper(getpid());
+TEST(LinuxPtraceDumperTest, MappingsIncludeLinuxGate) {
+ LinuxPtraceDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
@@ -301,8 +303,8 @@ TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
}
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
-TEST(LinuxDumperTest, LinuxGateMappingID) {
- LinuxDumper dumper(getpid());
+TEST(LinuxPtraceDumperTest, LinuxGateMappingID) {
+ LinuxPtraceDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
bool found_linux_gate = false;
@@ -329,7 +331,7 @@ TEST(LinuxDumperTest, LinuxGateMappingID) {
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID
// from a child process.
-TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
+TEST(LinuxPtraceDumperTest, LinuxGateMappingIDChild) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
@@ -345,7 +347,7 @@ TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
}
close(fds[0]);
- LinuxDumper dumper(child);
+ LinuxPtraceDumper dumper(child);
ASSERT_TRUE(dumper.Init());
bool found_linux_gate = false;
@@ -375,7 +377,7 @@ TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
}
#endif
-TEST(LinuxDumperTest, FileIDsMatch) {
+TEST(LinuxPtraceDumperTest, 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.
@@ -397,7 +399,7 @@ TEST(LinuxDumperTest, FileIDsMatch) {
}
close(fds[0]);
- LinuxDumper dumper(child);
+ LinuxPtraceDumper dumper(child);
ASSERT_TRUE(dumper.Init());
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
bool found_exe = false;
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
index 9a3d37fe..c6feabdd 100644
--- a/src/client/linux/minidump_writer/minidump_writer.cc
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -74,6 +74,8 @@
#include "client/linux/handler/exception_handler.h"
#include "client/linux/minidump_writer/line_reader.h"
#include "client/linux/minidump_writer/linux_dumper.h"
+#include "client/linux/minidump_writer/linux_core_dumper.h"
+#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
#include "client/linux/minidump_writer/minidump_extension_linux.h"
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
@@ -372,9 +374,9 @@ class MinidumpWriter {
const MappingList& mappings,
LinuxDumper* dumper)
: filename_(filename),
- ucontext_(&context->context),
+ ucontext_(context ? &context->context : NULL),
#if !defined(__ARM_EABI__)
- float_state_(&context->float_state),
+ float_state_(context ? &context->float_state : NULL),
#else
// TODO: fix this after fixing ExceptionHandler
float_state_(NULL),
@@ -401,18 +403,25 @@ class MinidumpWriter {
struct r_debug* r_debug = NULL;
uint32_t dynamic_length = 0;
#if !defined(__ANDROID__)
- // The Android NDK is missing structure definitions for most of this.
- // For now, it's simpler just to skip it.
- for (int i = 0;;) {
- ElfW(Dyn) dyn;
- dynamic_length += sizeof(dyn);
- dumper_->CopyFromProcess(&dyn, GetCrashThread(), _DYNAMIC+i++,
- sizeof(dyn));
- if (dyn.d_tag == DT_DEBUG) {
- r_debug = (struct r_debug*)dyn.d_un.d_ptr;
- continue;
- } else if (dyn.d_tag == DT_NULL) {
- break;
+ // This code assumes the crashing process is the same as this process and
+ // may hang or take a long time to complete if not so.
+ // Thus, we skip this code for a post-mortem based dump.
+ if (!dumper_->IsPostMortem()) {
+ // The Android NDK is missing structure definitions for most of this.
+ // For now, it's simpler just to skip it.
+ for (int i = 0;;) {
+ ElfW(Dyn) dyn;
+ dynamic_length += sizeof(dyn);
+ // NOTE: Use of _DYNAMIC assumes this is the same process as the
+ // crashing process. This loop will go forever if it's out of bounds.
+ dumper_->CopyFromProcess(&dyn, GetCrashThread(), _DYNAMIC+i++,
+ sizeof(dyn));
+ if (dyn.d_tag == DT_DEBUG) {
+ r_debug = (struct r_debug*)dyn.d_un.d_ptr;
+ continue;
+ } else if (dyn.d_tag == DT_NULL) {
+ break;
+ }
}
}
#endif
@@ -647,7 +656,8 @@ class MinidumpWriter {
// 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 ((pid_t)thread.thread_id == GetCrashThread()) {
+ if (static_cast<pid_t>(thread.thread_id) == GetCrashThread() &&
+ !dumper_->IsPostMortem()) {
const void* stack;
size_t stack_len;
if (!dumper_->GetStackInfo(&stack, &stack_len, GetStackPointer()))
@@ -736,6 +746,10 @@ class MinidumpWriter {
CPUFillFromThreadInfo(cpu.get(), info);
PopSeccompStackFrame(cpu.get(), thread, stack_copy);
thread.thread_context = cpu.location();
+ if (dumper_->threads()[i] == GetCrashThread()) {
+ assert(dumper_->IsPostMortem());
+ crashing_thread_context_ = cpu.location();
+ }
}
list.CopyIndexAfterObject(i, &thread, sizeof(thread));
@@ -1281,7 +1295,8 @@ class MinidumpWriter {
bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
const char* filename) {
char buf[NAME_MAX];
- dumper_->BuildProcPath(buf, pid, filename);
+ if (!dumper_->BuildProcPath(buf, pid, filename))
+ return false;
return WriteFile(result, buf);
}
@@ -1312,7 +1327,7 @@ bool WriteMinidump(const char* filename, pid_t crashing_process,
return false;
const ExceptionHandler::CrashContext* context =
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
- LinuxDumper dumper(crashing_process);
+ LinuxPtraceDumper dumper(crashing_process);
dumper.set_crash_address(
reinterpret_cast<uintptr_t>(context->siginfo.si_addr));
dumper.set_crash_signal(context->siginfo.si_signo);
@@ -1323,4 +1338,15 @@ bool WriteMinidump(const char* filename, pid_t crashing_process,
return writer.Dump();
}
+bool WriteMinidumpFromCore(const char* filename,
+ const char* core_path,
+ const char* procfs_override) {
+ MappingList mappings;
+ LinuxCoreDumper dumper(0, core_path, procfs_override);
+ MinidumpWriter writer(filename, NULL, mappings, &dumper);
+ if (!writer.Init())
+ return false;
+ return writer.Dump();
+}
+
} // namespace google_breakpad
diff --git a/src/client/linux/minidump_writer/minidump_writer.h b/src/client/linux/minidump_writer/minidump_writer.h
index 156ca35f..731249e3 100644
--- a/src/client/linux/minidump_writer/minidump_writer.h
+++ b/src/client/linux/minidump_writer/minidump_writer.h
@@ -62,6 +62,12 @@ bool WriteMinidump(const char* filename, pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings);
+// Write a minidump to the filesystem. Same as above, but uses the given
+// core file and procfs directory to generate the minidump post mortem.
+bool WriteMinidumpFromCore(const char* filename,
+ const char* core_path,
+ const char* procfs_override);
+
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
diff --git a/src/common/linux/elf_core_dump_unittest.cc b/src/common/linux/elf_core_dump_unittest.cc
index 89496ab9..11920f25 100644
--- a/src/common/linux/elf_core_dump_unittest.cc
+++ b/src/common/linux/elf_core_dump_unittest.cc
@@ -141,7 +141,7 @@ TEST(ElfCoreDumpTest, ValidCoreFile) {
// TODO(benchan): Revert to use ASSERT_TRUE once the flakiness in
// CrashGenerator is identified and fixed.
if (!crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
- kCrashSignal)) {
+ kCrashSignal, NULL)) {
fprintf(stderr, "ElfCoreDumpTest.ValidCoreFile test is skipped "
"due to no core dump generated");
return;
diff --git a/src/common/linux/tests/crash_generator.cc b/src/common/linux/tests/crash_generator.cc
index fd96e6ca..7274acf9 100644
--- a/src/common/linux/tests/crash_generator.cc
+++ b/src/common/linux/tests/crash_generator.cc
@@ -144,7 +144,8 @@ bool CrashGenerator::SetCoreFileSizeLimit(rlim_t limit) const {
}
bool CrashGenerator::CreateChildCrash(
- unsigned num_threads, unsigned crash_thread, int crash_signal) {
+ unsigned num_threads, unsigned crash_thread, int crash_signal,
+ pid_t* child_pid) {
if (num_threads == 0 || crash_thread >= num_threads)
return false;
@@ -178,6 +179,9 @@ bool CrashGenerator::CreateChildCrash(
perror("CrashGenerator: Child process not killed by the expected signal");
return false;
}
+
+ if (child_pid)
+ *child_pid = pid;
return true;
}
diff --git a/src/common/linux/tests/crash_generator.h b/src/common/linux/tests/crash_generator.h
index c88a8920..874b15cd 100644
--- a/src/common/linux/tests/crash_generator.h
+++ b/src/common/linux/tests/crash_generator.h
@@ -70,7 +70,7 @@ class CrashGenerator {
// a signal with number |crash_signal| to the |crash_thread|-th thread.
// Returns true on success.
bool CreateChildCrash(unsigned num_threads, unsigned crash_thread,
- int crash_signal);
+ int crash_signal, pid_t* child_pid);
// Creates |num_threads| threads in the child process.
void CreateThreadsInChildProcess(unsigned num_threads);
diff --git a/src/tools/linux/core2md/core2md.cc b/src/tools/linux/core2md/core2md.cc
new file mode 100644
index 00000000..c8060b5e
--- /dev/null
+++ b/src/tools/linux/core2md/core2md.cc
@@ -0,0 +1,57 @@
+// 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.
+
+// core2md.cc: A utility to convert an ELF core file to a minidump file.
+
+#include <stdio.h>
+
+#include "client/linux/minidump_writer/minidump_writer.h"
+
+static int ShowUsage(const char* argv0) {
+ fprintf(stderr, "Usage: %s <core file> <procfs dir> <output>\n", argv0);
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ if (argc != 4) {
+ return ShowUsage(argv[0]);
+ }
+
+ const char* core_file = argv[1];
+ const char* procfs_dir = argv[2];
+ const char* minidump_file = argv[3];
+ if (!google_breakpad::WriteMinidumpFromCore(minidump_file,
+ core_file,
+ procfs_dir)) {
+ fprintf(stderr, "Unable to generate minidump.\n");
+ return 1;
+ }
+
+ return 0;
+}