diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/crashreporter/google-breakpad/src/tools/linux | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/google-breakpad/src/tools/linux')
8 files changed, 2430 insertions, 0 deletions
diff --git a/toolkit/crashreporter/google-breakpad/src/tools/linux/core2md/core2md.cc b/toolkit/crashreporter/google-breakpad/src/tools/linux/core2md/core2md.cc new file mode 100644 index 0000000000..c3a9da3988 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/tools/linux/core2md/core2md.cc @@ -0,0 +1,72 @@ +// 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" +#include "client/linux/minidump_writer/linux_core_dumper.h" + +using google_breakpad::AppMemoryList; +using google_breakpad::MappingList; +using google_breakpad::LinuxCoreDumper; + +static int ShowUsage(const char* argv0) { + fprintf(stderr, "Usage: %s <core file> <procfs dir> <output>\n", argv0); + return 1; +} + +bool WriteMinidumpFromCore(const char* filename, + const char* core_path, + const char* procfs_override) { + MappingList mappings; + AppMemoryList memory_list; + LinuxCoreDumper dumper(0, core_path, procfs_override); + return google_breakpad::WriteMinidump(filename, mappings, memory_list, + &dumper); +} + +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 (!WriteMinidumpFromCore(minidump_file, + core_file, + procfs_dir)) { + perror("core2md: Unable to generate minidump"); + return 1; + } + + return 0; +} diff --git a/toolkit/crashreporter/google-breakpad/src/tools/linux/dump_syms/dump_syms.cc b/toolkit/crashreporter/google-breakpad/src/tools/linux/dump_syms/dump_syms.cc new file mode 100644 index 0000000000..ebdf23146c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/tools/linux/dump_syms/dump_syms.cc @@ -0,0 +1,137 @@ +// 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 <paths.h> +#include <stdio.h> +#include <unistd.h> + +#include <cstring> +#include <iostream> +#include <string> +#include <vector> + +#include "common/linux/dump_symbols.h" + +using google_breakpad::WriteSymbolFile; +using google_breakpad::WriteSymbolFileHeader; + +int usage(const char* self) { + fprintf(stderr, "Usage: %s [OPTION] <binary-with-debugging-info> " + "[directories-for-debug-file]\n\n", self); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -i: Output module header information only.\n"); + fprintf(stderr, " -c Do not generate CFI section\n"); + fprintf(stderr, " -r Do not handle inter-compilation " + "unit references\n"); + fprintf(stderr, " -v Print all warnings to stderr\n"); + fprintf(stderr, " -n <name> Use specified name for name of the object\n"); + fprintf(stderr, " -o <os> Use specified name for the " + "operating system\n"); + return 1; +} + +int main(int argc, char **argv) { + if (argc < 2) + return usage(argv[0]); + bool header_only = false; + bool cfi = true; + bool handle_inter_cu_refs = true; + bool log_to_stderr = false; + std::string obj_name; + const char* obj_os = "Linux"; + int arg_index = 1; + while (arg_index < argc && strlen(argv[arg_index]) > 0 && + argv[arg_index][0] == '-') { + if (strcmp("-i", argv[arg_index]) == 0) { + header_only = true; + } else if (strcmp("-c", argv[arg_index]) == 0) { + cfi = false; + } else if (strcmp("-r", argv[arg_index]) == 0) { + handle_inter_cu_refs = false; + } else if (strcmp("-v", argv[arg_index]) == 0) { + log_to_stderr = true; + } else if (strcmp("-n", argv[arg_index]) == 0) { + if (arg_index + 1 >= argc) { + fprintf(stderr, "Missing argument to -n\n"); + return usage(argv[0]); + } + obj_name = argv[arg_index + 1]; + ++arg_index; + } else if (strcmp("-o", argv[arg_index]) == 0) { + if (arg_index + 1 >= argc) { + fprintf(stderr, "Missing argument to -o\n"); + return usage(argv[0]); + } + obj_os = argv[arg_index + 1]; + ++arg_index; + } else { + printf("2.4 %s\n", argv[arg_index]); + return usage(argv[0]); + } + ++arg_index; + } + if (arg_index == argc) + return usage(argv[0]); + // Save stderr so it can be used below. + FILE* saved_stderr = fdopen(dup(fileno(stderr)), "w"); + if (!log_to_stderr) { + if (freopen(_PATH_DEVNULL, "w", stderr)) { + // If it fails, not a lot we can (or should) do. + // Add this brace section to silence gcc warnings. + } + } + const char* binary; + std::vector<string> debug_dirs; + binary = argv[arg_index]; + for (int debug_dir_index = arg_index + 1; + debug_dir_index < argc; + ++debug_dir_index) { + debug_dirs.push_back(argv[debug_dir_index]); + } + + if (obj_name.empty()) + obj_name = binary; + + if (header_only) { + if (!WriteSymbolFileHeader(binary, obj_name, obj_os, std::cout)) { + fprintf(saved_stderr, "Failed to process file.\n"); + return 1; + } + } else { + SymbolData symbol_data = cfi ? ALL_SYMBOL_DATA : NO_CFI; + google_breakpad::DumpOptions options(symbol_data, handle_inter_cu_refs); + if (!WriteSymbolFile(binary, obj_name, obj_os, debug_dirs, options, + std::cout)) { + fprintf(saved_stderr, "Failed to write symbol file.\n"); + return 1; + } + } + + return 0; +} diff --git a/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump-2-core.cc b/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump-2-core.cc new file mode 100644 index 0000000000..a60be32354 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump-2-core.cc @@ -0,0 +1,1428 @@ +// 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. + +// Converts a minidump file to a core file which gdb can read. +// Large parts lifted from the userspace core dumper: +// http://code.google.com/p/google-coredumper/ + +#include <elf.h> +#include <errno.h> +#include <limits.h> +#include <link.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/user.h> +#include <unistd.h> + +#include <map> +#include <string> +#include <vector> + +#include "common/linux/memory_mapped_file.h" +#include "common/minidump_type_helper.h" +#include "common/path_helper.h" +#include "common/scoped_ptr.h" +#include "common/using_std_string.h" +#include "google_breakpad/common/breakpad_types.h" +#include "google_breakpad/common/minidump_format.h" +#include "third_party/lss/linux_syscall_support.h" +#include "tools/linux/md2core/minidump_memory_range.h" + +#if ULONG_MAX == 0xffffffffffffffff + #define ELF_CLASS ELFCLASS64 +#else + #define ELF_CLASS ELFCLASS32 +#endif +#define Ehdr ElfW(Ehdr) +#define Phdr ElfW(Phdr) +#define Shdr ElfW(Shdr) +#define Nhdr ElfW(Nhdr) +#define auxv_t ElfW(auxv_t) + + +#if defined(__x86_64__) + #define ELF_ARCH EM_X86_64 +#elif defined(__i386__) + #define ELF_ARCH EM_386 +#elif defined(__arm__) + #define ELF_ARCH EM_ARM +#elif defined(__mips__) + #define ELF_ARCH EM_MIPS +#elif defined(__aarch64__) + #define ELF_ARCH EM_AARCH64 +#endif + +#if defined(__arm__) +// GLibc/ARM and Android/ARM both use 'user_regs' for the structure type +// containing core registers, while they use 'user_regs_struct' on other +// architectures. This file-local typedef simplifies the source code. +typedef user_regs user_regs_struct; +#elif defined (__mips__) +// This file-local typedef simplifies the source code. +typedef gregset_t user_regs_struct; +#endif + +using google_breakpad::MDTypeHelper; +using google_breakpad::MemoryMappedFile; +using google_breakpad::MinidumpMemoryRange; + +typedef MDTypeHelper<sizeof(ElfW(Addr))>::MDRawDebug MDRawDebug; +typedef MDTypeHelper<sizeof(ElfW(Addr))>::MDRawLinkMap MDRawLinkMap; + +static const MDRVA kInvalidMDRVA = static_cast<MDRVA>(-1); + +struct Options { + string minidump_path; + bool verbose; + int out_fd; + bool use_filename; + bool inc_guid; + string so_basedir; +}; + +static void +Usage(int argc, const char* argv[]) { + fprintf(stderr, + "Usage: %s [options] <minidump file>\n" + "\n" + "Convert a minidump file into a core file (often for use by gdb).\n" + "\n" + "The shared library list will by default have filenames as the runtime expects.\n" + "There are many flags to control the output names though to make it easier to\n" + "integrate with your debug environment (e.g. gdb).\n" + " Default: /lib64/libpthread.so.0\n" + " -f: /lib64/libpthread-2.19.so\n" + " -i: /lib64/<module id>-libpthread.so.0\n" + " -f -i: /lib64/<module id>-libpthread-2.19.so\n" + " -S /foo/: /foo/libpthread.so.0\n" + "\n" + "Options:\n" + " -v Enable verbose output\n" + " -o <file> Write coredump to specified file (otherwise use stdout).\n" + " -f Use the filename rather than the soname in the sharedlib list.\n" + " The soname is what the runtime system uses, but the filename is\n" + " how it's stored on disk.\n" + " -i Prefix sharedlib names with ID (when available). This makes it\n" + " easier to have a single directory full of symbols.\n" + " -S <dir> Set soname base directory. This will force all debug/symbol\n" + " lookups to be done in this directory rather than the filesystem\n" + " layout as it exists in the crashing image. This path should end\n" + " with a slash if it's a directory. e.g. /var/lib/breakpad/\n" + "", google_breakpad::BaseName(argv[0]).c_str()); +} + +static void +SetupOptions(int argc, const char* argv[], Options* options) { + extern int optind; + int ch; + const char* output_file = NULL; + + // Initialize the options struct as needed. + options->verbose = false; + options->use_filename = false; + options->inc_guid = false; + + while ((ch = getopt(argc, (char * const *)argv, "fhio:S:v")) != -1) { + switch (ch) { + case 'h': + Usage(argc, argv); + exit(0); + break; + case '?': + Usage(argc, argv); + exit(1); + break; + + case 'f': + options->use_filename = true; + break; + case 'i': + options->inc_guid = true; + break; + case 'o': + output_file = optarg; + break; + case 'S': + options->so_basedir = optarg; + break; + case 'v': + options->verbose = true; + break; + } + } + + if ((argc - optind) != 1) { + fprintf(stderr, "%s: Missing minidump file\n", argv[0]); + Usage(argc, argv); + exit(1); + } + + if (output_file == NULL || !strcmp(output_file, "-")) { + options->out_fd = STDOUT_FILENO; + } else { + options->out_fd = open(output_file, O_WRONLY|O_CREAT|O_TRUNC, 0664); + if (options->out_fd == -1) { + fprintf(stderr, "%s: could not open output %s: %s\n", argv[0], + output_file, strerror(errno)); + exit(1); + } + } + + options->minidump_path = argv[optind]; +} + +// Write all of the given buffer, handling short writes and EINTR. Return true +// iff successful. +static bool +writea(int fd, const void* idata, size_t length) { + const uint8_t* data = (const uint8_t*) idata; + + size_t done = 0; + while (done < length) { + ssize_t r; + do { + r = write(fd, data + done, length - done); + } while (r == -1 && errno == EINTR); + + if (r < 1) + return false; + done += r; + } + + return true; +} + +/* Dynamically determines the byte sex of the system. Returns non-zero + * for big-endian machines. + */ +static inline int sex() { + int probe = 1; + return !*(char *)&probe; +} + +typedef struct elf_timeval { /* Time value with microsecond resolution */ + long tv_sec; /* Seconds */ + long tv_usec; /* Microseconds */ +} elf_timeval; + +typedef struct _elf_siginfo { /* Information about signal (unused) */ + int32_t si_signo; /* Signal number */ + int32_t si_code; /* Extra code */ + int32_t si_errno; /* Errno */ +} _elf_siginfo; + +typedef struct prstatus { /* Information about thread; includes CPU reg*/ + _elf_siginfo pr_info; /* Info associated with signal */ + uint16_t pr_cursig; /* Current signal */ + unsigned long pr_sigpend; /* Set of pending signals */ + unsigned long pr_sighold; /* Set of held signals */ + pid_t pr_pid; /* Process ID */ + pid_t pr_ppid; /* Parent's process ID */ + pid_t pr_pgrp; /* Group ID */ + pid_t pr_sid; /* Session ID */ + elf_timeval pr_utime; /* User time */ + elf_timeval pr_stime; /* System time */ + elf_timeval pr_cutime; /* Cumulative user time */ + elf_timeval pr_cstime; /* Cumulative system time */ + user_regs_struct pr_reg; /* CPU registers */ + uint32_t pr_fpvalid; /* True if math co-processor being used */ +} prstatus; + +typedef struct prpsinfo { /* Information about process */ + unsigned char pr_state; /* Numeric process state */ + char pr_sname; /* Char for pr_state */ + unsigned char pr_zomb; /* Zombie */ + signed char pr_nice; /* Nice val */ + unsigned long pr_flag; /* Flags */ +#if defined(__x86_64__) || defined(__mips__) + uint32_t pr_uid; /* User ID */ + uint32_t pr_gid; /* Group ID */ +#else + uint16_t pr_uid; /* User ID */ + uint16_t pr_gid; /* Group ID */ +#endif + pid_t pr_pid; /* Process ID */ + pid_t pr_ppid; /* Parent's process ID */ + pid_t pr_pgrp; /* Group ID */ + pid_t pr_sid; /* Session ID */ + char pr_fname[16]; /* Filename of executable */ + char pr_psargs[80]; /* Initial part of arg list */ +} prpsinfo; + +// We parse the minidump file and keep the parsed information in this structure +struct CrashedProcess { + CrashedProcess() + : crashing_tid(-1), + auxv(NULL), + auxv_length(0) { + memset(&prps, 0, sizeof(prps)); + prps.pr_sname = 'R'; + memset(&debug, 0, sizeof(debug)); + } + + struct Mapping { + Mapping() + : permissions(0xFFFFFFFF), + start_address(0), + end_address(0), + offset(0) { + } + + uint32_t permissions; + uint64_t start_address, end_address, offset; + // The name we write out to the core. + string filename; + string data; + }; + std::map<uint64_t, Mapping> mappings; + + pid_t crashing_tid; + int fatal_signal; + + struct Thread { + pid_t tid; +#if defined(__mips__) + mcontext_t mcontext; +#else + user_regs_struct regs; +#endif +#if defined(__i386__) || defined(__x86_64__) + user_fpregs_struct fpregs; +#endif +#if defined(__i386__) + user_fpxregs_struct fpxregs; +#endif +#if defined(__aarch64__) + user_fpsimd_struct fpregs; +#endif + uintptr_t stack_addr; + const uint8_t* stack; + size_t stack_length; + }; + std::vector<Thread> threads; + + const uint8_t* auxv; + size_t auxv_length; + + prpsinfo prps; + + // The GUID/filename from MD_MODULE_LIST_STREAM entries. + // We gather them for merging later on into the list of maps. + struct Signature { + char guid[40]; + string filename; + }; + std::map<uintptr_t, Signature> signatures; + + string dynamic_data; + MDRawDebug debug; + std::vector<MDRawLinkMap> link_map; +}; + +#if defined(__i386__) +static uint32_t +U32(const uint8_t* data) { + uint32_t v; + memcpy(&v, data, sizeof(v)); + return v; +} + +static uint16_t +U16(const uint8_t* data) { + uint16_t v; + memcpy(&v, data, sizeof(v)); + return v; +} + +static void +ParseThreadRegisters(CrashedProcess::Thread* thread, + const MinidumpMemoryRange& range) { + const MDRawContextX86* rawregs = range.GetData<MDRawContextX86>(0); + + thread->regs.ebx = rawregs->ebx; + thread->regs.ecx = rawregs->ecx; + thread->regs.edx = rawregs->edx; + thread->regs.esi = rawregs->esi; + thread->regs.edi = rawregs->edi; + thread->regs.ebp = rawregs->ebp; + thread->regs.eax = rawregs->eax; + thread->regs.xds = rawregs->ds; + thread->regs.xes = rawregs->es; + thread->regs.xfs = rawregs->fs; + thread->regs.xgs = rawregs->gs; + thread->regs.orig_eax = rawregs->eax; + thread->regs.eip = rawregs->eip; + thread->regs.xcs = rawregs->cs; + thread->regs.eflags = rawregs->eflags; + thread->regs.esp = rawregs->esp; + thread->regs.xss = rawregs->ss; + + thread->fpregs.cwd = rawregs->float_save.control_word; + thread->fpregs.swd = rawregs->float_save.status_word; + thread->fpregs.twd = rawregs->float_save.tag_word; + thread->fpregs.fip = rawregs->float_save.error_offset; + thread->fpregs.fcs = rawregs->float_save.error_selector; + thread->fpregs.foo = rawregs->float_save.data_offset; + thread->fpregs.fos = rawregs->float_save.data_selector; + memcpy(thread->fpregs.st_space, rawregs->float_save.register_area, + 10 * 8); + + thread->fpxregs.cwd = rawregs->float_save.control_word; + thread->fpxregs.swd = rawregs->float_save.status_word; + thread->fpxregs.twd = rawregs->float_save.tag_word; + thread->fpxregs.fop = U16(rawregs->extended_registers + 6); + thread->fpxregs.fip = U16(rawregs->extended_registers + 8); + thread->fpxregs.fcs = U16(rawregs->extended_registers + 12); + thread->fpxregs.foo = U16(rawregs->extended_registers + 16); + thread->fpxregs.fos = U16(rawregs->extended_registers + 20); + thread->fpxregs.mxcsr = U32(rawregs->extended_registers + 24); + memcpy(thread->fpxregs.st_space, rawregs->extended_registers + 32, 128); + memcpy(thread->fpxregs.xmm_space, rawregs->extended_registers + 160, 128); +} +#elif defined(__x86_64__) +static void +ParseThreadRegisters(CrashedProcess::Thread* thread, + const MinidumpMemoryRange& range) { + const MDRawContextAMD64* rawregs = range.GetData<MDRawContextAMD64>(0); + + thread->regs.r15 = rawregs->r15; + thread->regs.r14 = rawregs->r14; + thread->regs.r13 = rawregs->r13; + thread->regs.r12 = rawregs->r12; + thread->regs.rbp = rawregs->rbp; + thread->regs.rbx = rawregs->rbx; + thread->regs.r11 = rawregs->r11; + thread->regs.r10 = rawregs->r10; + thread->regs.r9 = rawregs->r9; + thread->regs.r8 = rawregs->r8; + thread->regs.rax = rawregs->rax; + thread->regs.rcx = rawregs->rcx; + thread->regs.rdx = rawregs->rdx; + thread->regs.rsi = rawregs->rsi; + thread->regs.rdi = rawregs->rdi; + thread->regs.orig_rax = rawregs->rax; + thread->regs.rip = rawregs->rip; + thread->regs.cs = rawregs->cs; + thread->regs.eflags = rawregs->eflags; + thread->regs.rsp = rawregs->rsp; + thread->regs.ss = rawregs->ss; + thread->regs.fs_base = 0; + thread->regs.gs_base = 0; + thread->regs.ds = rawregs->ds; + thread->regs.es = rawregs->es; + thread->regs.fs = rawregs->fs; + thread->regs.gs = rawregs->gs; + + thread->fpregs.cwd = rawregs->flt_save.control_word; + thread->fpregs.swd = rawregs->flt_save.status_word; + thread->fpregs.ftw = rawregs->flt_save.tag_word; + thread->fpregs.fop = rawregs->flt_save.error_opcode; + thread->fpregs.rip = rawregs->flt_save.error_offset; + thread->fpregs.rdp = rawregs->flt_save.data_offset; + thread->fpregs.mxcsr = rawregs->flt_save.mx_csr; + thread->fpregs.mxcr_mask = rawregs->flt_save.mx_csr_mask; + memcpy(thread->fpregs.st_space, rawregs->flt_save.float_registers, 8 * 16); + memcpy(thread->fpregs.xmm_space, rawregs->flt_save.xmm_registers, 16 * 16); +} +#elif defined(__arm__) +static void +ParseThreadRegisters(CrashedProcess::Thread* thread, + const MinidumpMemoryRange& range) { + const MDRawContextARM* rawregs = range.GetData<MDRawContextARM>(0); + + thread->regs.uregs[0] = rawregs->iregs[0]; + thread->regs.uregs[1] = rawregs->iregs[1]; + thread->regs.uregs[2] = rawregs->iregs[2]; + thread->regs.uregs[3] = rawregs->iregs[3]; + thread->regs.uregs[4] = rawregs->iregs[4]; + thread->regs.uregs[5] = rawregs->iregs[5]; + thread->regs.uregs[6] = rawregs->iregs[6]; + thread->regs.uregs[7] = rawregs->iregs[7]; + thread->regs.uregs[8] = rawregs->iregs[8]; + thread->regs.uregs[9] = rawregs->iregs[9]; + thread->regs.uregs[10] = rawregs->iregs[10]; + thread->regs.uregs[11] = rawregs->iregs[11]; + thread->regs.uregs[12] = rawregs->iregs[12]; + thread->regs.uregs[13] = rawregs->iregs[13]; + thread->regs.uregs[14] = rawregs->iregs[14]; + thread->regs.uregs[15] = rawregs->iregs[15]; + + thread->regs.uregs[16] = rawregs->cpsr; + thread->regs.uregs[17] = 0; // what is ORIG_r0 exactly? +} +#elif defined(__aarch64__) +static void +ParseThreadRegisters(CrashedProcess::Thread* thread, + const MinidumpMemoryRange& range) { +#define COPY_REGS(rawregs) \ + do { \ + for (int i = 0; i < 31; ++i) \ + thread->regs.regs[i] = rawregs->iregs[i]; \ + thread->regs.sp = rawregs->iregs[MD_CONTEXT_ARM64_REG_SP]; \ + thread->regs.pc = rawregs->iregs[MD_CONTEXT_ARM64_REG_PC]; \ + thread->regs.pstate = rawregs->cpsr; \ + \ + memcpy(thread->fpregs.vregs, rawregs->float_save.regs, 8 * 32); \ + thread->fpregs.fpsr = rawregs->float_save.fpsr; \ + thread->fpregs.fpcr = rawregs->float_save.fpcr; \ + } while (false) + + if (range.length() == sizeof(MDRawContextARM64_Old)) { + const MDRawContextARM64_Old* rawregs = + range.GetData<MDRawContextARM64_Old>(0); + COPY_REGS(rawregs); + } else { + const MDRawContextARM64* rawregs = range.GetData<MDRawContextARM64>(0); + COPY_REGS(rawregs); + } +#undef COPY_REGS +} +#elif defined(__mips__) +static void +ParseThreadRegisters(CrashedProcess::Thread* thread, + const MinidumpMemoryRange& range) { + const MDRawContextMIPS* rawregs = range.GetData<MDRawContextMIPS>(0); + + for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i) + thread->mcontext.gregs[i] = rawregs->iregs[i]; + + thread->mcontext.pc = rawregs->epc; + + thread->mcontext.mdlo = rawregs->mdlo; + thread->mcontext.mdhi = rawregs->mdhi; + + thread->mcontext.hi1 = rawregs->hi[0]; + thread->mcontext.lo1 = rawregs->lo[0]; + thread->mcontext.hi2 = rawregs->hi[1]; + thread->mcontext.lo2 = rawregs->lo[1]; + thread->mcontext.hi3 = rawregs->hi[2]; + thread->mcontext.lo3 = rawregs->lo[2]; + + for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i) { + thread->mcontext.fpregs.fp_r.fp_fregs[i]._fp_fregs = + rawregs->float_save.regs[i]; + } + + thread->mcontext.fpc_csr = rawregs->float_save.fpcsr; +#if _MIPS_SIM == _ABIO32 + thread->mcontext.fpc_eir = rawregs->float_save.fir; +#endif +} +#else +#error "This code has not been ported to your platform yet" +#endif + +static void +ParseThreadList(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range, + const MinidumpMemoryRange& full_file) { + const uint32_t num_threads = *range.GetData<uint32_t>(0); + if (options.verbose) { + fprintf(stderr, + "MD_THREAD_LIST_STREAM:\n" + "Found %d threads\n" + "\n\n", + num_threads); + } + for (unsigned i = 0; i < num_threads; ++i) { + CrashedProcess::Thread thread; + memset(&thread, 0, sizeof(thread)); + const MDRawThread* rawthread = + range.GetArrayElement<MDRawThread>(sizeof(uint32_t), i); + thread.tid = rawthread->thread_id; + thread.stack_addr = rawthread->stack.start_of_memory_range; + MinidumpMemoryRange stack_range = + full_file.Subrange(rawthread->stack.memory); + thread.stack = stack_range.data(); + thread.stack_length = rawthread->stack.memory.data_size; + + ParseThreadRegisters(&thread, + full_file.Subrange(rawthread->thread_context)); + + crashinfo->threads.push_back(thread); + } +} + +static void +ParseSystemInfo(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range, + const MinidumpMemoryRange& full_file) { + const MDRawSystemInfo* sysinfo = range.GetData<MDRawSystemInfo>(0); + if (!sysinfo) { + fprintf(stderr, "Failed to access MD_SYSTEM_INFO_STREAM\n"); + exit(1); + } +#if defined(__i386__) + if (sysinfo->processor_architecture != MD_CPU_ARCHITECTURE_X86) { + fprintf(stderr, + "This version of minidump-2-core only supports x86 (32bit)%s.\n", + sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_AMD64 ? + ",\nbut the minidump file is from a 64bit machine" : ""); + exit(1); + } +#elif defined(__x86_64__) + if (sysinfo->processor_architecture != MD_CPU_ARCHITECTURE_AMD64) { + fprintf(stderr, + "This version of minidump-2-core only supports x86 (64bit)%s.\n", + sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_X86 ? + ",\nbut the minidump file is from a 32bit machine" : ""); + exit(1); + } +#elif defined(__arm__) + if (sysinfo->processor_architecture != MD_CPU_ARCHITECTURE_ARM) { + fprintf(stderr, + "This version of minidump-2-core only supports ARM (32bit).\n"); + exit(1); + } +#elif defined(__aarch64__) + if (sysinfo->processor_architecture != MD_CPU_ARCHITECTURE_ARM64_OLD) { + fprintf(stderr, + "This version of minidump-2-core only supports ARM (64bit).\n"); + exit(1); + } +#elif defined(__mips__) +# if _MIPS_SIM == _ABIO32 + if (sysinfo->processor_architecture != MD_CPU_ARCHITECTURE_MIPS) { + fprintf(stderr, + "This version of minidump-2-core only supports mips o32 (32bit).\n"); + exit(1); + } +# elif _MIPS_SIM == _ABI64 + if (sysinfo->processor_architecture != MD_CPU_ARCHITECTURE_MIPS64) { + fprintf(stderr, + "This version of minidump-2-core only supports mips n64 (64bit).\n"); + exit(1); + } +# else +# error "This mips ABI is currently not supported (n32)" +# endif +#else +#error "This code has not been ported to your platform yet" +#endif + if (!strstr(full_file.GetAsciiMDString(sysinfo->csd_version_rva).c_str(), + "Linux") && + sysinfo->platform_id != MD_OS_NACL) { + fprintf(stderr, "This minidump was not generated by Linux or NaCl.\n"); + exit(1); + } + + if (options.verbose) { + fprintf(stderr, + "MD_SYSTEM_INFO_STREAM:\n" + "Architecture: %s\n" + "Number of processors: %d\n" + "Processor level: %d\n" + "Processor model: %d\n" + "Processor stepping: %d\n", + sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_X86 + ? "i386" + : sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_AMD64 + ? "x86-64" + : sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_ARM + ? "ARM" + : sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_MIPS + ? "MIPS" + : sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_MIPS64 + ? "MIPS64" + : "???", + sysinfo->number_of_processors, + sysinfo->processor_level, + sysinfo->processor_revision >> 8, + sysinfo->processor_revision & 0xFF); + if (sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_X86 || + sysinfo->processor_architecture == MD_CPU_ARCHITECTURE_AMD64) { + fputs("Vendor id: ", stderr); + const char *nul = + (const char *)memchr(sysinfo->cpu.x86_cpu_info.vendor_id, 0, + sizeof(sysinfo->cpu.x86_cpu_info.vendor_id)); + fwrite(sysinfo->cpu.x86_cpu_info.vendor_id, + nul ? nul - (const char *)&sysinfo->cpu.x86_cpu_info.vendor_id[0] + : sizeof(sysinfo->cpu.x86_cpu_info.vendor_id), 1, stderr); + fputs("\n", stderr); + } + fprintf(stderr, "OS: %s\n", + full_file.GetAsciiMDString(sysinfo->csd_version_rva).c_str()); + fputs("\n\n", stderr); + } +} + +static void +ParseCPUInfo(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range) { + if (options.verbose) { + fputs("MD_LINUX_CPU_INFO:\n", stderr); + fwrite(range.data(), range.length(), 1, stderr); + fputs("\n\n\n", stderr); + } +} + +static void +ParseProcessStatus(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range) { + if (options.verbose) { + fputs("MD_LINUX_PROC_STATUS:\n", stderr); + fwrite(range.data(), range.length(), 1, stderr); + fputs("\n\n", stderr); + } +} + +static void +ParseLSBRelease(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range) { + if (options.verbose) { + fputs("MD_LINUX_LSB_RELEASE:\n", stderr); + fwrite(range.data(), range.length(), 1, stderr); + fputs("\n\n", stderr); + } +} + +static void +ParseMaps(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range) { + if (options.verbose) { + fputs("MD_LINUX_MAPS:\n", stderr); + fwrite(range.data(), range.length(), 1, stderr); + } + for (const uint8_t* ptr = range.data(); + ptr < range.data() + range.length();) { + const uint8_t* eol = (uint8_t*)memchr(ptr, '\n', + range.data() + range.length() - ptr); + string line((const char*)ptr, + eol ? eol - ptr : range.data() + range.length() - ptr); + ptr = eol ? eol + 1 : range.data() + range.length(); + unsigned long long start, stop, offset; + char* permissions = NULL; + char* filename = NULL; + sscanf(line.c_str(), "%llx-%llx %m[-rwxp] %llx %*[:0-9a-f] %*d %ms", + &start, &stop, &permissions, &offset, &filename); + if (filename && *filename == '/') { + CrashedProcess::Mapping mapping; + mapping.permissions = 0; + if (strchr(permissions, 'r')) { + mapping.permissions |= PF_R; + } + if (strchr(permissions, 'w')) { + mapping.permissions |= PF_W; + } + if (strchr(permissions, 'x')) { + mapping.permissions |= PF_X; + } + mapping.start_address = start; + mapping.end_address = stop; + mapping.offset = offset; + if (filename) { + mapping.filename = filename; + } + crashinfo->mappings[mapping.start_address] = mapping; + } + free(permissions); + free(filename); + } + if (options.verbose) { + fputs("\n\n\n", stderr); + } +} + +static void +ParseEnvironment(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range) { + if (options.verbose) { + fputs("MD_LINUX_ENVIRON:\n", stderr); + char* env = new char[range.length()]; + memcpy(env, range.data(), range.length()); + int nul_count = 0; + for (char *ptr = env;;) { + ptr = (char *)memchr(ptr, '\000', range.length() - (ptr - env)); + if (!ptr) { + break; + } + if (ptr > env && ptr[-1] == '\n') { + if (++nul_count > 5) { + // Some versions of Chrome try to rewrite the process' command line + // in a way that causes the environment to be corrupted. Afterwards, + // part of the environment will contain the trailing bit of the + // command line. The rest of the environment will be filled with + // NUL bytes. + // We detect this corruption by counting the number of consecutive + // NUL bytes. Normally, we would not expect any consecutive NUL + // bytes. But we are conservative and only suppress printing of + // the environment if we see at least five consecutive NULs. + fputs("Environment has been corrupted; no data available", stderr); + goto env_corrupted; + } + } else { + nul_count = 0; + } + *ptr = '\n'; + } + fwrite(env, range.length(), 1, stderr); + env_corrupted: + delete[] env; + fputs("\n\n\n", stderr); + } +} + +static void +ParseAuxVector(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range) { + // Some versions of Chrome erroneously used the MD_LINUX_AUXV stream value + // when dumping /proc/$x/maps + if (range.length() > 17) { + // The AUXV vector contains binary data, whereas the maps always begin + // with an 8+ digit hex address followed by a hyphen and another 8+ digit + // address. + char addresses[18]; + memcpy(addresses, range.data(), 17); + addresses[17] = '\000'; + if (strspn(addresses, "0123456789abcdef-") == 17) { + ParseMaps(options, crashinfo, range); + return; + } + } + + crashinfo->auxv = range.data(); + crashinfo->auxv_length = range.length(); +} + +static void +ParseCmdLine(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range) { + // The command line is supposed to use NUL bytes to separate arguments. + // As Chrome rewrites its own command line and (incorrectly) substitutes + // spaces, this is often not the case in our minidump files. + const char* cmdline = (const char*) range.data(); + if (options.verbose) { + fputs("MD_LINUX_CMD_LINE:\n", stderr); + unsigned i = 0; + for (; i < range.length() && cmdline[i] && cmdline[i] != ' '; ++i) { } + fputs("argv[0] = \"", stderr); + fwrite(cmdline, i, 1, stderr); + fputs("\"\n", stderr); + for (unsigned j = ++i, argc = 1; j < range.length(); ++j) { + if (!cmdline[j] || cmdline[j] == ' ') { + fprintf(stderr, "argv[%d] = \"", argc++); + fwrite(cmdline + i, j - i, 1, stderr); + fputs("\"\n", stderr); + i = j + 1; + } + } + fputs("\n\n", stderr); + } + + const char *binary_name = cmdline; + for (size_t i = 0; i < range.length(); ++i) { + if (cmdline[i] == '/') { + binary_name = cmdline + i + 1; + } else if (cmdline[i] == 0 || cmdline[i] == ' ') { + static const size_t fname_len = sizeof(crashinfo->prps.pr_fname) - 1; + static const size_t args_len = sizeof(crashinfo->prps.pr_psargs) - 1; + memset(crashinfo->prps.pr_fname, 0, fname_len + 1); + memset(crashinfo->prps.pr_psargs, 0, args_len + 1); + unsigned len = cmdline + i - binary_name; + memcpy(crashinfo->prps.pr_fname, binary_name, + len > fname_len ? fname_len : len); + + len = range.length() > args_len ? args_len : range.length(); + memcpy(crashinfo->prps.pr_psargs, cmdline, len); + for (unsigned j = 0; j < len; ++j) { + if (crashinfo->prps.pr_psargs[j] == 0) + crashinfo->prps.pr_psargs[j] = ' '; + } + break; + } + } +} + +static void +ParseDSODebugInfo(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range, + const MinidumpMemoryRange& full_file) { + const MDRawDebug* debug = range.GetData<MDRawDebug>(0); + if (!debug) { + return; + } + if (options.verbose) { + fprintf(stderr, + "MD_LINUX_DSO_DEBUG:\n" + "Version: %d\n" + "Number of DSOs: %d\n" + "Brk handler: 0x%" PRIx64 "\n" + "Dynamic loader at: 0x%" PRIx64 "\n" + "_DYNAMIC: 0x%" PRIx64 "\n", + debug->version, + debug->dso_count, + static_cast<uint64_t>(debug->brk), + static_cast<uint64_t>(debug->ldbase), + static_cast<uint64_t>(debug->dynamic)); + } + crashinfo->debug = *debug; + if (range.length() > sizeof(MDRawDebug)) { + char* dynamic_data = (char*)range.data() + sizeof(MDRawDebug); + crashinfo->dynamic_data.assign(dynamic_data, + range.length() - sizeof(MDRawDebug)); + } + if (debug->map != kInvalidMDRVA) { + for (unsigned int i = 0; i < debug->dso_count; ++i) { + const MDRawLinkMap* link_map = + full_file.GetArrayElement<MDRawLinkMap>(debug->map, i); + if (link_map) { + if (options.verbose) { + fprintf(stderr, + "#%03d: %" PRIx64 ", %" PRIx64 ", \"%s\"\n", + i, static_cast<uint64_t>(link_map->addr), + static_cast<uint64_t>(link_map->ld), + full_file.GetAsciiMDString(link_map->name).c_str()); + } + crashinfo->link_map.push_back(*link_map); + } + } + } + if (options.verbose) { + fputs("\n\n", stderr); + } +} + +static void +ParseExceptionStream(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range) { + const MDRawExceptionStream* exp = range.GetData<MDRawExceptionStream>(0); + crashinfo->crashing_tid = exp->thread_id; + crashinfo->fatal_signal = (int) exp->exception_record.exception_code; +} + +static bool +WriteThread(const Options& options, const CrashedProcess::Thread& thread, + int fatal_signal) { + struct prstatus pr; + memset(&pr, 0, sizeof(pr)); + + pr.pr_info.si_signo = fatal_signal; + pr.pr_cursig = fatal_signal; + pr.pr_pid = thread.tid; +#if defined(__mips__) + memcpy(&pr.pr_reg, &thread.mcontext.gregs, sizeof(user_regs_struct)); +#else + memcpy(&pr.pr_reg, &thread.regs, sizeof(user_regs_struct)); +#endif + + Nhdr nhdr; + memset(&nhdr, 0, sizeof(nhdr)); + nhdr.n_namesz = 5; + nhdr.n_descsz = sizeof(struct prstatus); + nhdr.n_type = NT_PRSTATUS; + if (!writea(options.out_fd, &nhdr, sizeof(nhdr)) || + !writea(options.out_fd, "CORE\0\0\0\0", 8) || + !writea(options.out_fd, &pr, sizeof(struct prstatus))) { + return false; + } + +#if defined(__i386__) || defined(__x86_64__) + nhdr.n_descsz = sizeof(user_fpregs_struct); + nhdr.n_type = NT_FPREGSET; + if (!writea(options.out_fd, &nhdr, sizeof(nhdr)) || + !writea(options.out_fd, "CORE\0\0\0\0", 8) || + !writea(options.out_fd, &thread.fpregs, sizeof(user_fpregs_struct))) { + return false; + } +#endif + +#if defined(__i386__) + nhdr.n_descsz = sizeof(user_fpxregs_struct); + nhdr.n_type = NT_PRXFPREG; + if (!writea(options.out_fd, &nhdr, sizeof(nhdr)) || + !writea(options.out_fd, "LINUX\0\0\0", 8) || + !writea(options.out_fd, &thread.fpxregs, sizeof(user_fpxregs_struct))) { + return false; + } +#endif + + return true; +} + +static void +ParseModuleStream(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& range, + const MinidumpMemoryRange& full_file) { + if (options.verbose) { + fputs("MD_MODULE_LIST_STREAM:\n", stderr); + } + const uint32_t num_mappings = *range.GetData<uint32_t>(0); + for (unsigned i = 0; i < num_mappings; ++i) { + CrashedProcess::Mapping mapping; + const MDRawModule* rawmodule = reinterpret_cast<const MDRawModule*>( + range.GetArrayElement(sizeof(uint32_t), MD_MODULE_SIZE, i)); + mapping.start_address = rawmodule->base_of_image; + mapping.end_address = rawmodule->size_of_image + rawmodule->base_of_image; + + if (crashinfo->mappings.find(mapping.start_address) == + crashinfo->mappings.end()) { + // We prefer data from MD_LINUX_MAPS over MD_MODULE_LIST_STREAM, as + // the former is a strict superset of the latter. + crashinfo->mappings[mapping.start_address] = mapping; + } + + const MDCVInfoPDB70* record = reinterpret_cast<const MDCVInfoPDB70*>( + full_file.GetData(rawmodule->cv_record.rva, MDCVInfoPDB70_minsize)); + char guid[40]; + sprintf(guid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + record->signature.data1, record->signature.data2, + record->signature.data3, + record->signature.data4[0], record->signature.data4[1], + record->signature.data4[2], record->signature.data4[3], + record->signature.data4[4], record->signature.data4[5], + record->signature.data4[6], record->signature.data4[7]); + + string filename = full_file.GetAsciiMDString(rawmodule->module_name_rva); + + CrashedProcess::Signature signature; + strcpy(signature.guid, guid); + signature.filename = filename; + crashinfo->signatures[rawmodule->base_of_image] = signature; + + if (options.verbose) { + fprintf(stderr, "0x%" PRIx64 "-0x%" PRIx64 ", ChkSum: 0x%08X, GUID: %s, " + " \"%s\"\n", + rawmodule->base_of_image, + rawmodule->base_of_image + rawmodule->size_of_image, + rawmodule->checksum, guid, filename.c_str()); + } + } + if (options.verbose) { + fputs("\n\n", stderr); + } +} + +static void +AddDataToMapping(CrashedProcess* crashinfo, const string& data, + uintptr_t addr) { + for (std::map<uint64_t, CrashedProcess::Mapping>::iterator + iter = crashinfo->mappings.begin(); + iter != crashinfo->mappings.end(); + ++iter) { + if (addr >= iter->second.start_address && + addr < iter->second.end_address) { + CrashedProcess::Mapping mapping = iter->second; + if ((addr & ~4095) != iter->second.start_address) { + // If there are memory pages in the mapping prior to where the + // data starts, truncate the existing mapping so that it ends with + // the page immediately preceding the data region. + iter->second.end_address = addr & ~4095; + if (!mapping.filename.empty()) { + // "mapping" is a copy of "iter->second". We are splitting the + // existing mapping into two separate ones when we write the data + // to the core file. The first one does not have any associated + // data in the core file, the second one is backed by data that is + // included with the core file. + // If this mapping wasn't supposed to be anonymous, then we also + // have to update the file offset upon splitting the mapping. + mapping.offset += iter->second.end_address - + iter->second.start_address; + } + } + // Create a new mapping that contains the data contents. We often + // limit the amount of data that is actually written to the core + // file. But it is OK if the mapping itself extends past the end of + // the data. + mapping.start_address = addr & ~4095; + mapping.data.assign(addr & 4095, 0).append(data); + mapping.data.append(-mapping.data.size() & 4095, 0); + crashinfo->mappings[mapping.start_address] = mapping; + return; + } + } + // Didn't find a suitable existing mapping for the data. Create a new one. + CrashedProcess::Mapping mapping; + mapping.permissions = PF_R | PF_W; + mapping.start_address = addr & ~4095; + mapping.end_address = + (addr + data.size() + 4095) & ~4095; + mapping.data.assign(addr & 4095, 0).append(data); + mapping.data.append(-mapping.data.size() & 4095, 0); + crashinfo->mappings[mapping.start_address] = mapping; +} + +static void +AugmentMappings(const Options& options, CrashedProcess* crashinfo, + const MinidumpMemoryRange& full_file) { + // For each thread, find the memory mapping that matches the thread's stack. + // Then adjust the mapping to include the stack dump. + for (unsigned i = 0; i < crashinfo->threads.size(); ++i) { + const CrashedProcess::Thread& thread = crashinfo->threads[i]; + AddDataToMapping(crashinfo, + string((char *)thread.stack, thread.stack_length), + thread.stack_addr); + } + + // Create a new link map with information about DSOs. We move this map to + // the beginning of the address space, as this area should always be + // available. + static const uintptr_t start_addr = 4096; + string data; + struct r_debug debug = { 0 }; + debug.r_version = crashinfo->debug.version; + debug.r_brk = (ElfW(Addr))crashinfo->debug.brk; + debug.r_state = r_debug::RT_CONSISTENT; + debug.r_ldbase = (ElfW(Addr))crashinfo->debug.ldbase; + debug.r_map = crashinfo->debug.dso_count > 0 ? + (struct link_map*)(start_addr + sizeof(debug)) : 0; + data.append((char*)&debug, sizeof(debug)); + + struct link_map* prev = 0; + for (std::vector<MDRawLinkMap>::iterator iter = crashinfo->link_map.begin(); + iter != crashinfo->link_map.end(); + ++iter) { + struct link_map link_map = { 0 }; + link_map.l_addr = (ElfW(Addr))iter->addr; + link_map.l_name = (char*)(start_addr + data.size() + sizeof(link_map)); + link_map.l_ld = (ElfW(Dyn)*)iter->ld; + link_map.l_prev = prev; + prev = (struct link_map*)(start_addr + data.size()); + string filename = full_file.GetAsciiMDString(iter->name); + + // Look up signature for this filename. If available, change filename + // to point to GUID, instead. + std::map<uintptr_t, CrashedProcess::Signature>::const_iterator sig = + crashinfo->signatures.find((uintptr_t)iter->addr); + if (sig != crashinfo->signatures.end()) { + // At this point, we have: + // old_filename: The path as found via SONAME (e.g. /lib/libpthread.so.0). + // sig_filename: The path on disk (e.g. /lib/libpthread-2.19.so). + const char* guid = sig->second.guid; + string sig_filename = sig->second.filename; + string old_filename = filename.empty() ? sig_filename : filename; + string new_filename; + + // First set up the leading path. We assume dirname always ends with a + // trailing slash (as needed), so we won't be appending one manually. + if (options.so_basedir.empty()) { + string dirname; + if (options.use_filename) { + dirname = sig_filename; + } else { + dirname = old_filename; + } + size_t slash = dirname.find_last_of('/'); + if (slash != string::npos) { + new_filename = dirname.substr(0, slash + 1); + } + } else { + new_filename = options.so_basedir; + } + + // Insert the module ID if requested. + if (options.inc_guid && + strcmp(guid, "00000000-0000-0000-0000-000000000000") != 0) { + new_filename += guid; + new_filename += "-"; + } + + // Decide whether we use the filename or the SONAME (where the SONAME tends + // to be a symlink to the actual file). + new_filename += google_breakpad::BaseName( + options.use_filename ? sig_filename : old_filename); + + if (filename != new_filename) { + if (options.verbose) { + fprintf(stderr, "0x%" PRIx64": rewriting mapping \"%s\" to \"%s\"\n", + static_cast<uint64_t>(link_map.l_addr), + filename.c_str(), new_filename.c_str()); + } + filename = new_filename; + } + } + + if (std::distance(iter, crashinfo->link_map.end()) == 1) { + link_map.l_next = 0; + } else { + link_map.l_next = (struct link_map*)(start_addr + data.size() + + sizeof(link_map) + + ((filename.size() + 8) & ~7)); + } + data.append((char*)&link_map, sizeof(link_map)); + data.append(filename); + data.append(8 - (filename.size() & 7), 0); + } + AddDataToMapping(crashinfo, data, start_addr); + + // Map the page containing the _DYNAMIC array + if (!crashinfo->dynamic_data.empty()) { + // Make _DYNAMIC DT_DEBUG entry point to our link map + for (int i = 0;; ++i) { + ElfW(Dyn) dyn; + if ((i+1)*sizeof(dyn) > crashinfo->dynamic_data.length()) { + no_dt_debug: + if (options.verbose) { + fprintf(stderr, "No DT_DEBUG entry found\n"); + } + return; + } + memcpy(&dyn, crashinfo->dynamic_data.c_str() + i*sizeof(dyn), + sizeof(dyn)); + if (dyn.d_tag == DT_DEBUG) { + crashinfo->dynamic_data.replace(i*sizeof(dyn) + + offsetof(ElfW(Dyn), d_un.d_ptr), + sizeof(start_addr), + (char*)&start_addr, sizeof(start_addr)); + break; + } else if (dyn.d_tag == DT_NULL) { + goto no_dt_debug; + } + } + AddDataToMapping(crashinfo, crashinfo->dynamic_data, + (uintptr_t)crashinfo->debug.dynamic); + } +} + +int +main(int argc, const char* argv[]) { + Options options; + SetupOptions(argc, argv, &options); + + MemoryMappedFile mapped_file(options.minidump_path.c_str(), 0); + if (!mapped_file.data()) { + fprintf(stderr, "Failed to mmap dump file: %s: %s\n", + options.minidump_path.c_str(), strerror(errno)); + return 1; + } + + MinidumpMemoryRange dump(mapped_file.data(), mapped_file.size()); + + const MDRawHeader* header = dump.GetData<MDRawHeader>(0); + + CrashedProcess crashinfo; + + // Always check the system info first, as that allows us to tell whether + // this is a minidump file that is compatible with our converter. + bool ok = false; + for (unsigned i = 0; i < header->stream_count; ++i) { + const MDRawDirectory* dirent = + dump.GetArrayElement<MDRawDirectory>(header->stream_directory_rva, i); + switch (dirent->stream_type) { + case MD_SYSTEM_INFO_STREAM: + ParseSystemInfo(options, &crashinfo, dump.Subrange(dirent->location), + dump); + ok = true; + break; + default: + break; + } + } + if (!ok) { + fprintf(stderr, "Cannot determine input file format.\n"); + exit(1); + } + + for (unsigned i = 0; i < header->stream_count; ++i) { + const MDRawDirectory* dirent = + dump.GetArrayElement<MDRawDirectory>(header->stream_directory_rva, i); + switch (dirent->stream_type) { + case MD_THREAD_LIST_STREAM: + ParseThreadList(options, &crashinfo, dump.Subrange(dirent->location), + dump); + break; + case MD_LINUX_CPU_INFO: + ParseCPUInfo(options, &crashinfo, dump.Subrange(dirent->location)); + break; + case MD_LINUX_PROC_STATUS: + ParseProcessStatus(options, &crashinfo, + dump.Subrange(dirent->location)); + break; + case MD_LINUX_LSB_RELEASE: + ParseLSBRelease(options, &crashinfo, dump.Subrange(dirent->location)); + break; + case MD_LINUX_ENVIRON: + ParseEnvironment(options, &crashinfo, dump.Subrange(dirent->location)); + break; + case MD_LINUX_MAPS: + ParseMaps(options, &crashinfo, dump.Subrange(dirent->location)); + break; + case MD_LINUX_AUXV: + ParseAuxVector(options, &crashinfo, dump.Subrange(dirent->location)); + break; + case MD_LINUX_CMD_LINE: + ParseCmdLine(options, &crashinfo, dump.Subrange(dirent->location)); + break; + case MD_LINUX_DSO_DEBUG: + ParseDSODebugInfo(options, &crashinfo, dump.Subrange(dirent->location), + dump); + break; + case MD_EXCEPTION_STREAM: + ParseExceptionStream(options, &crashinfo, + dump.Subrange(dirent->location)); + break; + case MD_MODULE_LIST_STREAM: + ParseModuleStream(options, &crashinfo, dump.Subrange(dirent->location), + dump); + break; + default: + if (options.verbose) + fprintf(stderr, "Skipping %x\n", dirent->stream_type); + } + } + + AugmentMappings(options, &crashinfo, dump); + + // Write the ELF header. The file will look like: + // ELF header + // Phdr for the PT_NOTE + // Phdr for each of the thread stacks + // PT_NOTE + // each of the thread stacks + Ehdr ehdr; + memset(&ehdr, 0, sizeof(Ehdr)); + ehdr.e_ident[0] = ELFMAG0; + ehdr.e_ident[1] = ELFMAG1; + ehdr.e_ident[2] = ELFMAG2; + ehdr.e_ident[3] = ELFMAG3; + ehdr.e_ident[4] = ELF_CLASS; + ehdr.e_ident[5] = sex() ? ELFDATA2MSB : ELFDATA2LSB; + ehdr.e_ident[6] = EV_CURRENT; + ehdr.e_type = ET_CORE; + ehdr.e_machine = ELF_ARCH; + ehdr.e_version = EV_CURRENT; + ehdr.e_phoff = sizeof(Ehdr); + ehdr.e_ehsize = sizeof(Ehdr); + ehdr.e_phentsize= sizeof(Phdr); + ehdr.e_phnum = 1 + // PT_NOTE + crashinfo.mappings.size(); // memory mappings + ehdr.e_shentsize= sizeof(Shdr); + if (!writea(options.out_fd, &ehdr, sizeof(Ehdr))) + return 1; + + size_t offset = sizeof(Ehdr) + ehdr.e_phnum * sizeof(Phdr); + size_t filesz = sizeof(Nhdr) + 8 + sizeof(prpsinfo) + + // sizeof(Nhdr) + 8 + sizeof(user) + + sizeof(Nhdr) + 8 + crashinfo.auxv_length + + crashinfo.threads.size() * ( + (sizeof(Nhdr) + 8 + sizeof(prstatus)) +#if defined(__i386__) || defined(__x86_64__) + + sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct) +#endif +#if defined(__i386__) + + sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct) +#endif + ); + + Phdr phdr; + memset(&phdr, 0, sizeof(Phdr)); + phdr.p_type = PT_NOTE; + phdr.p_offset = offset; + phdr.p_filesz = filesz; + if (!writea(options.out_fd, &phdr, sizeof(phdr))) + return 1; + + phdr.p_type = PT_LOAD; + phdr.p_align = 4096; + size_t note_align = phdr.p_align - ((offset+filesz) % phdr.p_align); + if (note_align == phdr.p_align) + note_align = 0; + offset += note_align; + + for (std::map<uint64_t, CrashedProcess::Mapping>::const_iterator iter = + crashinfo.mappings.begin(); + iter != crashinfo.mappings.end(); ++iter) { + const CrashedProcess::Mapping& mapping = iter->second; + if (mapping.permissions == 0xFFFFFFFF) { + // This is a map that we found in MD_MODULE_LIST_STREAM (as opposed to + // MD_LINUX_MAPS). It lacks some of the information that we would like + // to include. + phdr.p_flags = PF_R; + } else { + phdr.p_flags = mapping.permissions; + } + phdr.p_vaddr = mapping.start_address; + phdr.p_memsz = mapping.end_address - mapping.start_address; + if (mapping.data.size()) { + offset += filesz; + filesz = mapping.data.size(); + phdr.p_filesz = mapping.data.size(); + phdr.p_offset = offset; + } else { + phdr.p_filesz = 0; + phdr.p_offset = 0; + } + if (!writea(options.out_fd, &phdr, sizeof(phdr))) + return 1; + } + + Nhdr nhdr; + memset(&nhdr, 0, sizeof(nhdr)); + nhdr.n_namesz = 5; + nhdr.n_descsz = sizeof(prpsinfo); + nhdr.n_type = NT_PRPSINFO; + if (!writea(options.out_fd, &nhdr, sizeof(nhdr)) || + !writea(options.out_fd, "CORE\0\0\0\0", 8) || + !writea(options.out_fd, &crashinfo.prps, sizeof(prpsinfo))) { + return 1; + } + + nhdr.n_descsz = crashinfo.auxv_length; + nhdr.n_type = NT_AUXV; + if (!writea(options.out_fd, &nhdr, sizeof(nhdr)) || + !writea(options.out_fd, "CORE\0\0\0\0", 8) || + !writea(options.out_fd, crashinfo.auxv, crashinfo.auxv_length)) { + return 1; + } + + for (unsigned i = 0; i < crashinfo.threads.size(); ++i) { + if (crashinfo.threads[i].tid == crashinfo.crashing_tid) { + WriteThread(options, crashinfo.threads[i], crashinfo.fatal_signal); + break; + } + } + + for (unsigned i = 0; i < crashinfo.threads.size(); ++i) { + if (crashinfo.threads[i].tid != crashinfo.crashing_tid) + WriteThread(options, crashinfo.threads[i], 0); + } + + if (note_align) { + google_breakpad::scoped_array<char> scratch(new char[note_align]); + memset(scratch.get(), 0, note_align); + if (!writea(options.out_fd, scratch.get(), note_align)) + return 1; + } + + for (std::map<uint64_t, CrashedProcess::Mapping>::const_iterator iter = + crashinfo.mappings.begin(); + iter != crashinfo.mappings.end(); ++iter) { + const CrashedProcess::Mapping& mapping = iter->second; + if (mapping.data.size()) { + if (!writea(options.out_fd, mapping.data.c_str(), mapping.data.size())) + return 1; + } + } + + if (options.out_fd != STDOUT_FILENO) { + close(options.out_fd); + } + + return 0; +} diff --git a/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump_memory_range.h b/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump_memory_range.h new file mode 100644 index 0000000000..a793e2cfb9 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump_memory_range.h @@ -0,0 +1,89 @@ +// 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_memory_range.h: Define the google_breakpad::MinidumpMemoryRange +// class, which adds methods for handling minidump specific data structures +// on top of google_breakpad::MemoryRange. See common/memory_range.h for +// more details on MemoryRange. + +#ifndef TOOLS_LINUX_MD2CORE_MINIDUMP_MEMORY_RANGE_H_ +#define TOOLS_LINUX_MD2CORE_MINIDUMP_MEMORY_RANGE_H_ + +#include <string> + +#include "common/memory_range.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// A derived class of MemoryRange with added methods for handling minidump +// specific data structures. To avoid virtual functions, it is not designed +// to be used polymorphically. +class MinidumpMemoryRange : public MemoryRange { + public: + MinidumpMemoryRange() {} + + MinidumpMemoryRange(const void* data, size_t length) + : MemoryRange(data, length) {} + + // Returns a subrange of |length| bytes at |offset| bytes of this memory + // range, or an empty range if the subrange is out of bounds. + // This methods overrides the base implemementation in order to return + // an instance of MinidumpMemoryRange instead of MemoryRange. + MinidumpMemoryRange Subrange(size_t sub_offset, size_t sub_length) const { + if (Covers(sub_offset, sub_length)) + return MinidumpMemoryRange(data() + sub_offset, sub_length); + return MinidumpMemoryRange(); + } + + // Returns a subrange that covers the offset and length specified by + // |location|, or an empty range if the subrange is out of bounds. + MinidumpMemoryRange Subrange(const MDLocationDescriptor& location) const { + return MinidumpMemoryRange::Subrange(location.rva, location.data_size); + } + + // Gets a STL string from a MDString at |sub_offset| bytes of this memory + // range. This method only works correctly for ASCII characters and does + // not convert between UTF-16 and UTF-8. + const std::string GetAsciiMDString(size_t sub_offset) const { + std::string str; + const MDString* md_str = GetData<MDString>(sub_offset); + if (md_str) { + const uint16_t* buffer = &md_str->buffer[0]; + for (uint32_t i = 0; i < md_str->length && buffer[i]; ++i) { + str.push_back(buffer[i]); + } + } + return str; + } +}; + +} // namespace google_breakpad + +#endif // TOOLS_LINUX_MD2CORE_MINIDUMP_MEMORY_RANGE_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump_memory_range_unittest.cc b/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump_memory_range_unittest.cc new file mode 100644 index 0000000000..fe4ded83dc --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/tools/linux/md2core/minidump_memory_range_unittest.cc @@ -0,0 +1,258 @@ +// 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_memory_range_unittest.cc: +// Unit tests for google_breakpad::MinidumpMemoryRange. + +#include "breakpad_googletest_includes.h" +#include "tools/linux/md2core/minidump_memory_range.h" + +using google_breakpad::MinidumpMemoryRange; +using testing::Message; + +namespace { + +const uint32_t kBuffer[10] = { 0 }; +const size_t kBufferSize = sizeof(kBuffer); +const uint8_t* kBufferPointer = reinterpret_cast<const uint8_t*>(kBuffer); + +// Test vectors for verifying Covers, GetData, and Subrange. +const struct { + bool valid; + size_t offset; + size_t length; +} kSubranges[] = { + { true, 0, 0 }, + { true, 0, 2 }, + { true, 0, kBufferSize }, + { true, 2, 0 }, + { true, 2, 4 }, + { true, 2, kBufferSize - 2 }, + { true, kBufferSize - 1, 1 }, + { false, kBufferSize, 0 }, + { false, kBufferSize, static_cast<size_t>(-1) }, + { false, kBufferSize + 1, 0 }, + { false, static_cast<size_t>(-1), 2 }, + { false, 1, kBufferSize }, + { false, kBufferSize - 1, 2 }, + { false, 0, static_cast<size_t>(-1) }, + { false, 1, static_cast<size_t>(-1) }, +}; +const size_t kNumSubranges = sizeof(kSubranges) / sizeof(kSubranges[0]); + +// Test vectors for verifying GetArrayElement. +const struct { + size_t offset; + size_t size; + size_t index; + const void* const pointer; +} kElements[] = { + // Valid array elemenets + { 0, 1, 0, kBufferPointer }, + { 0, 1, 1, kBufferPointer + 1 }, + { 0, 1, kBufferSize - 1, kBufferPointer + kBufferSize - 1 }, + { 0, 2, 1, kBufferPointer + 2 }, + { 0, 4, 2, kBufferPointer + 8 }, + { 0, 4, 9, kBufferPointer + 36 }, + { kBufferSize - 1, 1, 0, kBufferPointer + kBufferSize - 1 }, + // Invalid array elemenets + { 0, 1, kBufferSize, NULL }, + { 0, 4, 10, NULL }, + { kBufferSize - 1, 1, 1, NULL }, + { kBufferSize - 1, 2, 0, NULL }, + { kBufferSize, 1, 0, NULL }, +}; +const size_t kNumElements = sizeof(kElements) / sizeof(kElements[0]); + +} // namespace + +TEST(MinidumpMemoryRangeTest, DefaultConstructor) { + MinidumpMemoryRange range; + EXPECT_EQ(NULL, range.data()); + EXPECT_EQ(0U, range.length()); +} + +TEST(MinidumpMemoryRangeTest, ConstructorWithDataAndLength) { + MinidumpMemoryRange range(kBuffer, kBufferSize); + EXPECT_EQ(kBufferPointer, range.data()); + EXPECT_EQ(kBufferSize, range.length()); +} + +TEST(MinidumpMemoryRangeTest, Reset) { + MinidumpMemoryRange range; + range.Reset(); + EXPECT_EQ(NULL, range.data()); + EXPECT_EQ(0U, range.length()); + + range.Set(kBuffer, kBufferSize); + EXPECT_EQ(kBufferPointer, range.data()); + EXPECT_EQ(kBufferSize, range.length()); + + range.Reset(); + EXPECT_EQ(NULL, range.data()); + EXPECT_EQ(0U, range.length()); +} + +TEST(MinidumpMemoryRangeTest, Set) { + MinidumpMemoryRange range; + range.Set(kBuffer, kBufferSize); + EXPECT_EQ(kBufferPointer, range.data()); + EXPECT_EQ(kBufferSize, range.length()); + + range.Set(NULL, 0); + EXPECT_EQ(NULL, range.data()); + EXPECT_EQ(0U, range.length()); +} + +TEST(MinidumpMemoryRangeTest, SubrangeOfEmptyMemoryRange) { + MinidumpMemoryRange range; + MinidumpMemoryRange subrange = range.Subrange(0, 10); + EXPECT_EQ(NULL, subrange.data()); + EXPECT_EQ(0U, subrange.length()); +} + +TEST(MinidumpMemoryRangeTest, SubrangeAndGetData) { + MinidumpMemoryRange range(kBuffer, kBufferSize); + for (size_t i = 0; i < kNumSubranges; ++i) { + bool valid = kSubranges[i].valid; + size_t sub_offset = kSubranges[i].offset; + size_t sub_length = kSubranges[i].length; + SCOPED_TRACE(Message() << "offset=" << sub_offset + << ", length=" << sub_length); + + MinidumpMemoryRange subrange = range.Subrange(sub_offset, sub_length); + if (valid) { + EXPECT_TRUE(range.Covers(sub_offset, sub_length)); + EXPECT_EQ(kBufferPointer + sub_offset, + range.GetData(sub_offset, sub_length)); + EXPECT_EQ(kBufferPointer + sub_offset, subrange.data()); + EXPECT_EQ(sub_length, subrange.length()); + } else { + EXPECT_FALSE(range.Covers(sub_offset, sub_length)); + EXPECT_EQ(NULL, range.GetData(sub_offset, sub_length)); + EXPECT_EQ(NULL, subrange.data()); + EXPECT_EQ(0U, subrange.length()); + } + } +} + +TEST(MinidumpMemoryRangeTest, SubrangeWithMDLocationDescriptor) { + MinidumpMemoryRange range(kBuffer, kBufferSize); + for (size_t i = 0; i < kNumSubranges; ++i) { + bool valid = kSubranges[i].valid; + size_t sub_offset = kSubranges[i].offset; + size_t sub_length = kSubranges[i].length; + SCOPED_TRACE(Message() << "offset=" << sub_offset + << ", length=" << sub_length); + + MDLocationDescriptor location; + location.rva = sub_offset; + location.data_size = sub_length; + MinidumpMemoryRange subrange = range.Subrange(location); + if (valid) { + EXPECT_TRUE(range.Covers(sub_offset, sub_length)); + EXPECT_EQ(kBufferPointer + sub_offset, + range.GetData(sub_offset, sub_length)); + EXPECT_EQ(kBufferPointer + sub_offset, subrange.data()); + EXPECT_EQ(sub_length, subrange.length()); + } else { + EXPECT_FALSE(range.Covers(sub_offset, sub_length)); + EXPECT_EQ(NULL, range.GetData(sub_offset, sub_length)); + EXPECT_EQ(NULL, subrange.data()); + EXPECT_EQ(0U, subrange.length()); + } + } +} + +TEST(MinidumpMemoryRangeTest, GetDataWithTemplateType) { + MinidumpMemoryRange range(kBuffer, kBufferSize); + const char* char_pointer = range.GetData<char>(0); + EXPECT_EQ(reinterpret_cast<const char*>(kBufferPointer), char_pointer); + const int* int_pointer = range.GetData<int>(0); + EXPECT_EQ(reinterpret_cast<const int*>(kBufferPointer), int_pointer); +} + +TEST(MinidumpMemoryRangeTest, GetArrayElement) { + MinidumpMemoryRange range(kBuffer, kBufferSize); + for (size_t i = 0; i < kNumElements; ++i) { + size_t element_offset = kElements[i].offset; + size_t element_size = kElements[i].size; + unsigned element_index = kElements[i].index; + const void* const element_pointer = kElements[i].pointer; + SCOPED_TRACE(Message() << "offset=" << element_offset + << ", size=" << element_size + << ", index=" << element_index); + EXPECT_EQ(element_pointer, range.GetArrayElement( + element_offset, element_size, element_index)); + } +} + +TEST(MinidumpMemoryRangeTest, GetArrayElmentWithTemplateType) { + MinidumpMemoryRange range(kBuffer, kBufferSize); + const char* char_pointer = range.GetArrayElement<char>(0, 0); + EXPECT_EQ(reinterpret_cast<const char*>(kBufferPointer), char_pointer); + const int* int_pointer = range.GetArrayElement<int>(0, 0); + EXPECT_EQ(reinterpret_cast<const int*>(kBufferPointer), int_pointer); +} + +TEST(MinidumpMemoryRangeTest, GetAsciiMDString) { + uint8_t buffer[100] = { 0 }; + + MDString* md_str = reinterpret_cast<MDString*>(buffer); + md_str->length = 4; + md_str->buffer[0] = 'T'; + md_str->buffer[1] = 'e'; + md_str->buffer[2] = 's'; + md_str->buffer[3] = 't'; + md_str->buffer[4] = '\0'; + + size_t str2_offset = + sizeof(MDString) + (md_str->length + 1) * sizeof(uint16_t); + + md_str = reinterpret_cast<MDString*>(buffer + str2_offset); + md_str->length = 9; // Test length larger than actual string + md_str->buffer[0] = 'S'; + md_str->buffer[1] = 't'; + md_str->buffer[2] = 'r'; + md_str->buffer[3] = 'i'; + md_str->buffer[4] = 'n'; + md_str->buffer[5] = 'g'; + md_str->buffer[6] = '\0'; + md_str->buffer[7] = '1'; + md_str->buffer[8] = '2'; + + MinidumpMemoryRange range(buffer, sizeof(buffer)); + EXPECT_EQ("Test", range.GetAsciiMDString(0)); + EXPECT_EQ("String", range.GetAsciiMDString(str2_offset)); + + // Test out-of-bounds cases. + EXPECT_EQ("", range.GetAsciiMDString( + sizeof(buffer) - sizeof(MDString) + 1)); + EXPECT_EQ("", range.GetAsciiMDString(sizeof(buffer))); +} diff --git a/toolkit/crashreporter/google-breakpad/src/tools/linux/symupload/minidump_upload.cc b/toolkit/crashreporter/google-breakpad/src/tools/linux/symupload/minidump_upload.cc new file mode 100644 index 0000000000..19f17450a1 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/tools/linux/symupload/minidump_upload.cc @@ -0,0 +1,153 @@ +// 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. + +// minidump_upload.cc: Upload a minidump to a HTTP server. +// The upload is sent as a multipart/form-data POST request with +// the following parameters: +// prod: the product name +// ver: the product version +// symbol_file: the breakpad format symbol file + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <string> + +#include "common/linux/http_upload.h" +#include "common/using_std_string.h" + +using google_breakpad::HTTPUpload; + +struct Options { + string minidumpPath; + string uploadURLStr; + string product; + string version; + string proxy; + string proxy_user_pwd; + bool success; +}; + +//============================================================================= +static void Start(Options *options) { + std::map<string, string> parameters; + // Add parameters + parameters["prod"] = options->product; + parameters["ver"] = options->version; + + std::map<string, string> files; + files["upload_file_minidump"] = options->minidumpPath; + + // Send it + string response, error; + bool success = HTTPUpload::SendRequest(options->uploadURLStr, + parameters, + files, + options->proxy, + options->proxy_user_pwd, + "", + &response, + NULL, + &error); + + if (success) { + printf("Successfully sent the minidump file.\n"); + } else { + printf("Failed to send minidump: %s\n", error.c_str()); + } + printf("Response:\n"); + printf("%s\n", response.c_str()); + options->success = success; +} + +//============================================================================= +static void +Usage(int argc, const char *argv[]) { + fprintf(stderr, "Submit minidump information.\n"); + fprintf(stderr, "Usage: %s [options...] -p <product> -v <version> <minidump> " + "<upload-URL>\n", argv[0]); + fprintf(stderr, "Options:\n"); + fprintf(stderr, "<minidump> should be a minidump.\n"); + fprintf(stderr, "<upload-URL> is the destination for the upload\n"); + + fprintf(stderr, "-p:\t <product> Product name\n"); + fprintf(stderr, "-v:\t <version> Product version\n"); + fprintf(stderr, "-x:\t <host[:port]> Use HTTP proxy on given port\n"); + fprintf(stderr, "-u:\t <user[:password]> Set proxy user and password\n"); + fprintf(stderr, "-h:\t Usage\n"); + fprintf(stderr, "-?:\t Usage\n"); +} + +//============================================================================= +static void +SetupOptions(int argc, const char *argv[], Options *options) { + extern int optind; + int ch; + + while ((ch = getopt(argc, (char * const *)argv, "p:u:v:x:h?")) != -1) { + switch (ch) { + case 'p': + options->product = optarg; + break; + case 'u': + options->proxy_user_pwd = optarg; + break; + case 'v': + options->version = optarg; + break; + case 'x': + options->proxy = optarg; + break; + + default: + fprintf(stderr, "Invalid option '%c'\n", ch); + Usage(argc, argv); + exit(1); + break; + } + } + + if ((argc - optind) != 2) { + fprintf(stderr, "%s: Missing symbols file and/or upload-URL\n", argv[0]); + Usage(argc, argv); + exit(1); + } + + options->minidumpPath = argv[optind]; + options->uploadURLStr = argv[optind + 1]; +} + +//============================================================================= +int main(int argc, const char* argv[]) { + Options options; + SetupOptions(argc, argv, &options); + Start(&options); + return options.success ? 0 : 1; +} diff --git a/toolkit/crashreporter/google-breakpad/src/tools/linux/symupload/sym_upload.cc b/toolkit/crashreporter/google-breakpad/src/tools/linux/symupload/sym_upload.cc new file mode 100644 index 0000000000..f155eb9552 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/tools/linux/symupload/sym_upload.cc @@ -0,0 +1,210 @@ +// 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. + +// symupload.cc: Upload a symbol file to a HTTP server. The upload is sent as +// a multipart/form-data POST request with the following parameters: +// code_file: the basename of the module, e.g. "app" +// debug_file: the basename of the debugging file, e.g. "app" +// debug_identifier: the debug file's identifier, usually consisting of +// the guid and age embedded in the pdb, e.g. +// "11111111BBBB3333DDDD555555555555F" +// version: the file version of the module, e.g. "1.2.3.4" +// os: the operating system that the module was built for +// cpu: the CPU that the module was built for +// symbol_file: the contents of the breakpad-format symbol file + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <locale> + +#include "common/linux/symbol_upload.h" + +using google_breakpad::sym_upload::UploadProtocol; +using google_breakpad::sym_upload::Options; + +static void StrToUpper(std::string* str) { + if (str == nullptr) { + fprintf(stderr, "nullptr passed to StrToUpper.\n"); + exit(1); + } + for (size_t i = 0; i < str->length(); i++) { + (*str)[i] = std::toupper((*str)[i], std::locale::classic()); + } +} + +//============================================================================= +static void +Usage(int argc, const char *argv[]) { + fprintf(stderr, "Submit symbol information.\n"); + fprintf(stderr, "Usage: %s [options...] <symbol-file> <upload-URL>\n", + argv[0]); + fprintf(stderr, "Options:\n"); + fprintf(stderr, "<symbol-file> should be created by using the dump_syms" + "tool.\n"); + fprintf(stderr, "<upload-URL> is the destination for the upload\n"); + fprintf(stderr, "-p:\t <protocol> One of ['sym-upload-v1'," + " 'sym-upload-v2'], defaults to 'sym-upload-v1'.\n"); + fprintf(stderr, "-v:\t Version information (e.g., 1.2.3.4)\n"); + fprintf(stderr, "-x:\t <host[:port]> Use HTTP proxy on given port\n"); + fprintf(stderr, "-u:\t <user[:password]> Set proxy user and password\n"); + fprintf(stderr, "-h:\t Usage\n"); + fprintf(stderr, "-?:\t Usage\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "These options only work with 'sym-upload-v2' protocol:\n"); + fprintf(stderr, "-k:\t <API-key> A secret used to authenticate with the" + " API.\n"); + fprintf(stderr, "-f:\t Force symbol upload if already exists.\n"); + fprintf(stderr, "-t:\t <symbol-type> Explicitly set symbol upload type (" + "default is 'breakpad').\n" + "\t One of ['breakpad', 'elf', 'pe', 'macho', 'debug_only', 'dwp', " + "'dsym', 'pdb'].\n" + "\t Note: When this flag is set to anything other than 'breakpad', then " + "the '-c' and '-i' flags must also be set.\n"); + fprintf(stderr, "-c:\t <code-file> Explicitly set 'code_file' for symbol " + "upload (basename of executable).\n"); + fprintf(stderr, "-i:\t <debug-id> Explicitly set 'debug_id' for symbol " + "upload (typically build ID of executable).\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Examples:\n"); + fprintf(stderr, " With 'sym-upload-v1':\n"); + fprintf(stderr, " %s path/to/symbol_file http://myuploadserver\n", + argv[0]); + fprintf(stderr, " With 'sym-upload-v2':\n"); + fprintf(stderr, " [Defaulting to symbol type 'BREAKPAD']\n"); + fprintf(stderr, " %s -p sym-upload-v2 -k mysecret123! " + "path/to/symbol_file http://myuploadserver\n", argv[0]); + fprintf(stderr, " [Explicitly set symbol type to 'elf']\n"); + fprintf(stderr, " %s -p sym-upload-v2 -k mysecret123! -t elf " + "-c app -i 11111111BBBB3333DDDD555555555555F " + "path/to/symbol_file http://myuploadserver\n", argv[0]); +} + +//============================================================================= +static void +SetupOptions(int argc, const char *argv[], Options *options) { + extern int optind; + int ch; + constexpr char flag_pattern[] = "u:v:x:p:k:t:c:i:hf?"; + + while ((ch = getopt(argc, (char * const *)argv, flag_pattern)) != -1) { + switch (ch) { + case 'h': + case '?': + Usage(argc, argv); + exit(0); + break; + case 'u': + options->proxy_user_pwd = optarg; + break; + case 'v': + options->version = optarg; + break; + case 'x': + options->proxy = optarg; + break; + case 'p': + if (strcmp(optarg, "sym-upload-v2") == 0) { + options->upload_protocol = UploadProtocol::SYM_UPLOAD_V2; + } else if (strcmp(optarg, "sym-upload-v1") == 0) { + options->upload_protocol = UploadProtocol::SYM_UPLOAD_V1; + } else { + fprintf(stderr, "Invalid protocol '%s'\n", optarg); + Usage(argc, argv); + exit(1); + } + break; + case 'k': + options->api_key = optarg; + break; + case 't': { + // This is really an enum, so treat as upper-case for consistency with + // enum naming convention on server-side. + options->type = optarg; + StrToUpper(&(options->type)); + break; + } + case 'c': + options->code_file = optarg; + break; + case 'i': + options->debug_id = optarg; + break; + case 'f': + options->force = true; + break; + + default: + fprintf(stderr, "Invalid option '%c'\n", ch); + Usage(argc, argv); + exit(1); + break; + } + } + + if ((argc - optind) != 2) { + fprintf(stderr, "%s: Missing symbols file and/or upload-URL\n", argv[0]); + Usage(argc, argv); + exit(1); + } + + bool is_breakpad_upload = options->type.empty() || + options->type == google_breakpad::sym_upload::kBreakpadSymbolType; + bool has_code_file = !options->code_file.empty(); + bool has_debug_id = !options->debug_id.empty(); + if (is_breakpad_upload && (has_code_file || has_debug_id)) { + fprintf(stderr, "\n"); + fprintf(stderr, "%s: -c and -i should only be specified for non-breakpad " + "symbol upload types.\n", argv[0]); + fprintf(stderr, "\n"); + Usage(argc, argv); + exit(1); + } + if (!is_breakpad_upload && (!has_code_file || !has_debug_id)) { + fprintf(stderr, "\n"); + fprintf(stderr, "%s: -c and -i must be specified for non-breakpad " + "symbol upload types.\n", argv[0]); + fprintf(stderr, "\n"); + Usage(argc, argv); + exit(1); + } + + options->symbolsPath = argv[optind]; + options->uploadURLStr = argv[optind + 1]; +} + +//============================================================================= +int main(int argc, const char* argv[]) { + Options options; + SetupOptions(argc, argv, &options); + google_breakpad::sym_upload::Start(&options); + return options.success ? 0 : 1; +} diff --git a/toolkit/crashreporter/google-breakpad/src/tools/linux/tools_linux.gypi b/toolkit/crashreporter/google-breakpad/src/tools/linux/tools_linux.gypi new file mode 100644 index 0000000000..020e4c1c71 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/tools/linux/tools_linux.gypi @@ -0,0 +1,83 @@ +# Copyright 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. + +{ + 'target_defaults': { + 'include_dirs': [ + '../..', + ], + }, + 'targets': [ + { + 'target_name': 'dump_syms', + 'type': 'executable', + 'sources': [ + 'dump_syms/dump_syms.cc', + ], + 'dependencies': [ + '../common/common.gyp:common', + ], + }, + { + 'target_name': 'md2core', + 'type': 'executable', + 'sources': [ + 'md2core/minidump-2-core.cc', + 'md2core/minidump_memory_range.h', + ], + 'dependencies': [ + '../common/common.gyp:common', + ], + }, + { + 'target_name': 'minidump_upload', + 'type': 'executable', + 'sources': [ + 'symupload/minidump_upload.cc', + ], + 'dependencies': [ + '../common/common.gyp:common', + ], + }, + { + 'target_name': 'symupload', + 'type': 'executable', + 'sources': [ + 'symupload/sym_upload.cc', + ], + 'link_settings': { + 'libraries': [ + '-ldl', + ], + }, + 'dependencies': [ + '../common/common.gyp:common', + ], + }, + ], +} |