diff options
Diffstat (limited to 'src/tools/linux')
-rw-r--r-- | src/tools/linux/md2core/minidump-2-core.cc | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/src/tools/linux/md2core/minidump-2-core.cc b/src/tools/linux/md2core/minidump-2-core.cc new file mode 100644 index 00000000..e333d119 --- /dev/null +++ b/src/tools/linux/md2core/minidump-2-core.cc @@ -0,0 +1,602 @@ +// 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/ +// +// Usage: minidump-2-core 1234.dmp > core + +#include <vector> + +#include <stdio.h> +#include <string.h> + +#include <elf.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.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 "breakpad/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 + + +#if defined(__x86_64__) + #define ELF_ARCH EM_X86_64 +#elif defined(__i386__) + #define ELF_ARCH EM_386 +#elif defined(__ARM_ARCH_3__) + #define ELF_ARCH EM_ARM +#elif defined(__mips__) + #define ELF_ARCH EM_MIPS +#endif + +static int usage(const char* argv0) { + fprintf(stderr, "Usage: %s <minidump file>\n", argv0); + return 1; +} + +// 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; +} + +// A range of a mmaped file. +class MMappedRange { + public: + MMappedRange(const void* data, size_t length) + : data_(reinterpret_cast<const uint8_t*>(data)), + length_(length) { + } + + // 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) { + if (offset + length < offset) + return NULL; + if (offset + length > length_) + return NULL; + return data_ + offset; + } + + // 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 size_t element_offset = offset + index * length; + return GetObject(element_offset, length); + } + + // Return a new range which is a subset of this range. + MMappedRange Subrange(const MDLocationDescriptor& location) const { + if (location.rva > length_ || + location.rva + location.data_size < location.rva || + location.rva + location.data_size > length_) { + return MMappedRange(NULL, 0); + } + + return MMappedRange(data_ + location.rva, location.data_size); + } + + const uint8_t* data() const { return data_; } + size_t length() const { return length_; } + + private: + const uint8_t* const data_; + const size_t length_; +}; + +/* 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'; + } + + struct Mapping { + uint64_t start_address, end_address; + }; + std::vector<Mapping> mappings; + + pid_t crashing_tid; + int fatal_signal; + + struct Thread { + pid_t tid; + user_regs_struct regs; + user_fpregs_struct fpregs; + user_fpxregs_struct fpxregs; + 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; +}; + +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; +} + +#if defined(__i386__) +static void +ParseThreadRegisters(CrashedProcess::Thread* thread, MMappedRange range) { + const MDRawContextX86* rawregs = + (const MDRawContextX86*) range.GetObject(0, sizeof(MDRawContextX86)); + + 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); +} +#else +#error "This code has not been ported to your platform yet" +#endif + +static void +ParseThreadList(CrashedProcess* crashinfo, MMappedRange range, + const MMappedRange& full_file) { + const uint32_t num_threads = + *(const uint32_t*) range.GetObject(0, sizeof(uint32_t)); + for (unsigned i = 0; i < num_threads; ++i) { + CrashedProcess::Thread thread; + memset(&thread, 0, sizeof(thread)); + const MDRawThread* rawthread = + (MDRawThread*) range.GetArrayElement(sizeof(uint32_t), + sizeof(MDRawThread), i); + thread.tid = rawthread->thread_id; + thread.stack_addr = rawthread->stack.start_of_memory_range; + MMappedRange 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 +ParseAuxVector(CrashedProcess* crashinfo, MMappedRange range) { + crashinfo->auxv = range.data(); + crashinfo->auxv_length = range.length(); +} + +static void +ParseCmdLine(CrashedProcess* crashinfo, MMappedRange range) { + const char* cmdline = (const char*) range.data(); + for (size_t i = 0; i < range.length(); ++i) { + if (cmdline[i] == 0) { + 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, + 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(); + 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] = ' '; + } + } + } +} + +static void +ParseExceptionStream(CrashedProcess* crashinfo, MMappedRange range) { + const MDRawExceptionStream* exp = + (MDRawExceptionStream*) range.GetObject(0, sizeof(MDRawExceptionStream)); + crashinfo->crashing_tid = exp->thread_id; + crashinfo->fatal_signal = (int) exp->exception_record.exception_code; +} + +static bool +WriteThread(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; + memcpy(&pr.pr_reg, &thread.regs, sizeof(user_regs_struct)); + + Nhdr nhdr; + memset(&nhdr, 0, sizeof(nhdr)); + nhdr.n_namesz = 5; + nhdr.n_descsz = sizeof(struct prstatus); + nhdr.n_type = NT_PRSTATUS; + if (!writea(1, &nhdr, sizeof(nhdr)) || + !writea(1, "CORE\0\0\0\0", 8) || + !writea(1, &pr, sizeof(struct prstatus))) { + return false; + } + + nhdr.n_descsz = sizeof(user_fpregs_struct); + nhdr.n_type = NT_FPREGSET; + if (!writea(1, &nhdr, sizeof(nhdr)) || + !writea(1, "CORE\0\0\0\0", 8) || + !writea(1, &thread.fpregs, sizeof(user_fpregs_struct))) { + return false; + } + + nhdr.n_descsz = sizeof(user_fpxregs_struct); + nhdr.n_type = NT_PRXFPREG; + if (!writea(1, &nhdr, sizeof(nhdr)) || + !writea(1, "LINUX\0\0\0", 8) || + !writea(1, &thread.fpxregs, sizeof(user_fpxregs_struct))) { + return false; + } + + return true; +} + +static void +ParseModuleStream(CrashedProcess* crashinfo, MMappedRange range) { + const uint32_t num_mappings = + *(const uint32_t*) range.GetObject(0, sizeof(uint32_t)); + for (unsigned i = 0; i < num_mappings; ++i) { + CrashedProcess::Mapping mapping; + const MDRawModule* rawmodule = + (MDRawModule*) range.GetArrayElement(sizeof(uint32_t), + sizeof(MDRawModule), i); + mapping.start_address = rawmodule->base_of_image; + mapping.end_address = rawmodule->size_of_image + rawmodule->base_of_image; + + crashinfo->mappings.push_back(mapping); + } +} + +int +main(int argc, char** argv) { + if (argc != 2) + return usage(argv[0]); + + const int fd = open(argv[1], O_RDONLY); + if (fd < 0) + return usage(argv[0]); + + struct stat st; + fstat(fd, &st); + + const void* bytes = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (bytes == MAP_FAILED) { + perror("Failed to mmap dump file"); + return 1; + } + + MMappedRange dump(bytes, st.st_size); + + const MDRawHeader* header = + (const MDRawHeader*) dump.GetObject(0, sizeof(MDRawHeader)); + + CrashedProcess crashinfo; + + 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_AUXV: + ParseAuxVector(&crashinfo, dump.Subrange(dirent->location)); + break; + case MD_LINUX_CMD_LINE: + ParseCmdLine(&crashinfo, dump.Subrange(dirent->location)); + break; + case MD_EXCEPTION_STREAM: + ParseExceptionStream(&crashinfo, dump.Subrange(dirent->location)); + break; + case MD_MODULE_LIST_STREAM: + ParseModuleStream(&crashinfo, dump.Subrange(dirent->location)); + default: + fprintf(stderr, "Skipping %x\n", dirent->stream_type); + } + } + + // 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 + crashinfo.threads.size() + crashinfo.mappings.size(); + 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 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)); + + Phdr phdr; + memset(&phdr, 0, sizeof(Phdr)); + phdr.p_type = PT_NOTE; + phdr.p_offset = offset; + phdr.p_filesz = filesz; + if (!writea(1, &phdr, sizeof(phdr))) + return 1; + + phdr.p_type = PT_LOAD; + phdr.p_align = getpagesize(); + 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; + 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 (!writea(1, &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(1, &nhdr, sizeof(nhdr)) || + !writea(1, "CORE\0\0\0\0", 8) || + !writea(1, &crashinfo.prps, sizeof(prpsinfo))) { + return 1; + } + + nhdr.n_descsz = crashinfo.auxv_length; + nhdr.n_type = NT_AUXV; + if (!writea(1, &nhdr, sizeof(nhdr)) || + !writea(1, "CORE\0\0\0\0", 8) || + !writea(1, &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(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(crashinfo.threads[i], 0); + } + + if (note_align) { + char scratch[note_align]; + memset(scratch, 0, sizeof(scratch)); + if (!writea(1, scratch, sizeof(scratch))) + 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; + } + + munmap(const_cast<void*>(bytes), st.st_size); + + return 0; +} |