| Index: breakpad/src/tools/linux/md2core/minidump-2-core.cc
|
| ===================================================================
|
| --- breakpad/src/tools/linux/md2core/minidump-2-core.cc (revision 639)
|
| +++ breakpad/src/tools/linux/md2core/minidump-2-core.cc (working copy)
|
| @@ -31,40 +31,40 @@
|
| // Large parts lifted from the userspace core dumper:
|
| // http://code.google.com/p/google-coredumper/
|
| //
|
| -// Usage: minidump-2-core 1234.dmp > core
|
| +// Usage: minidump-2-core [-v] 1234.dmp > core
|
|
|
| +#include <map>
|
| +#include <string>
|
| #include <vector>
|
|
|
| #include <stdio.h>
|
| +#include <stdlib.h>
|
| #include <string.h>
|
|
|
| #include <elf.h>
|
| #include <errno.h>
|
| #include <unistd.h>
|
| #include <fcntl.h>
|
| +#include <link.h>
|
| #include <sys/user.h>
|
| #include <sys/mman.h>
|
|
|
| #include "google_breakpad/common/minidump_format.h"
|
| #include "google_breakpad/common/minidump_cpu_x86.h"
|
| +#include "client/linux/minidump_writer/minidump_extension_linux.h"
|
| #include "common/linux/linux_syscall_support.h"
|
| -#include "common/linux/minidump_format_linux.h"
|
|
|
| +
|
| #if __WORDSIZE == 64
|
| #define ELF_CLASS ELFCLASS64
|
| - #define Ehdr Elf64_Ehdr
|
| - #define Phdr Elf64_Phdr
|
| - #define Shdr Elf64_Shdr
|
| - #define Nhdr Elf64_Nhdr
|
| - #define auxv_t Elf64_auxv_t
|
| #else
|
| #define ELF_CLASS ELFCLASS32
|
| - #define Ehdr Elf32_Ehdr
|
| - #define Phdr Elf32_Phdr
|
| - #define Shdr Elf32_Shdr
|
| - #define Nhdr Elf32_Nhdr
|
| - #define auxv_t Elf32_auxv_t
|
| #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__)
|
| @@ -77,8 +77,11 @@
|
| #define ELF_ARCH EM_MIPS
|
| #endif
|
|
|
| +static const MDRVA kInvalidMDRVA = static_cast<MDRVA>(-1);
|
| +static bool verbose;
|
| +
|
| static int usage(const char* argv0) {
|
| - fprintf(stderr, "Usage: %s <minidump file>\n", argv0);
|
| + fprintf(stderr, "Usage: %s [-v] <minidump file>\n", argv0);
|
| return 1;
|
| }
|
|
|
| @@ -113,7 +116,7 @@
|
|
|
| // Get an object of |length| bytes at |offset| and return a pointer to it
|
| // unless it's out of bounds.
|
| - const void* GetObject(size_t offset, size_t length) {
|
| + const void* GetObject(size_t offset, size_t length) const {
|
| if (offset + length < offset)
|
| return NULL;
|
| if (offset + length > length_)
|
| @@ -123,11 +126,24 @@
|
|
|
| // Get element |index| of an array of objects of length |length| starting at
|
| // |offset| bytes. Return NULL if out of bounds.
|
| - const void* GetArrayElement(size_t offset, size_t length, unsigned index) {
|
| + const void* GetArrayElement(size_t offset, size_t length,
|
| + unsigned index) const {
|
| const size_t element_offset = offset + index * length;
|
| return GetObject(element_offset, length);
|
| }
|
|
|
| + // Get a zero-terminated string. This method only works correctly for ASCII
|
| + // characters and does not convert between UTF-16 and UTF-8.
|
| + const std::string GetString(size_t offset) const {
|
| + const MDString* s = (const MDString*) GetObject(offset, sizeof(MDString));
|
| + const u_int16_t* buf = &s->buffer[0];
|
| + std::string str;
|
| + for (unsigned i = 0; i < s->length && buf[i]; ++i) {
|
| + str.push_back(buf[i]);
|
| + }
|
| + return str;
|
| + }
|
| +
|
| // Return a new range which is a subset of this range.
|
| MMappedRange Subrange(const MDLocationDescriptor& location) const {
|
| if (location.rva > length_ ||
|
| @@ -204,7 +220,7 @@
|
| char pr_psargs[80]; /* Initial part of arg list */
|
| } prpsinfo;
|
|
|
| -// We parse the minidump file and keep the parsed information in this structure.
|
| +// We parse the minidump file and keep the parsed information in this structure
|
| struct CrashedProcess {
|
| CrashedProcess()
|
| : crashing_tid(-1),
|
| @@ -212,12 +228,23 @@
|
| auxv_length(0) {
|
| memset(&prps, 0, sizeof(prps));
|
| prps.pr_sname = 'R';
|
| + memset(&debug, 0, sizeof(debug));
|
| }
|
|
|
| struct Mapping {
|
| - uint64_t start_address, end_address;
|
| + Mapping()
|
| + : permissions(0xFFFFFFFF),
|
| + start_address(0),
|
| + end_address(0),
|
| + offset(0) {
|
| + }
|
| +
|
| + uint32_t permissions;
|
| + uint64_t start_address, end_address, offset;
|
| + std::string filename;
|
| + std::string data;
|
| };
|
| - std::vector<Mapping> mappings;
|
| + std::map<uint64_t, Mapping> mappings;
|
|
|
| pid_t crashing_tid;
|
| int fatal_signal;
|
| @@ -226,7 +253,9 @@
|
| pid_t tid;
|
| user_regs_struct regs;
|
| user_fpregs_struct fpregs;
|
| +#if defined(__i386__)
|
| user_fpxregs_struct fpxregs;
|
| +#endif
|
| uintptr_t stack_addr;
|
| const uint8_t* stack;
|
| size_t stack_length;
|
| @@ -237,8 +266,15 @@
|
| size_t auxv_length;
|
|
|
| prpsinfo prps;
|
| +
|
| + std::map<uintptr_t, std::string> signatures;
|
| +
|
| + std::string dynamic_data;
|
| + MDRawDebug debug;
|
| + std::vector<MDRawLinkMap> link_map;
|
| };
|
|
|
| +#if defined(__i386__)
|
| static uint32_t
|
| U32(const uint8_t* data) {
|
| uint32_t v;
|
| @@ -253,7 +289,6 @@
|
| return v;
|
| }
|
|
|
| -#if defined(__i386__)
|
| static void
|
| ParseThreadRegisters(CrashedProcess::Thread* thread, MMappedRange range) {
|
| const MDRawContextX86* rawregs =
|
| @@ -299,6 +334,51 @@
|
| 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, MMappedRange range) {
|
| + const MDRawContextAMD64* rawregs =
|
| + (const MDRawContextAMD64*) range.GetObject(0, sizeof(MDRawContextAMD64));
|
| +
|
| + 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);
|
| +}
|
| #else
|
| #error "This code has not been ported to your platform yet"
|
| #endif
|
| @@ -308,6 +388,13 @@
|
| const MMappedRange& full_file) {
|
| const uint32_t num_threads =
|
| *(const uint32_t*) range.GetObject(0, sizeof(uint32_t));
|
| + if (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));
|
| @@ -328,43 +415,304 @@
|
| }
|
|
|
| static void
|
| +ParseSystemInfo(CrashedProcess* crashinfo, MMappedRange range,
|
| + const MMappedRange &full_file) {
|
| + const MDRawSystemInfo* sysinfo =
|
| + (MDRawSystemInfo*) range.GetObject(0, sizeof(MDRawSystemInfo));
|
| + 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);
|
| + }
|
| +#else
|
| +#error "This code has not been ported to your platform yet"
|
| +#endif
|
| + if (!strstr(full_file.GetString(sysinfo->csd_version_rva).c_str(), "Linux")){
|
| + fprintf(stderr, "This minidump was not generated by Linux.\n");
|
| + _exit(1);
|
| + }
|
| +
|
| + if (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->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.GetString(sysinfo->csd_version_rva).c_str());
|
| + fputs("\n\n", stderr);
|
| + }
|
| +}
|
| +
|
| +static void
|
| +ParseCPUInfo(CrashedProcess* crashinfo, MMappedRange range) {
|
| + if (verbose) {
|
| + fputs("MD_LINUX_CPU_INFO:\n", stderr);
|
| + fwrite(range.data(), range.length(), 1, stderr);
|
| + fputs("\n\n\n", stderr);
|
| + }
|
| +}
|
| +
|
| +static void
|
| +ParseProcessStatus(CrashedProcess* crashinfo, MMappedRange range) {
|
| + if (verbose) {
|
| + fputs("MD_LINUX_PROC_STATUS:\n", stderr);
|
| + fwrite(range.data(), range.length(), 1, stderr);
|
| + fputs("\n\n", stderr);
|
| + }
|
| +}
|
| +
|
| +static void
|
| +ParseLSBRelease(CrashedProcess* crashinfo, MMappedRange range) {
|
| + if (verbose) {
|
| + fputs("MD_LINUX_LSB_RELEASE:\n", stderr);
|
| + fwrite(range.data(), range.length(), 1, stderr);
|
| + fputs("\n\n", stderr);
|
| + }
|
| +}
|
| +
|
| +static void
|
| +ParseMaps(CrashedProcess* crashinfo, MMappedRange range) {
|
| + if (verbose) {
|
| + fputs("MD_LINUX_MAPS:\n", stderr);
|
| + fwrite(range.data(), range.length(), 1, stderr);
|
| + }
|
| + for (const u_int8_t* ptr = range.data();
|
| + ptr < range.data() + range.length();) {
|
| + const u_int8_t* eol = (u_int8_t*)memchr(ptr, '\n',
|
| + range.data() + range.length() - ptr);
|
| + std::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 (verbose) {
|
| + fputs("\n\n\n", stderr);
|
| + }
|
| +}
|
| +
|
| +static void
|
| +ParseEnvironment(CrashedProcess* crashinfo, MMappedRange range) {
|
| + if (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(CrashedProcess* crashinfo, MMappedRange 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(crashinfo, range);
|
| + return;
|
| + }
|
| + }
|
| +
|
| crashinfo->auxv = range.data();
|
| crashinfo->auxv_length = range.length();
|
| }
|
|
|
| static void
|
| ParseCmdLine(CrashedProcess* crashinfo, MMappedRange 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 (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] == 0) {
|
| + 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);
|
| - const char* binary_name = strrchr(cmdline, '/');
|
| - if (binary_name) {
|
| - binary_name++;
|
| - const unsigned len = strlen(binary_name);
|
| - memcpy(crashinfo->prps.pr_fname, binary_name,
|
| + unsigned len = cmdline + i - binary_name;
|
| + memcpy(crashinfo->prps.pr_fname, binary_name,
|
| len > fname_len ? fname_len : len);
|
| - } else {
|
| - memcpy(crashinfo->prps.pr_fname, cmdline,
|
| - i > fname_len ? fname_len : i);
|
| - }
|
|
|
| - const unsigned len = range.length() > args_len ?
|
| - args_len : range.length();
|
| + len = range.length() > args_len ? args_len : range.length();
|
| memcpy(crashinfo->prps.pr_psargs, cmdline, len);
|
| for (unsigned i = 0; i < len; ++i) {
|
| if (crashinfo->prps.pr_psargs[i] == 0)
|
| crashinfo->prps.pr_psargs[i] = ' ';
|
| }
|
| + break;
|
| }
|
| }
|
| }
|
|
|
| static void
|
| +ParseDSODebugInfo(CrashedProcess* crashinfo, MMappedRange range,
|
| + const MMappedRange &full_file) {
|
| + const MDRawDebug* debug =
|
| + (MDRawDebug*) range.GetObject(0, sizeof(MDRawDebug));
|
| + if (!debug) {
|
| + return;
|
| + }
|
| + if (verbose) {
|
| + fprintf(stderr,
|
| + "MD_LINUX_DSO_DEBUG:\n"
|
| + "Version: %d\n"
|
| + "Number of DSOs: %d\n"
|
| + "Brk handler: %p\n"
|
| + "Dynamic loader at: %p\n"
|
| + "_DYNAMIC: %p\n",
|
| + debug->version,
|
| + debug->dso_count,
|
| + debug->brk,
|
| + debug->ldbase,
|
| + 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 (int i = 0; i < debug->dso_count; ++i) {
|
| + const MDRawLinkMap* link_map =
|
| + (MDRawLinkMap*) full_file.GetArrayElement(debug->map,
|
| + sizeof(MDRawLinkMap), i);
|
| + if (link_map) {
|
| + if (verbose) {
|
| + fprintf(stderr,
|
| + "#%03d: %p, %p, \"%s\"\n",
|
| + i, link_map->addr, link_map->ld,
|
| + full_file.GetString(link_map->name).c_str());
|
| + }
|
| + crashinfo->link_map.push_back(*link_map);
|
| + }
|
| + }
|
| + }
|
| + if (verbose) {
|
| + fputs("\n\n", stderr);
|
| + }
|
| +}
|
| +
|
| +static void
|
| ParseExceptionStream(CrashedProcess* crashinfo, MMappedRange range) {
|
| const MDRawExceptionStream* exp =
|
| (MDRawExceptionStream*) range.GetObject(0, sizeof(MDRawExceptionStream));
|
| @@ -401,6 +749,7 @@
|
| return false;
|
| }
|
|
|
| +#if defined(__i386__)
|
| nhdr.n_descsz = sizeof(user_fpxregs_struct);
|
| nhdr.n_type = NT_PRXFPREG;
|
| if (!writea(1, &nhdr, sizeof(nhdr)) ||
|
| @@ -408,12 +757,17 @@
|
| !writea(1, &thread.fpxregs, sizeof(user_fpxregs_struct))) {
|
| return false;
|
| }
|
| +#endif
|
|
|
| return true;
|
| }
|
|
|
| static void
|
| -ParseModuleStream(CrashedProcess* crashinfo, MMappedRange range) {
|
| +ParseModuleStream(CrashedProcess* crashinfo, MMappedRange range,
|
| + const MMappedRange &full_file) {
|
| + if (verbose) {
|
| + fputs("MD_MODULE_LIST_STREAM:\n", stderr);
|
| + }
|
| const uint32_t num_mappings =
|
| *(const uint32_t*) range.GetObject(0, sizeof(uint32_t));
|
| for (unsigned i = 0; i < num_mappings; ++i) {
|
| @@ -424,16 +778,199 @@
|
| mapping.start_address = rawmodule->base_of_image;
|
| mapping.end_address = rawmodule->size_of_image + rawmodule->base_of_image;
|
|
|
| - crashinfo->mappings.push_back(mapping);
|
| + 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 =
|
| + (const MDCVInfoPDB70*)full_file.GetObject(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]);
|
| + std::string filename = full_file.GetString(rawmodule->module_name_rva);
|
| + size_t slash = filename.find_last_of('/');
|
| + std::string basename = slash == std::string::npos ?
|
| + filename : filename.substr(slash + 1);
|
| + if (strcmp(guid, "00000000-0000-0000-0000-000000000000")) {
|
| + crashinfo->signatures[rawmodule->base_of_image] =
|
| + std::string("/var/lib/breakpad/") + guid + "-" + basename;
|
| + }
|
| +
|
| + if (verbose) {
|
| + fprintf(stderr, "0x%08llX-0x%08llX, ChkSum: 0x%08X, GUID: %s, \"%s\"\n",
|
| + (unsigned long long)rawmodule->base_of_image,
|
| + (unsigned long long)rawmodule->base_of_image +
|
| + rawmodule->size_of_image,
|
| + rawmodule->checksum, guid, filename.c_str());
|
| + }
|
| }
|
| + if (verbose) {
|
| + fputs("\n\n", stderr);
|
| + }
|
| }
|
|
|
| +static void
|
| +AddDataToMapping(CrashedProcess* crashinfo, const std::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(CrashedProcess* crashinfo,
|
| + const MMappedRange &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,
|
| + std::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;
|
| + std::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());
|
| + std::string filename = full_file.GetString(iter->name);
|
| +
|
| + // Look up signature for this filename. If available, change filename
|
| + // to point to GUID, instead.
|
| + std::map<uintptr_t, std::string>::const_iterator guid =
|
| + crashinfo->signatures.find((uintptr_t)iter->addr);
|
| + if (guid != crashinfo->signatures.end()) {
|
| + filename = guid->second;
|
| + }
|
| +
|
| + 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 (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, char** argv) {
|
| - if (argc != 2)
|
| + int argi = 1;
|
| + while (argi < argc && argv[argi][0] == '-') {
|
| + if (!strcmp(argv[argi], "-v")) {
|
| + verbose = true;
|
| + } else {
|
| + return usage(argv[0]);
|
| + }
|
| + argi++;
|
| + }
|
| +
|
| + if (argc != argi + 1)
|
| return usage(argv[0]);
|
|
|
| - const int fd = open(argv[1], O_RDONLY);
|
| + const int fd = open(argv[argi], O_RDONLY);
|
| if (fd < 0)
|
| return usage(argv[0]);
|
|
|
| @@ -454,30 +991,73 @@
|
|
|
| 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 =
|
| (const MDRawDirectory*) dump.GetArrayElement(
|
| header->stream_directory_rva, sizeof(MDRawDirectory), i);
|
| switch (dirent->stream_type) {
|
| + case MD_SYSTEM_INFO_STREAM:
|
| + ParseSystemInfo(&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 =
|
| + (const MDRawDirectory*) dump.GetArrayElement(
|
| + header->stream_directory_rva, sizeof(MDRawDirectory), i);
|
| + switch (dirent->stream_type) {
|
| case MD_THREAD_LIST_STREAM:
|
| ParseThreadList(&crashinfo, dump.Subrange(dirent->location), dump);
|
| break;
|
| + case MD_LINUX_CPU_INFO:
|
| + ParseCPUInfo(&crashinfo, dump.Subrange(dirent->location));
|
| + break;
|
| + case MD_LINUX_PROC_STATUS:
|
| + ParseProcessStatus(&crashinfo, dump.Subrange(dirent->location));
|
| + break;
|
| + case MD_LINUX_LSB_RELEASE:
|
| + ParseLSBRelease(&crashinfo, dump.Subrange(dirent->location));
|
| + break;
|
| + case MD_LINUX_ENVIRON:
|
| + ParseEnvironment(&crashinfo, dump.Subrange(dirent->location));
|
| + break;
|
| + case MD_LINUX_MAPS:
|
| + ParseMaps(&crashinfo, dump.Subrange(dirent->location));
|
| + break;
|
| case MD_LINUX_AUXV:
|
| ParseAuxVector(&crashinfo, dump.Subrange(dirent->location));
|
| break;
|
| case MD_LINUX_CMD_LINE:
|
| ParseCmdLine(&crashinfo, dump.Subrange(dirent->location));
|
| break;
|
| + case MD_LINUX_DSO_DEBUG:
|
| + ParseDSODebugInfo(&crashinfo, dump.Subrange(dirent->location), dump);
|
| + break;
|
| case MD_EXCEPTION_STREAM:
|
| ParseExceptionStream(&crashinfo, dump.Subrange(dirent->location));
|
| break;
|
| case MD_MODULE_LIST_STREAM:
|
| - ParseModuleStream(&crashinfo, dump.Subrange(dirent->location));
|
| + ParseModuleStream(&crashinfo, dump.Subrange(dirent->location), dump);
|
| + break;
|
| default:
|
| - fprintf(stderr, "Skipping %x\n", dirent->stream_type);
|
| + if (verbose)
|
| + fprintf(stderr, "Skipping %x\n", dirent->stream_type);
|
| }
|
| }
|
|
|
| + AugmentMappings(&crashinfo, dump);
|
| +
|
| // Write the ELF header. The file will look like:
|
| // ELF header
|
| // Phdr for the PT_NOTE
|
| @@ -499,21 +1079,23 @@
|
| ehdr.e_phoff = sizeof(Ehdr);
|
| ehdr.e_ehsize = sizeof(Ehdr);
|
| ehdr.e_phentsize= sizeof(Phdr);
|
| - ehdr.e_phnum = 1 + crashinfo.threads.size() + crashinfo.mappings.size();
|
| + ehdr.e_phnum = 1 + // PT_NOTE
|
| + crashinfo.mappings.size(); // memory mappings
|
| ehdr.e_shentsize= sizeof(Shdr);
|
| if (!writea(1, &ehdr, sizeof(Ehdr)))
|
| return 1;
|
|
|
| - size_t offset = sizeof(Ehdr) +
|
| - (1 + crashinfo.threads.size() +
|
| - crashinfo.mappings.size()) * sizeof(Phdr);
|
| + 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)) +
|
| - sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct) +
|
| - sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct));
|
| + sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct)
|
| +#if defined(__i386__)
|
| + + sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct)
|
| +#endif
|
| + );
|
|
|
| Phdr phdr;
|
| memset(&phdr, 0, sizeof(Phdr));
|
| @@ -524,31 +1106,35 @@
|
| return 1;
|
|
|
| phdr.p_type = PT_LOAD;
|
| - phdr.p_align = getpagesize();
|
| + 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 (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
|
| - const CrashedProcess::Thread& thread = crashinfo.threads[i];
|
| - offset += filesz;
|
| - filesz = thread.stack_length;
|
| - phdr.p_offset = offset;
|
| - phdr.p_vaddr = thread.stack_addr;
|
| - phdr.p_filesz = phdr.p_memsz = filesz;
|
| - phdr.p_flags = PF_R | PF_W;
|
| - if (!writea(1, &phdr, sizeof(phdr)))
|
| - return 1;
|
| - }
|
| -
|
| - for (unsigned i = 0; i < crashinfo.mappings.size(); ++i) {
|
| - const CrashedProcess::Mapping& mapping = crashinfo.mappings[i];
|
| - phdr.p_offset = 0;
|
| + 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_filesz = 0;
|
| - phdr.p_flags = PF_R;
|
| 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(1, &phdr, sizeof(phdr)))
|
| return 1;
|
| }
|
| @@ -591,10 +1177,14 @@
|
| return 1;
|
| }
|
|
|
| - for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
|
| - const CrashedProcess::Thread& thread = crashinfo.threads[i];
|
| - if (!writea(1, thread.stack, thread.stack_length))
|
| - 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(1, mapping.data.c_str(), mapping.data.size()))
|
| + return 1;
|
| + }
|
| }
|
|
|
| munmap(const_cast<void*>(bytes), st.st_size);
|
|
|