// Copyright (c) 2012, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // linux_core_dumper.cc: Implement google_breakpad::LinuxCoreDumper. // See linux_core_dumper.h for details. #include "client/linux/minidump_writer/linux_core_dumper.h" #include #include #include #include #include #include #if defined(__mips__) && defined(__ANDROID__) // To get register definitions. #include #endif #include "common/linux/elf_gnu_compat.h" #include "common/linux/linux_libc_support.h" namespace google_breakpad { LinuxCoreDumper::LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path, const char* root_prefix) : LinuxDumper(pid, root_prefix), core_path_(core_path), procfs_path_(procfs_path), thread_infos_(&allocator_, 8) { assert(core_path_); } bool LinuxCoreDumper::BuildProcPath(char* path, pid_t pid, const char* node) const { if (!path || !node) return false; size_t node_len = my_strlen(node); if (node_len == 0) return false; size_t procfs_path_len = my_strlen(procfs_path_); size_t total_length = procfs_path_len + 1 + node_len; if (total_length >= NAME_MAX) return false; memcpy(path, procfs_path_, procfs_path_len); path[procfs_path_len] = '/'; memcpy(path + procfs_path_len + 1, node, node_len); path[total_length] = '\0'; return true; } bool LinuxCoreDumper::CopyFromProcess(void* dest, pid_t child, const void* src, size_t length) { ElfCoreDump::Addr virtual_address = reinterpret_cast(src); // TODO(benchan): Investigate whether the data to be copied could span // across multiple segments in the core dump file. ElfCoreDump::CopyData // and this method do not handle that case yet. if (!core_.CopyData(dest, virtual_address, length)) { // If the data segment is not found in the core dump, fill the result // with marker characters. memset(dest, 0xab, length); return false; } return true; } bool LinuxCoreDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) { if (index >= thread_infos_.size()) return false; *info = thread_infos_[index]; const uint8_t* stack_pointer; #if defined(__i386) memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); #elif defined(__x86_64) memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); #elif defined(__ARM_EABI__) memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp)); #elif defined(__aarch64__) memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp)); #elif defined(__mips__) stack_pointer = reinterpret_cast(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]); #else #error "This code hasn't been ported to your platform yet." #endif info->stack_pointer = reinterpret_cast(stack_pointer); return true; } bool LinuxCoreDumper::IsPostMortem() const { return true; } bool LinuxCoreDumper::ThreadsSuspend() { return true; } bool LinuxCoreDumper::ThreadsResume() { return true; } bool LinuxCoreDumper::EnumerateThreads() { if (!mapped_core_file_.Map(core_path_, 0)) { fprintf(stderr, "Could not map core dump file into memory\n"); return false; } core_.SetContent(mapped_core_file_.content()); if (!core_.IsValid()) { fprintf(stderr, "Invalid core dump file\n"); return false; } ElfCoreDump::Note note = core_.GetFirstNote(); if (!note.IsValid()) { fprintf(stderr, "PT_NOTE section not found\n"); return false; } bool first_thread = true; do { ElfCoreDump::Word type = note.GetType(); MemoryRange name = note.GetName(); MemoryRange description = note.GetDescription(); if (type == 0 || name.IsEmpty() || description.IsEmpty()) { fprintf(stderr, "Could not found a valid PT_NOTE.\n"); return false; } // Based on write_note_info() in linux/kernel/fs/binfmt_elf.c, notes are // ordered as follows (NT_PRXFPREG and NT_386_TLS are i386 specific): // Thread Name Type // ------------------------------------------------------------------- // 1st thread CORE NT_PRSTATUS // process-wide CORE NT_PRPSINFO // process-wide CORE NT_SIGINFO // process-wide CORE NT_AUXV // 1st thread CORE NT_FPREGSET // 1st thread LINUX NT_PRXFPREG // 1st thread LINUX NT_386_TLS // // 2nd thread CORE NT_PRSTATUS // 2nd thread CORE NT_FPREGSET // 2nd thread LINUX NT_PRXFPREG // 2nd thread LINUX NT_386_TLS // // 3rd thread CORE NT_PRSTATUS // 3rd thread CORE NT_FPREGSET // 3rd thread LINUX NT_PRXFPREG // 3rd thread LINUX NT_386_TLS // // The following code only works if notes are ordered as expected. switch (type) { case NT_PRSTATUS: { if (description.length() != sizeof(elf_prstatus)) { fprintf(stderr, "Found NT_PRSTATUS descriptor of unexpected size\n"); return false; } const elf_prstatus* status = reinterpret_cast(description.data()); pid_t pid = status->pr_pid; ThreadInfo info; memset(&info, 0, sizeof(ThreadInfo)); info.tgid = status->pr_pgrp; info.ppid = status->pr_ppid; #if defined(__mips__) #if defined(__ANDROID__) for (int i = EF_R0; i <= EF_R31; i++) info.mcontext.gregs[i - EF_R0] = status->pr_reg[i]; #else // __ANDROID__ for (int i = EF_REG0; i <= EF_REG31; i++) info.mcontext.gregs[i - EF_REG0] = status->pr_reg[i]; #endif // __ANDROID__ info.mcontext.mdlo = status->pr_reg[EF_LO]; info.mcontext.mdhi = status->pr_reg[EF_HI]; info.mcontext.pc = status->pr_reg[EF_CP0_EPC]; #else // __mips__ memcpy(&info.regs, status->pr_reg, sizeof(info.regs)); #endif // __mips__ if (first_thread) { crash_thread_ = pid; crash_signal_ = status->pr_info.si_signo; crash_signal_code_ = status->pr_info.si_code; } first_thread = false; threads_.push_back(pid); thread_infos_.push_back(info); break; } case NT_SIGINFO: { if (description.length() != sizeof(siginfo_t)) { fprintf(stderr, "Found NT_SIGINFO descriptor of unexpected size\n"); return false; } const siginfo_t* info = reinterpret_cast(description.data()); // Set crash_address when si_addr is valid for the signal. switch (info->si_signo) { case MD_EXCEPTION_CODE_LIN_SIGBUS: case MD_EXCEPTION_CODE_LIN_SIGFPE: case MD_EXCEPTION_CODE_LIN_SIGILL: case MD_EXCEPTION_CODE_LIN_SIGSEGV: case MD_EXCEPTION_CODE_LIN_SIGSYS: case MD_EXCEPTION_CODE_LIN_SIGTRAP: crash_address_ = reinterpret_cast(info->si_addr); break; } // Set crash_exception_info for common signals. Since exception info is // unsigned, but some of these fields might be signed, we always cast. switch (info->si_signo) { case MD_EXCEPTION_CODE_LIN_SIGKILL: set_crash_exception_info({ static_cast(info->si_pid), static_cast(info->si_uid), }); break; case MD_EXCEPTION_CODE_LIN_SIGSYS: #ifdef si_syscall set_crash_exception_info({ static_cast(info->si_syscall), static_cast(info->si_arch), }); #endif break; } break; } #if defined(__i386) || defined(__x86_64) case NT_FPREGSET: { if (thread_infos_.empty()) return false; ThreadInfo* info = &thread_infos_.back(); if (description.length() != sizeof(info->fpregs)) { fprintf(stderr, "Found NT_FPREGSET descriptor of unexpected size\n"); return false; } memcpy(&info->fpregs, description.data(), sizeof(info->fpregs)); break; } #endif #if defined(__i386) case NT_PRXFPREG: { if (thread_infos_.empty()) return false; ThreadInfo* info = &thread_infos_.back(); if (description.length() != sizeof(info->fpxregs)) { fprintf(stderr, "Found NT_PRXFPREG descriptor of unexpected size\n"); return false; } memcpy(&info->fpxregs, description.data(), sizeof(info->fpxregs)); break; } #endif } note = note.GetNextNote(); } while (note.IsValid()); return true; } } // namespace google_breakpad