| // 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. |
| |
| #include "google_breakpad/processor/minidump_processor.h" |
| |
| #include <assert.h> |
| |
| #include <algorithm> |
| #include <string> |
| |
| #include "common/scoped_ptr.h" |
| #include "common/stdio_wrapper.h" |
| #include "common/using_std_string.h" |
| #include "google_breakpad/processor/call_stack.h" |
| #include "google_breakpad/processor/minidump.h" |
| #include "google_breakpad/processor/process_state.h" |
| #include "google_breakpad/processor/exploitability.h" |
| #include "google_breakpad/processor/stack_frame_symbolizer.h" |
| #include "processor/logging.h" |
| #include "processor/stackwalker_x86.h" |
| #include "processor/symbolic_constants_win.h" |
| |
| namespace google_breakpad { |
| |
| MinidumpProcessor::MinidumpProcessor(SymbolSupplier* supplier, |
| SourceLineResolverInterface* resolver) |
| : frame_symbolizer_(new StackFrameSymbolizer(supplier, resolver)), |
| own_frame_symbolizer_(true), |
| enable_exploitability_(false), |
| enable_objdump_(false) { |
| } |
| |
| MinidumpProcessor::MinidumpProcessor(SymbolSupplier* supplier, |
| SourceLineResolverInterface* resolver, |
| bool enable_exploitability) |
| : frame_symbolizer_(new StackFrameSymbolizer(supplier, resolver)), |
| own_frame_symbolizer_(true), |
| enable_exploitability_(enable_exploitability), |
| enable_objdump_(false) { |
| } |
| |
| MinidumpProcessor::MinidumpProcessor(StackFrameSymbolizer* frame_symbolizer, |
| bool enable_exploitability) |
| : frame_symbolizer_(frame_symbolizer), |
| own_frame_symbolizer_(false), |
| enable_exploitability_(enable_exploitability), |
| enable_objdump_(false) { |
| assert(frame_symbolizer_); |
| } |
| |
| MinidumpProcessor::~MinidumpProcessor() { |
| if (own_frame_symbolizer_) delete frame_symbolizer_; |
| } |
| |
| ProcessResult MinidumpProcessor::Process( |
| Minidump* dump, ProcessState* process_state) { |
| assert(dump); |
| assert(process_state); |
| |
| process_state->Clear(); |
| |
| const MDRawHeader* header = dump->header(); |
| if (!header) { |
| BPLOG(ERROR) << "Minidump " << dump->path() << " has no header"; |
| return PROCESS_ERROR_NO_MINIDUMP_HEADER; |
| } |
| process_state->time_date_stamp_ = header->time_date_stamp; |
| |
| bool has_process_create_time = |
| GetProcessCreateTime(dump, &process_state->process_create_time_); |
| |
| bool has_cpu_info = GetCPUInfo(dump, &process_state->system_info_); |
| bool has_os_info = GetOSInfo(dump, &process_state->system_info_); |
| |
| uint32_t dump_thread_id = 0; |
| bool has_dump_thread = false; |
| uint32_t requesting_thread_id = 0; |
| bool has_requesting_thread = false; |
| |
| MinidumpBreakpadInfo* breakpad_info = dump->GetBreakpadInfo(); |
| if (breakpad_info) { |
| has_dump_thread = breakpad_info->GetDumpThreadID(&dump_thread_id); |
| has_requesting_thread = |
| breakpad_info->GetRequestingThreadID(&requesting_thread_id); |
| } |
| |
| MinidumpException* exception = dump->GetException(); |
| if (exception) { |
| process_state->crashed_ = true; |
| has_requesting_thread = exception->GetThreadID(&requesting_thread_id); |
| |
| process_state->crash_reason_ = GetCrashReason( |
| dump, &process_state->crash_address_); |
| |
| process_state->exception_record_.set_code( |
| exception->exception()->exception_record.exception_code, |
| // TODO(ivanpe): Populate description. |
| /* description = */ ""); |
| process_state->exception_record_.set_flags( |
| exception->exception()->exception_record.exception_flags, |
| // TODO(ivanpe): Populate description. |
| /* description = */ ""); |
| process_state->exception_record_.set_nested_exception_record_address( |
| exception->exception()->exception_record.exception_record); |
| process_state->exception_record_.set_address(process_state->crash_address_); |
| const uint32_t num_parameters = |
| std::min(exception->exception()->exception_record.number_parameters, |
| MD_EXCEPTION_MAXIMUM_PARAMETERS); |
| for (uint32_t i = 0; i < num_parameters; ++i) { |
| process_state->exception_record_.add_parameter( |
| exception->exception()->exception_record.exception_information[i], |
| // TODO(ivanpe): Populate description. |
| /* description = */ ""); |
| } |
| } |
| |
| // This will just return an empty string if it doesn't exist. |
| process_state->assertion_ = GetAssertion(dump); |
| |
| MinidumpModuleList* module_list = dump->GetModuleList(); |
| |
| // Put a copy of the module list into ProcessState object. This is not |
| // necessarily a MinidumpModuleList, but it adheres to the CodeModules |
| // interface, which is all that ProcessState needs to expose. |
| if (module_list) { |
| process_state->modules_ = module_list->Copy(); |
| process_state->shrunk_range_modules_ = |
| process_state->modules_->GetShrunkRangeModules(); |
| for (unsigned int i = 0; |
| i < process_state->shrunk_range_modules_.size(); |
| i++) { |
| linked_ptr<const CodeModule> module = |
| process_state->shrunk_range_modules_[i]; |
| BPLOG(INFO) << "The range for module " << module->code_file() |
| << " was shrunk down by " << HexString( |
| module->shrink_down_delta()) << " bytes. "; |
| } |
| } |
| |
| MinidumpUnloadedModuleList* unloaded_module_list = |
| dump->GetUnloadedModuleList(); |
| if (unloaded_module_list) { |
| process_state->unloaded_modules_ = unloaded_module_list->Copy(); |
| } |
| |
| MinidumpMemoryList* memory_list = dump->GetMemoryList(); |
| if (memory_list) { |
| BPLOG(INFO) << "Found " << memory_list->region_count() |
| << " memory regions."; |
| } |
| |
| MinidumpThreadList* threads = dump->GetThreadList(); |
| if (!threads) { |
| BPLOG(ERROR) << "Minidump " << dump->path() << " has no thread list"; |
| return PROCESS_ERROR_NO_THREAD_LIST; |
| } |
| |
| BPLOG(INFO) << "Minidump " << dump->path() << " has " << |
| (has_cpu_info ? "" : "no ") << "CPU info, " << |
| (has_os_info ? "" : "no ") << "OS info, " << |
| (breakpad_info != NULL ? "" : "no ") << "Breakpad info, " << |
| (exception != NULL ? "" : "no ") << "exception, " << |
| (module_list != NULL ? "" : "no ") << "module list, " << |
| (threads != NULL ? "" : "no ") << "thread list, " << |
| (has_dump_thread ? "" : "no ") << "dump thread, " << |
| (has_requesting_thread ? "" : "no ") << "requesting thread, and " << |
| (has_process_create_time ? "" : "no ") << "process create time"; |
| |
| bool interrupted = false; |
| bool found_requesting_thread = false; |
| unsigned int thread_count = threads->thread_count(); |
| |
| // Reset frame_symbolizer_ at the beginning of stackwalk for each minidump. |
| frame_symbolizer_->Reset(); |
| |
| for (unsigned int thread_index = 0; |
| thread_index < thread_count; |
| ++thread_index) { |
| char thread_string_buffer[64]; |
| snprintf(thread_string_buffer, sizeof(thread_string_buffer), "%d/%d", |
| thread_index, thread_count); |
| string thread_string = dump->path() + ":" + thread_string_buffer; |
| |
| MinidumpThread* thread = threads->GetThreadAtIndex(thread_index); |
| if (!thread) { |
| BPLOG(ERROR) << "Could not get thread for " << thread_string; |
| return PROCESS_ERROR_GETTING_THREAD; |
| } |
| |
| uint32_t thread_id; |
| if (!thread->GetThreadID(&thread_id)) { |
| BPLOG(ERROR) << "Could not get thread ID for " << thread_string; |
| return PROCESS_ERROR_GETTING_THREAD_ID; |
| } |
| |
| thread_string += " id " + HexString(thread_id); |
| BPLOG(INFO) << "Looking at thread " << thread_string; |
| |
| // If this thread is the thread that produced the minidump, don't process |
| // it. Because of the problems associated with a thread producing a |
| // dump of itself (when both its context and its stack are in flux), |
| // processing that stack wouldn't provide much useful data. |
| if (has_dump_thread && thread_id == dump_thread_id) { |
| continue; |
| } |
| |
| MinidumpContext* context = thread->GetContext(); |
| |
| if (has_requesting_thread && thread_id == requesting_thread_id) { |
| if (found_requesting_thread) { |
| // There can't be more than one requesting thread. |
| BPLOG(ERROR) << "Duplicate requesting thread: " << thread_string; |
| return PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS; |
| } |
| |
| // Use processed_state->threads_.size() instead of thread_index. |
| // thread_index points to the thread index in the minidump, which |
| // might be greater than the thread index in the threads vector if |
| // any of the minidump's threads are skipped and not placed into the |
| // processed threads vector. The thread vector's current size will |
| // be the index of the current thread when it's pushed into the |
| // vector. |
| process_state->requesting_thread_ = process_state->threads_.size(); |
| |
| found_requesting_thread = true; |
| |
| if (process_state->crashed_) { |
| // Use the exception record's context for the crashed thread, instead |
| // of the thread's own context. For the crashed thread, the thread's |
| // own context is the state inside the exception handler. Using it |
| // would not result in the expected stack trace from the time of the |
| // crash. If the exception context is invalid, however, we fall back |
| // on the thread context. |
| MinidumpContext* ctx = exception->GetContext(); |
| context = ctx ? ctx : thread->GetContext(); |
| } |
| } |
| |
| // If the memory region for the stack cannot be read using the RVA stored |
| // in the memory descriptor inside MINIDUMP_THREAD, try to locate and use |
| // a memory region (containing the stack) from the minidump memory list. |
| MinidumpMemoryRegion* thread_memory = thread->GetMemory(); |
| if (!thread_memory && memory_list) { |
| uint64_t start_stack_memory_range = thread->GetStartOfStackMemoryRange(); |
| if (start_stack_memory_range) { |
| thread_memory = memory_list->GetMemoryRegionForAddress( |
| start_stack_memory_range); |
| } |
| } |
| if (!thread_memory) { |
| BPLOG(ERROR) << "No memory region for " << thread_string; |
| } |
| |
| // Use process_state->modules_ instead of module_list, because the |
| // |modules| argument will be used to populate the |module| fields in |
| // the returned StackFrame objects, which will be placed into the |
| // returned ProcessState object. module_list's lifetime is only as |
| // long as the Minidump object: it will be deleted when this function |
| // returns. process_state->modules_ is owned by the ProcessState object |
| // (just like the StackFrame objects), and is much more suitable for this |
| // task. |
| scoped_ptr<Stackwalker> stackwalker( |
| Stackwalker::StackwalkerForCPU(process_state->system_info(), |
| context, |
| thread_memory, |
| process_state->modules_, |
| process_state->unloaded_modules_, |
| frame_symbolizer_)); |
| |
| scoped_ptr<CallStack> stack(new CallStack()); |
| if (stackwalker.get()) { |
| if (!stackwalker->Walk(stack.get(), |
| &process_state->modules_without_symbols_, |
| &process_state->modules_with_corrupt_symbols_)) { |
| BPLOG(INFO) << "Stackwalker interrupt (missing symbols?) at " |
| << thread_string; |
| interrupted = true; |
| } |
| } else { |
| // Threads with missing CPU contexts will hit this, but |
| // don't abort processing the rest of the dump just for |
| // one bad thread. |
| BPLOG(ERROR) << "No stackwalker for " << thread_string; |
| } |
| stack->set_tid(thread_id); |
| process_state->threads_.push_back(stack.release()); |
| process_state->thread_memory_regions_.push_back(thread_memory); |
| } |
| |
| if (interrupted) { |
| BPLOG(INFO) << "Processing interrupted for " << dump->path(); |
| return PROCESS_SYMBOL_SUPPLIER_INTERRUPTED; |
| } |
| |
| // If a requesting thread was indicated, it must be present. |
| if (has_requesting_thread && !found_requesting_thread) { |
| // Don't mark as an error, but invalidate the requesting thread |
| BPLOG(ERROR) << "Minidump indicated requesting thread " << |
| HexString(requesting_thread_id) << ", not found in " << |
| dump->path(); |
| process_state->requesting_thread_ = -1; |
| } |
| |
| // Exploitability defaults to EXPLOITABILITY_NOT_ANALYZED |
| process_state->exploitability_ = EXPLOITABILITY_NOT_ANALYZED; |
| |
| // If an exploitability run was requested we perform the platform specific |
| // rating. |
| if (enable_exploitability_) { |
| scoped_ptr<Exploitability> exploitability( |
| Exploitability::ExploitabilityForPlatform(dump, |
| process_state, |
| enable_objdump_)); |
| // The engine will be null if the platform is not supported |
| if (exploitability != NULL) { |
| process_state->exploitability_ = exploitability->CheckExploitability(); |
| } else { |
| process_state->exploitability_ = EXPLOITABILITY_ERR_NOENGINE; |
| } |
| } |
| |
| BPLOG(INFO) << "Processed " << dump->path(); |
| return PROCESS_OK; |
| } |
| |
| ProcessResult MinidumpProcessor::Process( |
| const string& minidump_file, ProcessState* process_state) { |
| BPLOG(INFO) << "Processing minidump in file " << minidump_file; |
| |
| Minidump dump(minidump_file); |
| if (!dump.Read()) { |
| BPLOG(ERROR) << "Minidump " << dump.path() << " could not be read"; |
| return PROCESS_ERROR_MINIDUMP_NOT_FOUND; |
| } |
| |
| return Process(&dump, process_state); |
| } |
| |
| // Returns the MDRawSystemInfo from a minidump, or NULL if system info is |
| // not available from the minidump. If system_info is non-NULL, it is used |
| // to pass back the MinidumpSystemInfo object. |
| static const MDRawSystemInfo* GetSystemInfo(Minidump* dump, |
| MinidumpSystemInfo** system_info) { |
| MinidumpSystemInfo* minidump_system_info = dump->GetSystemInfo(); |
| if (!minidump_system_info) |
| return NULL; |
| |
| if (system_info) |
| *system_info = minidump_system_info; |
| |
| return minidump_system_info->system_info(); |
| } |
| |
| static uint64_t GetAddressForArchitecture(const MDCPUArchitecture architecture, |
| size_t raw_address) |
| { |
| switch (architecture) { |
| case MD_CPU_ARCHITECTURE_X86: |
| case MD_CPU_ARCHITECTURE_MIPS: |
| case MD_CPU_ARCHITECTURE_PPC: |
| case MD_CPU_ARCHITECTURE_SHX: |
| case MD_CPU_ARCHITECTURE_ARM: |
| case MD_CPU_ARCHITECTURE_X86_WIN64: |
| // 32-bit architectures, mask the upper bits. |
| return raw_address & 0xffffffffULL; |
| |
| default: |
| // All other architectures either have 64-bit pointers or it's impossible |
| // to tell from the minidump (e.g. MSIL or SPARC) so use 64-bits anyway. |
| return raw_address; |
| } |
| } |
| |
| // Extract CPU info string from ARM-specific MDRawSystemInfo structure. |
| // raw_info: pointer to source MDRawSystemInfo. |
| // cpu_info: address of target string, cpu info text will be appended to it. |
| static void GetARMCpuInfo(const MDRawSystemInfo* raw_info, |
| string* cpu_info) { |
| assert(raw_info != NULL && cpu_info != NULL); |
| |
| // Write ARM architecture version. |
| char cpu_string[32]; |
| snprintf(cpu_string, sizeof(cpu_string), "ARMv%d", |
| raw_info->processor_level); |
| cpu_info->append(cpu_string); |
| |
| // There is no good list of implementer id values, but the following |
| // pages provide some help: |
| // http://comments.gmane.org/gmane.linux.linaro.devel/6903 |
| // http://forum.xda-developers.com/archive/index.php/t-480226.html |
| const struct { |
| uint32_t id; |
| const char* name; |
| } vendors[] = { |
| { 0x41, "ARM" }, |
| { 0x51, "Qualcomm" }, |
| { 0x56, "Marvell" }, |
| { 0x69, "Intel/Marvell" }, |
| }; |
| const struct { |
| uint32_t id; |
| const char* name; |
| } parts[] = { |
| { 0x4100c050, "Cortex-A5" }, |
| { 0x4100c080, "Cortex-A8" }, |
| { 0x4100c090, "Cortex-A9" }, |
| { 0x4100c0f0, "Cortex-A15" }, |
| { 0x4100c140, "Cortex-R4" }, |
| { 0x4100c150, "Cortex-R5" }, |
| { 0x4100b360, "ARM1136" }, |
| { 0x4100b560, "ARM1156" }, |
| { 0x4100b760, "ARM1176" }, |
| { 0x4100b020, "ARM11-MPCore" }, |
| { 0x41009260, "ARM926" }, |
| { 0x41009460, "ARM946" }, |
| { 0x41009660, "ARM966" }, |
| { 0x510006f0, "Krait" }, |
| { 0x510000f0, "Scorpion" }, |
| }; |
| |
| const struct { |
| uint32_t hwcap; |
| const char* name; |
| } features[] = { |
| { MD_CPU_ARM_ELF_HWCAP_SWP, "swp" }, |
| { MD_CPU_ARM_ELF_HWCAP_HALF, "half" }, |
| { MD_CPU_ARM_ELF_HWCAP_THUMB, "thumb" }, |
| { MD_CPU_ARM_ELF_HWCAP_26BIT, "26bit" }, |
| { MD_CPU_ARM_ELF_HWCAP_FAST_MULT, "fastmult" }, |
| { MD_CPU_ARM_ELF_HWCAP_FPA, "fpa" }, |
| { MD_CPU_ARM_ELF_HWCAP_VFP, "vfpv2" }, |
| { MD_CPU_ARM_ELF_HWCAP_EDSP, "edsp" }, |
| { MD_CPU_ARM_ELF_HWCAP_JAVA, "java" }, |
| { MD_CPU_ARM_ELF_HWCAP_IWMMXT, "iwmmxt" }, |
| { MD_CPU_ARM_ELF_HWCAP_CRUNCH, "crunch" }, |
| { MD_CPU_ARM_ELF_HWCAP_THUMBEE, "thumbee" }, |
| { MD_CPU_ARM_ELF_HWCAP_NEON, "neon" }, |
| { MD_CPU_ARM_ELF_HWCAP_VFPv3, "vfpv3" }, |
| { MD_CPU_ARM_ELF_HWCAP_VFPv3D16, "vfpv3d16" }, |
| { MD_CPU_ARM_ELF_HWCAP_TLS, "tls" }, |
| { MD_CPU_ARM_ELF_HWCAP_VFPv4, "vfpv4" }, |
| { MD_CPU_ARM_ELF_HWCAP_IDIVA, "idiva" }, |
| { MD_CPU_ARM_ELF_HWCAP_IDIVT, "idivt" }, |
| }; |
| |
| uint32_t cpuid = raw_info->cpu.arm_cpu_info.cpuid; |
| if (cpuid != 0) { |
| // Extract vendor name from CPUID |
| const char* vendor = NULL; |
| uint32_t vendor_id = (cpuid >> 24) & 0xff; |
| for (size_t i = 0; i < sizeof(vendors)/sizeof(vendors[0]); ++i) { |
| if (vendors[i].id == vendor_id) { |
| vendor = vendors[i].name; |
| break; |
| } |
| } |
| cpu_info->append(" "); |
| if (vendor) { |
| cpu_info->append(vendor); |
| } else { |
| snprintf(cpu_string, sizeof(cpu_string), "vendor(0x%x)", vendor_id); |
| cpu_info->append(cpu_string); |
| } |
| |
| // Extract part name from CPUID |
| uint32_t part_id = (cpuid & 0xff00fff0); |
| const char* part = NULL; |
| for (size_t i = 0; i < sizeof(parts)/sizeof(parts[0]); ++i) { |
| if (parts[i].id == part_id) { |
| part = parts[i].name; |
| break; |
| } |
| } |
| cpu_info->append(" "); |
| if (part != NULL) { |
| cpu_info->append(part); |
| } else { |
| snprintf(cpu_string, sizeof(cpu_string), "part(0x%x)", part_id); |
| cpu_info->append(cpu_string); |
| } |
| } |
| uint32_t elf_hwcaps = raw_info->cpu.arm_cpu_info.elf_hwcaps; |
| if (elf_hwcaps != 0) { |
| cpu_info->append(" features: "); |
| const char* comma = ""; |
| for (size_t i = 0; i < sizeof(features)/sizeof(features[0]); ++i) { |
| if (elf_hwcaps & features[i].hwcap) { |
| cpu_info->append(comma); |
| cpu_info->append(features[i].name); |
| comma = ","; |
| } |
| } |
| } |
| } |
| |
| // static |
| bool MinidumpProcessor::GetCPUInfo(Minidump* dump, SystemInfo* info) { |
| assert(dump); |
| assert(info); |
| |
| info->cpu.clear(); |
| info->cpu_info.clear(); |
| |
| MinidumpSystemInfo* system_info; |
| const MDRawSystemInfo* raw_system_info = GetSystemInfo(dump, &system_info); |
| if (!raw_system_info) |
| return false; |
| |
| switch (raw_system_info->processor_architecture) { |
| case MD_CPU_ARCHITECTURE_X86: |
| case MD_CPU_ARCHITECTURE_AMD64: { |
| if (raw_system_info->processor_architecture == |
| MD_CPU_ARCHITECTURE_X86) |
| info->cpu = "x86"; |
| else |
| info->cpu = "amd64"; |
| |
| const string* cpu_vendor = system_info->GetCPUVendor(); |
| if (cpu_vendor) { |
| info->cpu_info = *cpu_vendor; |
| info->cpu_info.append(" "); |
| } |
| |
| char x86_info[36]; |
| snprintf(x86_info, sizeof(x86_info), "family %u model %u stepping %u", |
| raw_system_info->processor_level, |
| raw_system_info->processor_revision >> 8, |
| raw_system_info->processor_revision & 0xff); |
| info->cpu_info.append(x86_info); |
| break; |
| } |
| |
| case MD_CPU_ARCHITECTURE_PPC: { |
| info->cpu = "ppc"; |
| break; |
| } |
| |
| case MD_CPU_ARCHITECTURE_PPC64: { |
| info->cpu = "ppc64"; |
| break; |
| } |
| |
| case MD_CPU_ARCHITECTURE_SPARC: { |
| info->cpu = "sparc"; |
| break; |
| } |
| |
| case MD_CPU_ARCHITECTURE_ARM: { |
| info->cpu = "arm"; |
| GetARMCpuInfo(raw_system_info, &info->cpu_info); |
| break; |
| } |
| |
| case MD_CPU_ARCHITECTURE_ARM64: |
| case MD_CPU_ARCHITECTURE_ARM64_OLD: { |
| info->cpu = "arm64"; |
| break; |
| } |
| |
| case MD_CPU_ARCHITECTURE_MIPS: { |
| info->cpu = "mips"; |
| break; |
| } |
| case MD_CPU_ARCHITECTURE_MIPS64: { |
| info->cpu = "mips64"; |
| break; |
| } |
| |
| default: { |
| // Assign the numeric architecture ID into the CPU string. |
| char cpu_string[7]; |
| snprintf(cpu_string, sizeof(cpu_string), "0x%04x", |
| raw_system_info->processor_architecture); |
| info->cpu = cpu_string; |
| break; |
| } |
| } |
| |
| info->cpu_count = raw_system_info->number_of_processors; |
| |
| return true; |
| } |
| |
| // static |
| bool MinidumpProcessor::GetOSInfo(Minidump* dump, SystemInfo* info) { |
| assert(dump); |
| assert(info); |
| |
| info->os.clear(); |
| info->os_short.clear(); |
| info->os_version.clear(); |
| |
| MinidumpSystemInfo* system_info; |
| const MDRawSystemInfo* raw_system_info = GetSystemInfo(dump, &system_info); |
| if (!raw_system_info) |
| return false; |
| |
| info->os_short = system_info->GetOS(); |
| |
| switch (raw_system_info->platform_id) { |
| case MD_OS_WIN32_NT: { |
| info->os = "Windows NT"; |
| break; |
| } |
| |
| case MD_OS_WIN32_WINDOWS: { |
| info->os = "Windows"; |
| break; |
| } |
| |
| case MD_OS_MAC_OS_X: { |
| info->os = "Mac OS X"; |
| break; |
| } |
| |
| case MD_OS_IOS: { |
| info->os = "iOS"; |
| break; |
| } |
| |
| case MD_OS_LINUX: { |
| info->os = "Linux"; |
| break; |
| } |
| |
| case MD_OS_SOLARIS: { |
| info->os = "Solaris"; |
| break; |
| } |
| |
| case MD_OS_ANDROID: { |
| info->os = "Android"; |
| break; |
| } |
| |
| case MD_OS_PS3: { |
| info->os = "PS3"; |
| break; |
| } |
| |
| case MD_OS_NACL: { |
| info->os = "NaCl"; |
| break; |
| } |
| |
| case MD_OS_FUCHSIA: { |
| info->os = "Fuchsia"; |
| break; |
| } |
| |
| default: { |
| // Assign the numeric platform ID into the OS string. |
| char os_string[11]; |
| snprintf(os_string, sizeof(os_string), "0x%08x", |
| raw_system_info->platform_id); |
| info->os = os_string; |
| break; |
| } |
| } |
| |
| char os_version_string[33]; |
| snprintf(os_version_string, sizeof(os_version_string), "%u.%u.%u", |
| raw_system_info->major_version, |
| raw_system_info->minor_version, |
| raw_system_info->build_number); |
| info->os_version = os_version_string; |
| |
| const string* csd_version = system_info->GetCSDVersion(); |
| if (csd_version) { |
| info->os_version.append(" "); |
| info->os_version.append(*csd_version); |
| } |
| |
| return true; |
| } |
| |
| // static |
| bool MinidumpProcessor::GetProcessCreateTime(Minidump* dump, |
| uint32_t* process_create_time) { |
| assert(dump); |
| assert(process_create_time); |
| |
| *process_create_time = 0; |
| |
| MinidumpMiscInfo* minidump_misc_info = dump->GetMiscInfo(); |
| if (!minidump_misc_info) { |
| return false; |
| } |
| |
| const MDRawMiscInfo* md_raw_misc_info = minidump_misc_info->misc_info(); |
| if (!md_raw_misc_info) { |
| return false; |
| } |
| |
| if (!(md_raw_misc_info->flags1 & MD_MISCINFO_FLAGS1_PROCESS_TIMES)) { |
| return false; |
| } |
| |
| *process_create_time = md_raw_misc_info->process_create_time; |
| return true; |
| } |
| |
| // static |
| string MinidumpProcessor::GetCrashReason(Minidump* dump, uint64_t* address) { |
| MinidumpException* exception = dump->GetException(); |
| if (!exception) |
| return ""; |
| |
| const MDRawExceptionStream* raw_exception = exception->exception(); |
| if (!raw_exception) |
| return ""; |
| |
| if (address) |
| *address = raw_exception->exception_record.exception_address; |
| |
| // The reason value is OS-specific and possibly CPU-specific. Set up |
| // sensible numeric defaults for the reason string in case we can't |
| // map the codes to a string (because there's no system info, or because |
| // it's an unrecognized platform, or because it's an unrecognized code.) |
| char reason_string[24]; |
| char flags_string[11]; |
| uint32_t exception_code = raw_exception->exception_record.exception_code; |
| uint32_t exception_flags = raw_exception->exception_record.exception_flags; |
| snprintf(flags_string, sizeof(flags_string), "0x%08x", exception_flags); |
| snprintf(reason_string, sizeof(reason_string), "0x%08x / %s", exception_code, |
| flags_string); |
| string reason = reason_string; |
| |
| const MDRawSystemInfo* raw_system_info = GetSystemInfo(dump, NULL); |
| if (!raw_system_info) |
| return reason; |
| |
| switch (raw_system_info->platform_id) { |
| case MD_OS_FUCHSIA: { |
| switch (exception_code) { |
| case MD_EXCEPTION_CODE_FUCHSIA_GENERAL: |
| reason = "GENERAL / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_FATAL_PAGE_FAULT: |
| reason = "FATAL_PAGE_FAULT / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_UNDEFINED_INSTRUCTION: |
| reason = "UNDEFINED_INSTRUCTION / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_SW_BREAKPOINT: |
| reason = "SW_BREAKPOINT / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_HW_BREAKPOINT: |
| reason = "HW_BREAKPOINT / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_UNALIGNED_ACCESS: |
| reason = "UNALIGNED_ACCESS / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_THREAD_STARTING: |
| reason = "THREAD_STARTING / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_THREAD_EXITING: |
| reason = "THREAD_EXITING / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_POLICY_ERROR: |
| reason = "POLICY_ERROR / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_CODE_FUCHSIA_PROCESS_STARTING: |
| reason = "PROCESS_STARTING / "; |
| reason.append(flags_string); |
| break; |
| default: |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| } |
| break; |
| } |
| |
| case MD_OS_MAC_OS_X: |
| case MD_OS_IOS: { |
| switch (exception_code) { |
| case MD_EXCEPTION_MAC_BAD_ACCESS: |
| reason = "EXC_BAD_ACCESS / "; |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_INVALID_ADDRESS: |
| reason.append("KERN_INVALID_ADDRESS"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PROTECTION_FAILURE: |
| reason.append("KERN_PROTECTION_FAILURE"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_NO_ACCESS: |
| reason.append("KERN_NO_ACCESS"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_MEMORY_FAILURE: |
| reason.append("KERN_MEMORY_FAILURE"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_MEMORY_ERROR: |
| reason.append("KERN_MEMORY_ERROR"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_CODESIGN_ERROR: |
| reason.append("KERN_CODESIGN_ERROR"); |
| break; |
| default: |
| // arm and ppc overlap |
| if (raw_system_info->processor_architecture == |
| MD_CPU_ARCHITECTURE_ARM || |
| raw_system_info->processor_architecture == |
| MD_CPU_ARCHITECTURE_ARM64_OLD) { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_ARM_DA_ALIGN: |
| reason.append("EXC_ARM_DA_ALIGN"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_ARM_DA_DEBUG: |
| reason.append("EXC_ARM_DA_DEBUG"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| } else if (raw_system_info->processor_architecture == |
| MD_CPU_ARCHITECTURE_PPC) { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_PPC_VM_PROT_READ: |
| reason.append("EXC_PPC_VM_PROT_READ"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_BADSPACE: |
| reason.append("EXC_PPC_BADSPACE"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_UNALIGNED: |
| reason.append("EXC_PPC_UNALIGNED"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| } else if (raw_system_info->processor_architecture == |
| MD_CPU_ARCHITECTURE_X86 || |
| raw_system_info->processor_architecture == |
| MD_CPU_ARCHITECTURE_AMD64) { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_X86_GENERAL_PROTECTION_FAULT: |
| reason.append("EXC_I386_GPFLT"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| } else { |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| } |
| break; |
| } |
| break; |
| case MD_EXCEPTION_MAC_BAD_INSTRUCTION: |
| reason = "EXC_BAD_INSTRUCTION / "; |
| switch (raw_system_info->processor_architecture) { |
| case MD_CPU_ARCHITECTURE_ARM: |
| case MD_CPU_ARCHITECTURE_ARM64_OLD: { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_ARM_UNDEFINED: |
| reason.append("EXC_ARM_UNDEFINED"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| case MD_CPU_ARCHITECTURE_PPC: { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_PPC_INVALID_SYSCALL: |
| reason.append("EXC_PPC_INVALID_SYSCALL"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_UNIMPLEMENTED_INSTRUCTION: |
| reason.append("EXC_PPC_UNIPL_INST"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_PRIVILEGED_INSTRUCTION: |
| reason.append("EXC_PPC_PRIVINST"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_PRIVILEGED_REGISTER: |
| reason.append("EXC_PPC_PRIVREG"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_TRACE: |
| reason.append("EXC_PPC_TRACE"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_PERFORMANCE_MONITOR: |
| reason.append("EXC_PPC_PERFMON"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| case MD_CPU_ARCHITECTURE_AMD64: |
| case MD_CPU_ARCHITECTURE_X86: { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_X86_INVALID_OPERATION: |
| reason.append("EXC_I386_INVOP"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_INVALID_TASK_STATE_SEGMENT: |
| reason.append("EXC_I386_INVTSSFLT"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_SEGMENT_NOT_PRESENT: |
| reason.append("EXC_I386_SEGNPFLT"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_STACK_FAULT: |
| reason.append("EXC_I386_STKFLT"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_GENERAL_PROTECTION_FAULT: |
| reason.append("EXC_I386_GPFLT"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_ALIGNMENT_FAULT: |
| reason.append("EXC_I386_ALIGNFLT"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| case MD_EXCEPTION_MAC_ARITHMETIC: |
| reason = "EXC_ARITHMETIC / "; |
| switch (raw_system_info->processor_architecture) { |
| case MD_CPU_ARCHITECTURE_PPC: { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_PPC_OVERFLOW: |
| reason.append("EXC_PPC_OVERFLOW"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_ZERO_DIVIDE: |
| reason.append("EXC_PPC_ZERO_DIVIDE"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_FLOAT_INEXACT: |
| reason.append("EXC_FLT_INEXACT"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_FLOAT_ZERO_DIVIDE: |
| reason.append("EXC_PPC_FLT_ZERO_DIVIDE"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_FLOAT_UNDERFLOW: |
| reason.append("EXC_PPC_FLT_UNDERFLOW"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_FLOAT_OVERFLOW: |
| reason.append("EXC_PPC_FLT_OVERFLOW"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_FLOAT_NOT_A_NUMBER: |
| reason.append("EXC_PPC_FLT_NOT_A_NUMBER"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_NO_EMULATION: |
| reason.append("EXC_PPC_NOEMULATION"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_ALTIVEC_ASSIST: |
| reason.append("EXC_PPC_ALTIVECASSIST"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| case MD_CPU_ARCHITECTURE_AMD64: |
| case MD_CPU_ARCHITECTURE_X86: { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_X86_DIV: |
| reason.append("EXC_I386_DIV"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_INTO: |
| reason.append("EXC_I386_INTO"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_NOEXT: |
| reason.append("EXC_I386_NOEXT"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_EXTOVR: |
| reason.append("EXC_I386_EXTOVR"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_EXTERR: |
| reason.append("EXC_I386_EXTERR"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_EMERR: |
| reason.append("EXC_I386_EMERR"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_BOUND: |
| reason.append("EXC_I386_BOUND"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_SSEEXTERR: |
| reason.append("EXC_I386_SSEEXTERR"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| case MD_EXCEPTION_MAC_EMULATION: |
| reason = "EXC_EMULATION / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_MAC_SOFTWARE: |
| reason = "EXC_SOFTWARE / "; |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_ABORT: |
| reason.append("SIGABRT"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_NS_EXCEPTION: |
| reason.append("UNCAUGHT_NS_EXCEPTION"); |
| break; |
| // These are ppc only but shouldn't be a problem as they're |
| // unused on x86 |
| case MD_EXCEPTION_CODE_MAC_PPC_TRAP: |
| reason.append("EXC_PPC_TRAP"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_PPC_MIGRATE: |
| reason.append("EXC_PPC_MIGRATE"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| case MD_EXCEPTION_MAC_BREAKPOINT: |
| reason = "EXC_BREAKPOINT / "; |
| switch (raw_system_info->processor_architecture) { |
| case MD_CPU_ARCHITECTURE_ARM: |
| case MD_CPU_ARCHITECTURE_ARM64_OLD: { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_ARM_DA_ALIGN: |
| reason.append("EXC_ARM_DA_ALIGN"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_ARM_DA_DEBUG: |
| reason.append("EXC_ARM_DA_DEBUG"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_ARM_BREAKPOINT: |
| reason.append("EXC_ARM_BREAKPOINT"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| case MD_CPU_ARCHITECTURE_PPC: { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_PPC_BREAKPOINT: |
| reason.append("EXC_PPC_BREAKPOINT"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| case MD_CPU_ARCHITECTURE_AMD64: |
| case MD_CPU_ARCHITECTURE_X86: { |
| switch (exception_flags) { |
| case MD_EXCEPTION_CODE_MAC_X86_SGL: |
| reason.append("EXC_I386_SGL"); |
| break; |
| case MD_EXCEPTION_CODE_MAC_X86_BPT: |
| reason.append("EXC_I386_BPT"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| case MD_EXCEPTION_MAC_SYSCALL: |
| reason = "EXC_SYSCALL / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_MAC_MACH_SYSCALL: |
| reason = "EXC_MACH_SYSCALL / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_MAC_RPC_ALERT: |
| reason = "EXC_RPC_ALERT / "; |
| reason.append(flags_string); |
| break; |
| case MD_EXCEPTION_MAC_SIMULATED: |
| reason = "Simulated Exception"; |
| break; |
| } |
| break; |
| } |
| |
| case MD_OS_WIN32_NT: |
| case MD_OS_WIN32_WINDOWS: { |
| switch (exception_code) { |
| case MD_EXCEPTION_CODE_WIN_CONTROL_C: |
| reason = "DBG_CONTROL_C"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_GUARD_PAGE_VIOLATION: |
| reason = "EXCEPTION_GUARD_PAGE"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_DATATYPE_MISALIGNMENT: |
| reason = "EXCEPTION_DATATYPE_MISALIGNMENT"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_BREAKPOINT: |
| reason = "EXCEPTION_BREAKPOINT"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_SINGLE_STEP: |
| reason = "EXCEPTION_SINGLE_STEP"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_ACCESS_VIOLATION: |
| // For EXCEPTION_ACCESS_VIOLATION, Windows puts the address that |
| // caused the fault in exception_information[1]. |
| // exception_information[0] is 0 if the violation was caused by |
| // an attempt to read data, 1 if it was an attempt to write data, |
| // and 8 if this was a data execution violation. |
| // This information is useful in addition to the code address, which |
| // will be present in the crash thread's instruction field anyway. |
| if (raw_exception->exception_record.number_parameters >= 1) { |
| MDAccessViolationTypeWin av_type = |
| static_cast<MDAccessViolationTypeWin> |
| (raw_exception->exception_record.exception_information[0]); |
| switch (av_type) { |
| case MD_ACCESS_VIOLATION_WIN_READ: |
| reason = "EXCEPTION_ACCESS_VIOLATION_READ"; |
| break; |
| case MD_ACCESS_VIOLATION_WIN_WRITE: |
| reason = "EXCEPTION_ACCESS_VIOLATION_WRITE"; |
| break; |
| case MD_ACCESS_VIOLATION_WIN_EXEC: |
| reason = "EXCEPTION_ACCESS_VIOLATION_EXEC"; |
| break; |
| default: |
| reason = "EXCEPTION_ACCESS_VIOLATION"; |
| break; |
| } |
| } else { |
| reason = "EXCEPTION_ACCESS_VIOLATION"; |
| } |
| if (address && |
| raw_exception->exception_record.number_parameters >= 2) { |
| *address = |
| raw_exception->exception_record.exception_information[1]; |
| } |
| break; |
| case MD_EXCEPTION_CODE_WIN_IN_PAGE_ERROR: |
| // For EXCEPTION_IN_PAGE_ERROR, Windows puts the address that |
| // caused the fault in exception_information[1]. |
| // exception_information[0] is 0 if the violation was caused by |
| // an attempt to read data, 1 if it was an attempt to write data, |
| // and 8 if this was a data execution violation. |
| // exception_information[2] contains the underlying NTSTATUS code, |
| // which is the explanation for why this error occured. |
| // This information is useful in addition to the code address, which |
| // will be present in the crash thread's instruction field anyway. |
| if (raw_exception->exception_record.number_parameters >= 1) { |
| MDInPageErrorTypeWin av_type = |
| static_cast<MDInPageErrorTypeWin> |
| (raw_exception->exception_record.exception_information[0]); |
| switch (av_type) { |
| case MD_IN_PAGE_ERROR_WIN_READ: |
| reason = "EXCEPTION_IN_PAGE_ERROR_READ"; |
| break; |
| case MD_IN_PAGE_ERROR_WIN_WRITE: |
| reason = "EXCEPTION_IN_PAGE_ERROR_WRITE"; |
| break; |
| case MD_IN_PAGE_ERROR_WIN_EXEC: |
| reason = "EXCEPTION_IN_PAGE_ERROR_EXEC"; |
| break; |
| default: |
| reason = "EXCEPTION_IN_PAGE_ERROR"; |
| break; |
| } |
| } else { |
| reason = "EXCEPTION_IN_PAGE_ERROR"; |
| } |
| if (address && |
| raw_exception->exception_record.number_parameters >= 2) { |
| *address = |
| raw_exception->exception_record.exception_information[1]; |
| } |
| if (raw_exception->exception_record.number_parameters >= 3) { |
| uint32_t ntstatus = |
| static_cast<uint32_t> |
| (raw_exception->exception_record.exception_information[2]); |
| reason.append(" / "); |
| reason.append(NTStatusToString(ntstatus)); |
| } |
| break; |
| case MD_EXCEPTION_CODE_WIN_INVALID_HANDLE: |
| reason = "EXCEPTION_INVALID_HANDLE"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_ILLEGAL_INSTRUCTION: |
| reason = "EXCEPTION_ILLEGAL_INSTRUCTION"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_NONCONTINUABLE_EXCEPTION: |
| reason = "EXCEPTION_NONCONTINUABLE_EXCEPTION"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_INVALID_DISPOSITION: |
| reason = "EXCEPTION_INVALID_DISPOSITION"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_ARRAY_BOUNDS_EXCEEDED: |
| reason = "EXCEPTION_BOUNDS_EXCEEDED"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_FLOAT_DENORMAL_OPERAND: |
| reason = "EXCEPTION_FLT_DENORMAL_OPERAND"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_FLOAT_DIVIDE_BY_ZERO: |
| reason = "EXCEPTION_FLT_DIVIDE_BY_ZERO"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_FLOAT_INEXACT_RESULT: |
| reason = "EXCEPTION_FLT_INEXACT_RESULT"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_FLOAT_INVALID_OPERATION: |
| reason = "EXCEPTION_FLT_INVALID_OPERATION"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_FLOAT_OVERFLOW: |
| reason = "EXCEPTION_FLT_OVERFLOW"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_FLOAT_STACK_CHECK: |
| reason = "EXCEPTION_FLT_STACK_CHECK"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_FLOAT_UNDERFLOW: |
| reason = "EXCEPTION_FLT_UNDERFLOW"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_INTEGER_DIVIDE_BY_ZERO: |
| reason = "EXCEPTION_INT_DIVIDE_BY_ZERO"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_INTEGER_OVERFLOW: |
| reason = "EXCEPTION_INT_OVERFLOW"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_PRIVILEGED_INSTRUCTION: |
| reason = "EXCEPTION_PRIV_INSTRUCTION"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_STACK_OVERFLOW: |
| reason = "EXCEPTION_STACK_OVERFLOW"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_BAD_FUNCTION_TABLE: |
| reason = "EXCEPTION_BAD_FUNCTION_TABLE"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK: |
| reason = "EXCEPTION_POSSIBLE_DEADLOCK"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_STACK_BUFFER_OVERRUN: |
| reason = "EXCEPTION_STACK_BUFFER_OVERRUN"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_HEAP_CORRUPTION: |
| reason = "EXCEPTION_HEAP_CORRUPTION"; |
| break; |
| case MD_EXCEPTION_OUT_OF_MEMORY: |
| reason = "Out of Memory"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_UNHANDLED_CPP_EXCEPTION: |
| reason = "Unhandled C++ Exception"; |
| break; |
| case MD_EXCEPTION_CODE_WIN_SIMULATED: |
| reason = "Simulated Exception"; |
| break; |
| default: |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| |
| case MD_OS_ANDROID: |
| case MD_OS_LINUX: { |
| switch (exception_code) { |
| case MD_EXCEPTION_CODE_LIN_SIGHUP: |
| reason = "SIGHUP"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGINT: |
| reason = "SIGINT"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGQUIT: |
| reason = "SIGQUIT"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGILL: |
| reason = "SIGILL / "; |
| switch (exception_flags) { |
| case MD_EXCEPTION_FLAG_LIN_ILL_ILLOPC: |
| reason.append("ILL_ILLOPC"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_ILL_ILLOPN: |
| reason.append("ILL_ILLOPN"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_ILL_ILLADR: |
| reason.append("ILL_ILLADR"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_ILL_ILLTRP: |
| reason.append("ILL_ILLTRP"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_ILL_PRVOPC: |
| reason.append("ILL_PRVOPC"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_ILL_PRVREG: |
| reason.append("ILL_PRVREG"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_ILL_COPROC: |
| reason.append("ILL_COPROC"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_ILL_BADSTK: |
| reason.append("ILL_BADSTK"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGTRAP: |
| reason = "SIGTRAP"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGABRT: |
| reason = "SIGABRT"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGBUS: |
| reason = "SIGBUS / "; |
| switch (exception_flags) { |
| case MD_EXCEPTION_FLAG_LIN_BUS_ADRALN: |
| reason.append("BUS_ADRALN"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_BUS_ADRERR: |
| reason.append("BUS_ADRERR"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_BUS_OBJERR: |
| reason.append("BUS_OBJERR"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_BUS_MCEERR_AR: |
| reason.append("BUS_MCEERR_AR"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_BUS_MCEERR_AO: |
| reason.append("BUS_MCEERR_AO"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGFPE: |
| reason = "SIGFPE / "; |
| switch (exception_flags) { |
| case MD_EXCEPTION_FLAG_LIN_FPE_INTDIV: |
| reason.append("FPE_INTDIV"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_FPE_INTOVF: |
| reason.append("FPE_INTOVF"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_FPE_FLTDIV: |
| reason.append("FPE_FLTDIV"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_FPE_FLTOVF: |
| reason.append("FPE_FLTOVF"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_FPE_FLTUND: |
| reason.append("FPE_FLTUND"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_FPE_FLTRES: |
| reason.append("FPE_FLTRES"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_FPE_FLTINV: |
| reason.append("FPE_FLTINV"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_FPE_FLTSUB: |
| reason.append("FPE_FLTSUB"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGKILL: |
| reason = "SIGKILL"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGUSR1: |
| reason = "SIGUSR1"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGSEGV: |
| reason = "SIGSEGV /"; |
| switch (exception_flags) { |
| case MD_EXCEPTION_FLAG_LIN_SEGV_MAPERR: |
| reason.append("SEGV_MAPERR"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_SEGV_ACCERR: |
| reason.append("SEGV_ACCERR"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_SEGV_BNDERR: |
| reason.append("SEGV_BNDERR"); |
| break; |
| case MD_EXCEPTION_FLAG_LIN_SEGV_PKUERR: |
| reason.append("SEGV_PKUERR"); |
| break; |
| default: |
| reason.append(flags_string); |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGUSR2: |
| reason = "SIGUSR2"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGPIPE: |
| reason = "SIGPIPE"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGALRM: |
| reason = "SIGALRM"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGTERM: |
| reason = "SIGTERM"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGSTKFLT: |
| reason = "SIGSTKFLT"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGCHLD: |
| reason = "SIGCHLD"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGCONT: |
| reason = "SIGCONT"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGSTOP: |
| reason = "SIGSTOP"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGTSTP: |
| reason = "SIGTSTP"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGTTIN: |
| reason = "SIGTTIN"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGTTOU: |
| reason = "SIGTTOU"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGURG: |
| reason = "SIGURG"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGXCPU: |
| reason = "SIGXCPU"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGXFSZ: |
| reason = "SIGXFSZ"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGVTALRM: |
| reason = "SIGVTALRM"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGPROF: |
| reason = "SIGPROF"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGWINCH: |
| reason = "SIGWINCH"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGIO: |
| reason = "SIGIO"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGPWR: |
| reason = "SIGPWR"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_SIGSYS: |
| reason = "SIGSYS"; |
| break; |
| case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: |
| reason = "DUMP_REQUESTED"; |
| break; |
| default: |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| |
| case MD_OS_SOLARIS: { |
| switch (exception_code) { |
| case MD_EXCEPTION_CODE_SOL_SIGHUP: |
| reason = "SIGHUP"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGINT: |
| reason = "SIGINT"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGQUIT: |
| reason = "SIGQUIT"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGILL: |
| reason = "SIGILL"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGTRAP: |
| reason = "SIGTRAP"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGIOT: |
| reason = "SIGIOT | SIGABRT"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGEMT: |
| reason = "SIGEMT"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGFPE: |
| reason = "SIGFPE"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGKILL: |
| reason = "SIGKILL"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGBUS: |
| reason = "SIGBUS"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGSEGV: |
| reason = "SIGSEGV"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGSYS: |
| reason = "SIGSYS"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGPIPE: |
| reason = "SIGPIPE"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGALRM: |
| reason = "SIGALRM"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGTERM: |
| reason = "SIGTERM"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGUSR1: |
| reason = "SIGUSR1"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGUSR2: |
| reason = "SIGUSR2"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGCLD: |
| reason = "SIGCLD | SIGCHLD"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGPWR: |
| reason = "SIGPWR"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGWINCH: |
| reason = "SIGWINCH"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGURG: |
| reason = "SIGURG"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGPOLL: |
| reason = "SIGPOLL | SIGIO"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGSTOP: |
| reason = "SIGSTOP"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGTSTP: |
| reason = "SIGTSTP"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGCONT: |
| reason = "SIGCONT"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGTTIN: |
| reason = "SIGTTIN"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGTTOU: |
| reason = "SIGTTOU"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGVTALRM: |
| reason = "SIGVTALRM"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGPROF: |
| reason = "SIGPROF"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGXCPU: |
| reason = "SIGXCPU"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGXFSZ: |
| reason = "SIGXFSZ"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGWAITING: |
| reason = "SIGWAITING"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGLWP: |
| reason = "SIGLWP"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGFREEZE: |
| reason = "SIGFREEZE"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGTHAW: |
| reason = "SIGTHAW"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGCANCEL: |
| reason = "SIGCANCEL"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGLOST: |
| reason = "SIGLOST"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGXRES: |
| reason = "SIGXRES"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGJVM1: |
| reason = "SIGJVM1"; |
| break; |
| case MD_EXCEPTION_CODE_SOL_SIGJVM2: |
| reason = "SIGJVM2"; |
| break; |
| default: |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| break; |
| } |
| |
| case MD_OS_PS3: { |
| switch (exception_code) { |
| case MD_EXCEPTION_CODE_PS3_UNKNOWN: |
| reason = "UNKNOWN"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_TRAP_EXCEP: |
| reason = "TRAP_EXCEP"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_PRIV_INSTR: |
| reason = "PRIV_INSTR"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_ILLEGAL_INSTR: |
| reason = "ILLEGAL_INSTR"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_INSTR_STORAGE: |
| reason = "INSTR_STORAGE"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_INSTR_SEGMENT: |
| reason = "INSTR_SEGMENT"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_DATA_STORAGE: |
| reason = "DATA_STORAGE"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_DATA_SEGMENT: |
| reason = "DATA_SEGMENT"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_FLOAT_POINT: |
| reason = "FLOAT_POINT"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_DABR_MATCH: |
| reason = "DABR_MATCH"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_ALIGN_EXCEP: |
| reason = "ALIGN_EXCEP"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_MEMORY_ACCESS: |
| reason = "MEMORY_ACCESS"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_ALIGN: |
| reason = "COPRO_ALIGN"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_INVALID_COM: |
| reason = "COPRO_INVALID_COM"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_ERR: |
| reason = "COPRO_ERR"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_FIR: |
| reason = "COPRO_FIR"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_DATA_SEGMENT: |
| reason = "COPRO_DATA_SEGMENT"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_DATA_STORAGE: |
| reason = "COPRO_DATA_STORAGE"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_STOP_INSTR: |
| reason = "COPRO_STOP_INSTR"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_HALT_INSTR: |
| reason = "COPRO_HALT_INSTR"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_HALTINST_UNKNOWN: |
| reason = "COPRO_HALTINSTR_UNKNOWN"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_COPRO_MEMORY_ACCESS: |
| reason = "COPRO_MEMORY_ACCESS"; |
| break; |
| case MD_EXCEPTION_CODE_PS3_GRAPHIC: |
| reason = "GRAPHIC"; |
| break; |
| default: |
| BPLOG(INFO) << "Unknown exception reason "<< reason; |
| break; |
| } |
| break; |
| } |
| |
| default: { |
| BPLOG(INFO) << "Unknown exception reason " << reason; |
| break; |
| } |
| } |
| |
| if (address) { |
| *address = GetAddressForArchitecture( |
| static_cast<MDCPUArchitecture>(raw_system_info->processor_architecture), |
| *address); |
| } |
| |
| return reason; |
| } |
| |
| // static |
| string MinidumpProcessor::GetAssertion(Minidump* dump) { |
| MinidumpAssertion* assertion = dump->GetAssertion(); |
| if (!assertion) |
| return ""; |
| |
| const MDRawAssertionInfo* raw_assertion = assertion->assertion(); |
| if (!raw_assertion) |
| return ""; |
| |
| string assertion_string; |
| switch (raw_assertion->type) { |
| case MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER: |
| assertion_string = "Invalid parameter passed to library function"; |
| break; |
| case MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL: |
| assertion_string = "Pure virtual function called"; |
| break; |
| default: { |
| char assertion_type[32]; |
| snprintf(assertion_type, sizeof(assertion_type), |
| "0x%08x", raw_assertion->type); |
| assertion_string = "Unknown assertion type "; |
| assertion_string += assertion_type; |
| break; |
| } |
| } |
| |
| string expression = assertion->expression(); |
| if (!expression.empty()) { |
| assertion_string.append(" " + expression); |
| } |
| |
| string function = assertion->function(); |
| if (!function.empty()) { |
| assertion_string.append(" in function " + function); |
| } |
| |
| string file = assertion->file(); |
| if (!file.empty()) { |
| assertion_string.append(", in file " + file); |
| } |
| |
| if (raw_assertion->line != 0) { |
| char assertion_line[32]; |
| snprintf(assertion_line, sizeof(assertion_line), "%u", raw_assertion->line); |
| assertion_string.append(" at line "); |
| assertion_string.append(assertion_line); |
| } |
| |
| return assertion_string; |
| } |
| |
| } // namespace google_breakpad |