| // 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 <algorithm> |
| #include <cstdio> |
| |
| #include <mach/host_info.h> |
| #include <mach/machine.h> |
| #include <mach/vm_statistics.h> |
| #include <mach-o/dyld.h> |
| #include <mach-o/loader.h> |
| #include <sys/sysctl.h> |
| #include <sys/resource.h> |
| |
| #include <CoreFoundation/CoreFoundation.h> |
| |
| #include "client/mac/handler/minidump_generator.h" |
| |
| #if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT) |
| #include <mach/arm/thread_status.h> |
| #endif |
| #ifdef HAS_PPC_SUPPORT |
| #include <mach/ppc/thread_status.h> |
| #endif |
| #ifdef HAS_X86_SUPPORT |
| #include <mach/i386/thread_status.h> |
| #endif |
| |
| #include "client/minidump_file_writer-inl.h" |
| #include "common/mac/file_id.h" |
| #include "common/mac/macho_id.h" |
| #include "common/mac/string_utilities.h" |
| |
| using MacStringUtils::ConvertToString; |
| using MacStringUtils::IntegerValueAtIndex; |
| |
| namespace google_breakpad { |
| |
| #if defined(__LP64__) && __LP64__ |
| #define LC_SEGMENT_ARCH LC_SEGMENT_64 |
| #else |
| #define LC_SEGMENT_ARCH LC_SEGMENT |
| #endif |
| |
| // constructor when generating from within the crashed process |
| MinidumpGenerator::MinidumpGenerator() |
| : writer_(), |
| exception_type_(0), |
| exception_code_(0), |
| exception_subcode_(0), |
| exception_thread_(0), |
| crashing_task_(mach_task_self()), |
| handler_thread_(mach_thread_self()), |
| cpu_type_(DynamicImages::GetNativeCPUType()), |
| task_context_(NULL), |
| dynamic_images_(NULL), |
| memory_blocks_(&allocator_) { |
| GatherSystemInformation(); |
| } |
| |
| // constructor when generating from a different process than the |
| // crashed process |
| MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task, |
| mach_port_t handler_thread) |
| : writer_(), |
| exception_type_(0), |
| exception_code_(0), |
| exception_subcode_(0), |
| exception_thread_(0), |
| crashing_task_(crashing_task), |
| handler_thread_(handler_thread), |
| cpu_type_(DynamicImages::GetNativeCPUType()), |
| task_context_(NULL), |
| dynamic_images_(NULL), |
| memory_blocks_(&allocator_) { |
| if (crashing_task != mach_task_self()) { |
| dynamic_images_ = new DynamicImages(crashing_task_); |
| cpu_type_ = dynamic_images_->GetCPUType(); |
| } else { |
| dynamic_images_ = NULL; |
| cpu_type_ = DynamicImages::GetNativeCPUType(); |
| } |
| |
| GatherSystemInformation(); |
| } |
| |
| MinidumpGenerator::~MinidumpGenerator() { |
| delete dynamic_images_; |
| } |
| |
| char MinidumpGenerator::build_string_[16]; |
| int MinidumpGenerator::os_major_version_ = 0; |
| int MinidumpGenerator::os_minor_version_ = 0; |
| int MinidumpGenerator::os_build_number_ = 0; |
| |
| // static |
| void MinidumpGenerator::GatherSystemInformation() { |
| // If this is non-zero, then we've already gathered the information |
| if (os_major_version_) |
| return; |
| |
| // This code extracts the version and build information from the OS |
| CFStringRef vers_path = |
| CFSTR("/System/Library/CoreServices/SystemVersion.plist"); |
| CFURLRef sys_vers = |
| CFURLCreateWithFileSystemPath(NULL, |
| vers_path, |
| kCFURLPOSIXPathStyle, |
| false); |
| CFReadStreamRef read_stream = CFReadStreamCreateWithFile(NULL, sys_vers); |
| CFRelease(sys_vers); |
| if (!read_stream) { |
| return; |
| } |
| if (!CFReadStreamOpen(read_stream)) { |
| CFRelease(read_stream); |
| return; |
| } |
| CFMutableDataRef data = NULL; |
| while (true) { |
| // Actual data file tests: Mac at 480 bytes and iOS at 413 bytes. |
| const CFIndex kMaxBufferLength = 1024; |
| UInt8 data_bytes[kMaxBufferLength]; |
| CFIndex num_bytes_read = |
| CFReadStreamRead(read_stream, data_bytes, kMaxBufferLength); |
| if (num_bytes_read < 0) { |
| if (data) { |
| CFRelease(data); |
| data = NULL; |
| } |
| break; |
| } else if (num_bytes_read == 0) { |
| break; |
| } else if (!data) { |
| data = CFDataCreateMutable(NULL, 0); |
| } |
| CFDataAppendBytes(data, data_bytes, num_bytes_read); |
| } |
| CFReadStreamClose(read_stream); |
| CFRelease(read_stream); |
| if (!data) { |
| return; |
| } |
| CFDictionaryRef list = |
| static_cast<CFDictionaryRef>(CFPropertyListCreateWithData( |
| NULL, data, kCFPropertyListImmutable, NULL, NULL)); |
| CFRelease(data); |
| if (!list) { |
| return; |
| } |
| CFStringRef build_version = static_cast<CFStringRef> |
| (CFDictionaryGetValue(list, CFSTR("ProductBuildVersion"))); |
| CFStringRef product_version = static_cast<CFStringRef> |
| (CFDictionaryGetValue(list, CFSTR("ProductVersion"))); |
| string build_str = ConvertToString(build_version); |
| string product_str = ConvertToString(product_version); |
| |
| CFRelease(list); |
| |
| strlcpy(build_string_, build_str.c_str(), sizeof(build_string_)); |
| |
| // Parse the string that looks like "10.4.8" |
| os_major_version_ = IntegerValueAtIndex(product_str, 0); |
| os_minor_version_ = IntegerValueAtIndex(product_str, 1); |
| os_build_number_ = IntegerValueAtIndex(product_str, 2); |
| } |
| |
| void MinidumpGenerator::SetTaskContext(breakpad_ucontext_t *task_context) { |
| task_context_ = task_context; |
| } |
| |
| string MinidumpGenerator::UniqueNameInDirectory(const string &dir, |
| string *unique_name) { |
| CFUUIDRef uuid = CFUUIDCreate(NULL); |
| CFStringRef uuid_cfstr = CFUUIDCreateString(NULL, uuid); |
| CFRelease(uuid); |
| string file_name(ConvertToString(uuid_cfstr)); |
| CFRelease(uuid_cfstr); |
| string path(dir); |
| |
| // Ensure that the directory (if non-empty) has a trailing slash so that |
| // we can append the file name and have a valid pathname. |
| if (!dir.empty()) { |
| if (dir.at(dir.size() - 1) != '/') |
| path.append(1, '/'); |
| } |
| |
| path.append(file_name); |
| path.append(".dmp"); |
| |
| if (unique_name) |
| *unique_name = file_name; |
| |
| return path; |
| } |
| |
| bool MinidumpGenerator::Write(const char *path) { |
| WriteStreamFN writers[] = { |
| &MinidumpGenerator::WriteThreadListStream, |
| &MinidumpGenerator::WriteMemoryListStream, |
| &MinidumpGenerator::WriteSystemInfoStream, |
| &MinidumpGenerator::WriteModuleListStream, |
| &MinidumpGenerator::WriteMiscInfoStream, |
| &MinidumpGenerator::WriteBreakpadInfoStream, |
| // Exception stream needs to be the last entry in this array as it may |
| // be omitted in the case where the minidump is written without an |
| // exception. |
| &MinidumpGenerator::WriteExceptionStream, |
| }; |
| bool result = false; |
| |
| // If opening was successful, create the header, directory, and call each |
| // writer. The destructor for the TypedMDRVAs will cause the data to be |
| // flushed. The destructor for the MinidumpFileWriter will close the file. |
| if (writer_.Open(path)) { |
| TypedMDRVA<MDRawHeader> header(&writer_); |
| TypedMDRVA<MDRawDirectory> dir(&writer_); |
| |
| if (!header.Allocate()) |
| return false; |
| |
| int writer_count = static_cast<int>(sizeof(writers) / sizeof(writers[0])); |
| |
| // If we don't have exception information, don't write out the |
| // exception stream |
| if (!exception_thread_ && !exception_type_) |
| --writer_count; |
| |
| // Add space for all writers |
| if (!dir.AllocateArray(writer_count)) |
| return false; |
| |
| MDRawHeader *header_ptr = header.get(); |
| header_ptr->signature = MD_HEADER_SIGNATURE; |
| header_ptr->version = MD_HEADER_VERSION; |
| time(reinterpret_cast<time_t *>(&(header_ptr->time_date_stamp))); |
| header_ptr->stream_count = writer_count; |
| header_ptr->stream_directory_rva = dir.position(); |
| |
| MDRawDirectory local_dir; |
| result = true; |
| for (int i = 0; (result) && (i < writer_count); ++i) { |
| result = (this->*writers[i])(&local_dir); |
| |
| if (result) |
| dir.CopyIndex(i, &local_dir); |
| } |
| } |
| return result; |
| } |
| |
| size_t MinidumpGenerator::CalculateStackSize(mach_vm_address_t start_addr) { |
| mach_vm_address_t stack_region_base = start_addr; |
| mach_vm_size_t stack_region_size; |
| natural_t nesting_level = 0; |
| vm_region_submap_info_64 submap_info; |
| mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; |
| |
| vm_region_recurse_info_t region_info; |
| region_info = reinterpret_cast<vm_region_recurse_info_t>(&submap_info); |
| |
| if (start_addr == 0) { |
| return 0; |
| } |
| |
| kern_return_t result = |
| mach_vm_region_recurse(crashing_task_, &stack_region_base, |
| &stack_region_size, &nesting_level, |
| region_info, &info_count); |
| |
| if (result != KERN_SUCCESS || start_addr < stack_region_base) { |
| // Failure or stack corruption, since mach_vm_region had to go |
| // higher in the process address space to find a valid region. |
| return 0; |
| } |
| |
| unsigned int tag = submap_info.user_tag; |
| |
| // If the user tag is VM_MEMORY_STACK, look for more readable regions with |
| // the same tag placed immediately above the computed stack region. Under |
| // some circumstances, the stack for thread 0 winds up broken up into |
| // multiple distinct abutting regions. This can happen for several reasons, |
| // including user code that calls setrlimit(RLIMIT_STACK, ...) or changes |
| // the access on stack pages by calling mprotect. |
| if (tag == VM_MEMORY_STACK) { |
| while (true) { |
| mach_vm_address_t next_region_base = stack_region_base + |
| stack_region_size; |
| mach_vm_address_t proposed_next_region_base = next_region_base; |
| mach_vm_size_t next_region_size; |
| nesting_level = 0; |
| info_count = VM_REGION_SUBMAP_INFO_COUNT_64; |
| result = mach_vm_region_recurse(crashing_task_, &next_region_base, |
| &next_region_size, &nesting_level, |
| region_info, &info_count); |
| if (result != KERN_SUCCESS || |
| next_region_base != proposed_next_region_base || |
| submap_info.user_tag != tag || |
| (submap_info.protection & VM_PROT_READ) == 0) { |
| break; |
| } |
| |
| stack_region_size += next_region_size; |
| } |
| } |
| |
| return stack_region_base + stack_region_size - start_addr; |
| } |
| |
| bool MinidumpGenerator::WriteStackFromStartAddress( |
| mach_vm_address_t start_addr, |
| MDMemoryDescriptor *stack_location) { |
| UntypedMDRVA memory(&writer_); |
| |
| bool result = false; |
| size_t size = CalculateStackSize(start_addr); |
| |
| if (size == 0) { |
| // In some situations the stack address for the thread can come back 0. |
| // In these cases we skip over the threads in question and stuff the |
| // stack with a clearly borked value. |
| start_addr = 0xDEADBEEF; |
| size = 16; |
| if (!memory.Allocate(size)) |
| return false; |
| |
| unsigned long long dummy_stack[2]; // Fill dummy stack with 16 bytes of |
| // junk. |
| dummy_stack[0] = 0xDEADBEEF; |
| dummy_stack[1] = 0xDEADBEEF; |
| |
| result = memory.Copy(dummy_stack, size); |
| } else { |
| |
| if (!memory.Allocate(size)) |
| return false; |
| |
| if (dynamic_images_) { |
| vector<uint8_t> stack_memory; |
| if (ReadTaskMemory(crashing_task_, |
| start_addr, |
| size, |
| stack_memory) != KERN_SUCCESS) { |
| return false; |
| } |
| |
| result = memory.Copy(&stack_memory[0], size); |
| } else { |
| result = memory.Copy(reinterpret_cast<const void *>(start_addr), size); |
| } |
| } |
| |
| stack_location->start_of_memory_range = start_addr; |
| stack_location->memory = memory.location(); |
| |
| return result; |
| } |
| |
| bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state, |
| MDMemoryDescriptor *stack_location) { |
| switch (cpu_type_) { |
| #ifdef HAS_ARM_SUPPORT |
| case CPU_TYPE_ARM: |
| return WriteStackARM(state, stack_location); |
| #endif |
| #ifdef HAS_ARM64_SUPPORT |
| case CPU_TYPE_ARM64: |
| return WriteStackARM64(state, stack_location); |
| #endif |
| #ifdef HAS_PPC_SUPPORT |
| case CPU_TYPE_POWERPC: |
| return WriteStackPPC(state, stack_location); |
| case CPU_TYPE_POWERPC64: |
| return WriteStackPPC64(state, stack_location); |
| #endif |
| #ifdef HAS_X86_SUPPORT |
| case CPU_TYPE_I386: |
| return WriteStackX86(state, stack_location); |
| case CPU_TYPE_X86_64: |
| return WriteStackX86_64(state, stack_location); |
| #endif |
| default: |
| return false; |
| } |
| } |
| |
| bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state, |
| MDLocationDescriptor *register_location) { |
| switch (cpu_type_) { |
| #ifdef HAS_ARM_SUPPORT |
| case CPU_TYPE_ARM: |
| return WriteContextARM(state, register_location); |
| #endif |
| #ifdef HAS_ARM64_SUPPORT |
| case CPU_TYPE_ARM64: |
| return WriteContextARM64(state, register_location); |
| #endif |
| #ifdef HAS_PPC_SUPPORT |
| case CPU_TYPE_POWERPC: |
| return WriteContextPPC(state, register_location); |
| case CPU_TYPE_POWERPC64: |
| return WriteContextPPC64(state, register_location); |
| #endif |
| #ifdef HAS_X86_SUPPORT |
| case CPU_TYPE_I386: |
| return WriteContextX86(state, register_location); |
| case CPU_TYPE_X86_64: |
| return WriteContextX86_64(state, register_location); |
| #endif |
| default: |
| return false; |
| } |
| } |
| |
| uint64_t MinidumpGenerator::CurrentPCForStack( |
| breakpad_thread_state_data_t state) { |
| switch (cpu_type_) { |
| #ifdef HAS_ARM_SUPPORT |
| case CPU_TYPE_ARM: |
| return CurrentPCForStackARM(state); |
| #endif |
| #ifdef HAS_ARM64_SUPPORT |
| case CPU_TYPE_ARM64: |
| return CurrentPCForStackARM64(state); |
| #endif |
| #ifdef HAS_PPC_SUPPORT |
| case CPU_TYPE_POWERPC: |
| return CurrentPCForStackPPC(state); |
| case CPU_TYPE_POWERPC64: |
| return CurrentPCForStackPPC64(state); |
| #endif |
| #ifdef HAS_X86_SUPPORT |
| case CPU_TYPE_I386: |
| return CurrentPCForStackX86(state); |
| case CPU_TYPE_X86_64: |
| return CurrentPCForStackX86_64(state); |
| #endif |
| default: |
| assert(0 && "Unknown CPU type!"); |
| return 0; |
| } |
| } |
| |
| #ifdef HAS_ARM_SUPPORT |
| bool MinidumpGenerator::WriteStackARM(breakpad_thread_state_data_t state, |
| MDMemoryDescriptor *stack_location) { |
| arm_thread_state_t *machine_state = |
| reinterpret_cast<arm_thread_state_t *>(state); |
| mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, sp); |
| return WriteStackFromStartAddress(start_addr, stack_location); |
| } |
| |
| uint64_t |
| MinidumpGenerator::CurrentPCForStackARM(breakpad_thread_state_data_t state) { |
| arm_thread_state_t *machine_state = |
| reinterpret_cast<arm_thread_state_t *>(state); |
| |
| return REGISTER_FROM_THREADSTATE(machine_state, pc); |
| } |
| |
| bool MinidumpGenerator::WriteContextARM(breakpad_thread_state_data_t state, |
| MDLocationDescriptor *register_location) |
| { |
| TypedMDRVA<MDRawContextARM> context(&writer_); |
| arm_thread_state_t *machine_state = |
| reinterpret_cast<arm_thread_state_t *>(state); |
| |
| if (!context.Allocate()) |
| return false; |
| |
| *register_location = context.location(); |
| MDRawContextARM *context_ptr = context.get(); |
| context_ptr->context_flags = MD_CONTEXT_ARM_FULL; |
| |
| #define AddGPR(a) context_ptr->iregs[a] = REGISTER_FROM_THREADSTATE(machine_state, r[a]) |
| |
| context_ptr->iregs[13] = REGISTER_FROM_THREADSTATE(machine_state, sp); |
| context_ptr->iregs[14] = REGISTER_FROM_THREADSTATE(machine_state, lr); |
| context_ptr->iregs[15] = REGISTER_FROM_THREADSTATE(machine_state, pc); |
| context_ptr->cpsr = REGISTER_FROM_THREADSTATE(machine_state, cpsr); |
| |
| AddGPR(0); |
| AddGPR(1); |
| AddGPR(2); |
| AddGPR(3); |
| AddGPR(4); |
| AddGPR(5); |
| AddGPR(6); |
| AddGPR(7); |
| AddGPR(8); |
| AddGPR(9); |
| AddGPR(10); |
| AddGPR(11); |
| AddGPR(12); |
| #undef AddGPR |
| |
| return true; |
| } |
| #endif |
| |
| #ifdef HAS_ARM64_SUPPORT |
| bool MinidumpGenerator::WriteStackARM64(breakpad_thread_state_data_t state, |
| MDMemoryDescriptor *stack_location) { |
| arm_thread_state64_t *machine_state = |
| reinterpret_cast<arm_thread_state64_t *>(state); |
| mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, sp); |
| return WriteStackFromStartAddress(start_addr, stack_location); |
| } |
| |
| uint64_t |
| MinidumpGenerator::CurrentPCForStackARM64(breakpad_thread_state_data_t state) { |
| arm_thread_state64_t *machine_state = |
| reinterpret_cast<arm_thread_state64_t *>(state); |
| |
| return REGISTER_FROM_THREADSTATE(machine_state, pc); |
| } |
| |
| bool |
| MinidumpGenerator::WriteContextARM64(breakpad_thread_state_data_t state, |
| MDLocationDescriptor *register_location) |
| { |
| TypedMDRVA<MDRawContextARM64_Old> context(&writer_); |
| arm_thread_state64_t *machine_state = |
| reinterpret_cast<arm_thread_state64_t *>(state); |
| |
| if (!context.Allocate()) |
| return false; |
| |
| *register_location = context.location(); |
| MDRawContextARM64_Old *context_ptr = context.get(); |
| context_ptr->context_flags = MD_CONTEXT_ARM64_FULL_OLD; |
| |
| #define AddGPR(a) context_ptr->iregs[a] = \ |
| REGISTER_FROM_THREADSTATE(machine_state, x[a]) |
| |
| context_ptr->iregs[29] = REGISTER_FROM_THREADSTATE(machine_state, fp); |
| context_ptr->iregs[30] = REGISTER_FROM_THREADSTATE(machine_state, lr); |
| context_ptr->iregs[31] = REGISTER_FROM_THREADSTATE(machine_state, sp); |
| context_ptr->iregs[32] = REGISTER_FROM_THREADSTATE(machine_state, pc); |
| context_ptr->cpsr = REGISTER_FROM_THREADSTATE(machine_state, cpsr); |
| |
| AddGPR(0); |
| AddGPR(1); |
| AddGPR(2); |
| AddGPR(3); |
| AddGPR(4); |
| AddGPR(5); |
| AddGPR(6); |
| AddGPR(7); |
| AddGPR(8); |
| AddGPR(9); |
| AddGPR(10); |
| AddGPR(11); |
| AddGPR(12); |
| AddGPR(13); |
| AddGPR(14); |
| AddGPR(15); |
| AddGPR(16); |
| AddGPR(17); |
| AddGPR(18); |
| AddGPR(19); |
| AddGPR(20); |
| AddGPR(21); |
| AddGPR(22); |
| AddGPR(23); |
| AddGPR(24); |
| AddGPR(25); |
| AddGPR(26); |
| AddGPR(27); |
| AddGPR(28); |
| #undef AddGPR |
| |
| return true; |
| } |
| #endif |
| |
| #ifdef HAS_PCC_SUPPORT |
| bool MinidumpGenerator::WriteStackPPC(breakpad_thread_state_data_t state, |
| MDMemoryDescriptor *stack_location) { |
| ppc_thread_state_t *machine_state = |
| reinterpret_cast<ppc_thread_state_t *>(state); |
| mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1); |
| return WriteStackFromStartAddress(start_addr, stack_location); |
| } |
| |
| bool MinidumpGenerator::WriteStackPPC64(breakpad_thread_state_data_t state, |
| MDMemoryDescriptor *stack_location) { |
| ppc_thread_state64_t *machine_state = |
| reinterpret_cast<ppc_thread_state64_t *>(state); |
| mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1); |
| return WriteStackFromStartAddress(start_addr, stack_location); |
| } |
| |
| uint64_t |
| MinidumpGenerator::CurrentPCForStackPPC(breakpad_thread_state_data_t state) { |
| ppc_thread_state_t *machine_state = |
| reinterpret_cast<ppc_thread_state_t *>(state); |
| |
| return REGISTER_FROM_THREADSTATE(machine_state, srr0); |
| } |
| |
| uint64_t |
| MinidumpGenerator::CurrentPCForStackPPC64(breakpad_thread_state_data_t state) { |
| ppc_thread_state64_t *machine_state = |
| reinterpret_cast<ppc_thread_state64_t *>(state); |
| |
| return REGISTER_FROM_THREADSTATE(machine_state, srr0); |
| } |
| |
| bool MinidumpGenerator::WriteContextPPC(breakpad_thread_state_data_t state, |
| MDLocationDescriptor *register_location) |
| { |
| TypedMDRVA<MDRawContextPPC> context(&writer_); |
| ppc_thread_state_t *machine_state = |
| reinterpret_cast<ppc_thread_state_t *>(state); |
| |
| if (!context.Allocate()) |
| return false; |
| |
| *register_location = context.location(); |
| MDRawContextPPC *context_ptr = context.get(); |
| context_ptr->context_flags = MD_CONTEXT_PPC_BASE; |
| |
| #define AddReg(a) context_ptr->a = static_cast<__typeof__(context_ptr->a)>( \ |
| REGISTER_FROM_THREADSTATE(machine_state, a)) |
| #define AddGPR(a) context_ptr->gpr[a] = \ |
| static_cast<__typeof__(context_ptr->a)>( \ |
| REGISTER_FROM_THREADSTATE(machine_state, r ## a) |
| |
| AddReg(srr0); |
| AddReg(cr); |
| AddReg(xer); |
| AddReg(ctr); |
| AddReg(lr); |
| AddReg(vrsave); |
| |
| AddGPR(0); |
| AddGPR(1); |
| AddGPR(2); |
| AddGPR(3); |
| AddGPR(4); |
| AddGPR(5); |
| AddGPR(6); |
| AddGPR(7); |
| AddGPR(8); |
| AddGPR(9); |
| AddGPR(10); |
| AddGPR(11); |
| AddGPR(12); |
| AddGPR(13); |
| AddGPR(14); |
| AddGPR(15); |
| AddGPR(16); |
| AddGPR(17); |
| AddGPR(18); |
| AddGPR(19); |
| AddGPR(20); |
| AddGPR(21); |
| AddGPR(22); |
| AddGPR(23); |
| AddGPR(24); |
| AddGPR(25); |
| AddGPR(26); |
| AddGPR(27); |
| AddGPR(28); |
| AddGPR(29); |
| AddGPR(30); |
| AddGPR(31); |
| AddReg(mq); |
| #undef AddReg |
| #undef AddGPR |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteContextPPC64( |
| breakpad_thread_state_data_t state, |
| MDLocationDescriptor *register_location) { |
| TypedMDRVA<MDRawContextPPC64> context(&writer_); |
| ppc_thread_state64_t *machine_state = |
| reinterpret_cast<ppc_thread_state64_t *>(state); |
| |
| if (!context.Allocate()) |
| return false; |
| |
| *register_location = context.location(); |
| MDRawContextPPC64 *context_ptr = context.get(); |
| context_ptr->context_flags = MD_CONTEXT_PPC_BASE; |
| |
| #define AddReg(a) context_ptr->a = static_cast<__typeof__(context_ptr->a)>( \ |
| REGISTER_FROM_THREADSTATE(machine_state, a)) |
| #define AddGPR(a) context_ptr->gpr[a] = \ |
| static_cast<__typeof__(context_ptr->a)>( \ |
| REGISTER_FROM_THREADSTATE(machine_state, r ## a) |
| |
| AddReg(srr0); |
| AddReg(cr); |
| AddReg(xer); |
| AddReg(ctr); |
| AddReg(lr); |
| AddReg(vrsave); |
| |
| AddGPR(0); |
| AddGPR(1); |
| AddGPR(2); |
| AddGPR(3); |
| AddGPR(4); |
| AddGPR(5); |
| AddGPR(6); |
| AddGPR(7); |
| AddGPR(8); |
| AddGPR(9); |
| AddGPR(10); |
| AddGPR(11); |
| AddGPR(12); |
| AddGPR(13); |
| AddGPR(14); |
| AddGPR(15); |
| AddGPR(16); |
| AddGPR(17); |
| AddGPR(18); |
| AddGPR(19); |
| AddGPR(20); |
| AddGPR(21); |
| AddGPR(22); |
| AddGPR(23); |
| AddGPR(24); |
| AddGPR(25); |
| AddGPR(26); |
| AddGPR(27); |
| AddGPR(28); |
| AddGPR(29); |
| AddGPR(30); |
| AddGPR(31); |
| #undef AddReg |
| #undef AddGPR |
| |
| return true; |
| } |
| |
| #endif |
| |
| #ifdef HAS_X86_SUPPORT |
| bool MinidumpGenerator::WriteStackX86(breakpad_thread_state_data_t state, |
| MDMemoryDescriptor *stack_location) { |
| i386_thread_state_t *machine_state = |
| reinterpret_cast<i386_thread_state_t *>(state); |
| |
| mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, esp); |
| return WriteStackFromStartAddress(start_addr, stack_location); |
| } |
| |
| bool MinidumpGenerator::WriteStackX86_64(breakpad_thread_state_data_t state, |
| MDMemoryDescriptor *stack_location) { |
| x86_thread_state64_t *machine_state = |
| reinterpret_cast<x86_thread_state64_t *>(state); |
| |
| mach_vm_address_t start_addr = static_cast<mach_vm_address_t>( |
| REGISTER_FROM_THREADSTATE(machine_state, rsp)); |
| return WriteStackFromStartAddress(start_addr, stack_location); |
| } |
| |
| uint64_t |
| MinidumpGenerator::CurrentPCForStackX86(breakpad_thread_state_data_t state) { |
| i386_thread_state_t *machine_state = |
| reinterpret_cast<i386_thread_state_t *>(state); |
| |
| return REGISTER_FROM_THREADSTATE(machine_state, eip); |
| } |
| |
| uint64_t |
| MinidumpGenerator::CurrentPCForStackX86_64(breakpad_thread_state_data_t state) { |
| x86_thread_state64_t *machine_state = |
| reinterpret_cast<x86_thread_state64_t *>(state); |
| |
| return REGISTER_FROM_THREADSTATE(machine_state, rip); |
| } |
| |
| bool MinidumpGenerator::WriteContextX86(breakpad_thread_state_data_t state, |
| MDLocationDescriptor *register_location) |
| { |
| TypedMDRVA<MDRawContextX86> context(&writer_); |
| i386_thread_state_t *machine_state = |
| reinterpret_cast<i386_thread_state_t *>(state); |
| |
| if (!context.Allocate()) |
| return false; |
| |
| *register_location = context.location(); |
| MDRawContextX86 *context_ptr = context.get(); |
| |
| #define AddReg(a) context_ptr->a = static_cast<__typeof__(context_ptr->a)>( \ |
| REGISTER_FROM_THREADSTATE(machine_state, a)) |
| |
| context_ptr->context_flags = MD_CONTEXT_X86; |
| AddReg(eax); |
| AddReg(ebx); |
| AddReg(ecx); |
| AddReg(edx); |
| AddReg(esi); |
| AddReg(edi); |
| AddReg(ebp); |
| AddReg(esp); |
| |
| AddReg(cs); |
| AddReg(ds); |
| AddReg(ss); |
| AddReg(es); |
| AddReg(fs); |
| AddReg(gs); |
| AddReg(eflags); |
| |
| AddReg(eip); |
| #undef AddReg |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteContextX86_64( |
| breakpad_thread_state_data_t state, |
| MDLocationDescriptor *register_location) { |
| TypedMDRVA<MDRawContextAMD64> context(&writer_); |
| x86_thread_state64_t *machine_state = |
| reinterpret_cast<x86_thread_state64_t *>(state); |
| |
| if (!context.Allocate()) |
| return false; |
| |
| *register_location = context.location(); |
| MDRawContextAMD64 *context_ptr = context.get(); |
| |
| #define AddReg(a) context_ptr->a = static_cast<__typeof__(context_ptr->a)>( \ |
| REGISTER_FROM_THREADSTATE(machine_state, a)) |
| |
| context_ptr->context_flags = MD_CONTEXT_AMD64; |
| AddReg(rax); |
| AddReg(rbx); |
| AddReg(rcx); |
| AddReg(rdx); |
| AddReg(rdi); |
| AddReg(rsi); |
| AddReg(rbp); |
| AddReg(rsp); |
| AddReg(r8); |
| AddReg(r9); |
| AddReg(r10); |
| AddReg(r11); |
| AddReg(r12); |
| AddReg(r13); |
| AddReg(r14); |
| AddReg(r15); |
| AddReg(rip); |
| // according to AMD's software developer guide, bits above 18 are |
| // not used in the flags register. Since the minidump format |
| // specifies 32 bits for the flags register, we can truncate safely |
| // with no loss. |
| context_ptr->eflags = static_cast<uint32_t>(REGISTER_FROM_THREADSTATE(machine_state, rflags)); |
| AddReg(cs); |
| AddReg(fs); |
| AddReg(gs); |
| #undef AddReg |
| |
| return true; |
| } |
| #endif |
| |
| bool MinidumpGenerator::GetThreadState(thread_act_t target_thread, |
| thread_state_t state, |
| mach_msg_type_number_t *count) { |
| if (task_context_ && target_thread == mach_thread_self()) { |
| switch (cpu_type_) { |
| #ifdef HAS_ARM_SUPPORT |
| case CPU_TYPE_ARM: |
| size_t final_size = |
| std::min(static_cast<size_t>(*count), sizeof(arm_thread_state_t)); |
| memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size); |
| *count = static_cast<mach_msg_type_number_t>(final_size); |
| return true; |
| #endif |
| #ifdef HAS_ARM64_SUPPORT |
| case CPU_TYPE_ARM64: { |
| size_t final_size = |
| std::min(static_cast<size_t>(*count), sizeof(arm_thread_state64_t)); |
| memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size); |
| *count = static_cast<mach_msg_type_number_t>(final_size); |
| return true; |
| } |
| #endif |
| #ifdef HAS_X86_SUPPORT |
| case CPU_TYPE_I386: |
| case CPU_TYPE_X86_64: { |
| size_t state_size = cpu_type_ == CPU_TYPE_I386 ? |
| sizeof(i386_thread_state_t) : sizeof(x86_thread_state64_t); |
| size_t final_size = |
| std::min(static_cast<size_t>(*count), state_size); |
| memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size); |
| *count = static_cast<mach_msg_type_number_t>(final_size); |
| return true; |
| } |
| #endif |
| } |
| } |
| |
| thread_state_flavor_t flavor; |
| switch (cpu_type_) { |
| #ifdef HAS_ARM_SUPPORT |
| case CPU_TYPE_ARM: |
| flavor = ARM_THREAD_STATE; |
| break; |
| #endif |
| #ifdef HAS_ARM64_SUPPORT |
| case CPU_TYPE_ARM64: |
| flavor = ARM_THREAD_STATE64; |
| break; |
| #endif |
| #ifdef HAS_PPC_SUPPORT |
| case CPU_TYPE_POWERPC: |
| flavor = PPC_THREAD_STATE; |
| break; |
| case CPU_TYPE_POWERPC64: |
| flavor = PPC_THREAD_STATE64; |
| break; |
| #endif |
| #ifdef HAS_X86_SUPPORT |
| case CPU_TYPE_I386: |
| flavor = i386_THREAD_STATE; |
| break; |
| case CPU_TYPE_X86_64: |
| flavor = x86_THREAD_STATE64; |
| break; |
| #endif |
| default: |
| return false; |
| } |
| return thread_get_state(target_thread, flavor, |
| state, count) == KERN_SUCCESS; |
| } |
| |
| bool MinidumpGenerator::WriteThreadStream(mach_port_t thread_id, |
| MDRawThread *thread) { |
| breakpad_thread_state_data_t state; |
| mach_msg_type_number_t state_count |
| = static_cast<mach_msg_type_number_t>(sizeof(state)); |
| |
| if (GetThreadState(thread_id, state, &state_count)) { |
| if (!WriteStack(state, &thread->stack)) |
| return false; |
| |
| memory_blocks_.push_back(thread->stack); |
| |
| if (!WriteContext(state, &thread->thread_context)) |
| return false; |
| |
| thread->thread_id = thread_id; |
| } else { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteThreadListStream( |
| MDRawDirectory *thread_list_stream) { |
| TypedMDRVA<MDRawThreadList> list(&writer_); |
| thread_act_port_array_t threads_for_task; |
| mach_msg_type_number_t thread_count; |
| int non_generator_thread_count; |
| |
| if (task_threads(crashing_task_, &threads_for_task, &thread_count)) |
| return false; |
| |
| // Don't include the generator thread |
| if (handler_thread_ != MACH_PORT_NULL) |
| non_generator_thread_count = thread_count - 1; |
| else |
| non_generator_thread_count = thread_count; |
| if (!list.AllocateObjectAndArray(non_generator_thread_count, |
| sizeof(MDRawThread))) |
| return false; |
| |
| thread_list_stream->stream_type = MD_THREAD_LIST_STREAM; |
| thread_list_stream->location = list.location(); |
| |
| list.get()->number_of_threads = non_generator_thread_count; |
| |
| MDRawThread thread; |
| int thread_idx = 0; |
| |
| for (unsigned int i = 0; i < thread_count; ++i) { |
| memset(&thread, 0, sizeof(MDRawThread)); |
| |
| if (threads_for_task[i] != handler_thread_) { |
| if (!WriteThreadStream(threads_for_task[i], &thread)) |
| return false; |
| |
| list.CopyIndexAfterObject(thread_idx++, &thread, sizeof(MDRawThread)); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteMemoryListStream( |
| MDRawDirectory *memory_list_stream) { |
| TypedMDRVA<MDRawMemoryList> list(&writer_); |
| |
| // If the dump has an exception, include some memory around the |
| // instruction pointer. |
| const size_t kIPMemorySize = 256; // bytes |
| bool have_ip_memory = false; |
| MDMemoryDescriptor ip_memory_d; |
| if (exception_thread_ && exception_type_) { |
| breakpad_thread_state_data_t state; |
| mach_msg_type_number_t stateCount |
| = static_cast<mach_msg_type_number_t>(sizeof(state)); |
| |
| if (GetThreadState(exception_thread_, state, &stateCount)) { |
| uint64_t ip = CurrentPCForStack(state); |
| // Bound it to the upper and lower bounds of the region |
| // it's contained within. If it's not in a known memory region, |
| // don't bother trying to write it. |
| mach_vm_address_t addr = static_cast<vm_address_t>(ip); |
| mach_vm_size_t size; |
| natural_t nesting_level = 0; |
| vm_region_submap_info_64 info; |
| mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; |
| vm_region_recurse_info_t recurse_info; |
| recurse_info = reinterpret_cast<vm_region_recurse_info_t>(&info); |
| |
| kern_return_t ret = |
| mach_vm_region_recurse(crashing_task_, |
| &addr, |
| &size, |
| &nesting_level, |
| recurse_info, |
| &info_count); |
| if (ret == KERN_SUCCESS && ip >= addr && ip < (addr + size)) { |
| // Try to get 128 bytes before and after the IP, but |
| // settle for whatever's available. |
| ip_memory_d.start_of_memory_range = |
| std::max(uintptr_t(addr), |
| uintptr_t(ip - (kIPMemorySize / 2))); |
| uintptr_t end_of_range = |
| std::min(uintptr_t(ip + (kIPMemorySize / 2)), |
| uintptr_t(addr + size)); |
| uintptr_t range_diff = end_of_range - |
| static_cast<uintptr_t>(ip_memory_d.start_of_memory_range); |
| ip_memory_d.memory.data_size = static_cast<uint32_t>(range_diff); |
| have_ip_memory = true; |
| // This needs to get appended to the list even though |
| // the memory bytes aren't filled in yet so the entire |
| // list can be written first. The memory bytes will get filled |
| // in after the memory list is written. |
| memory_blocks_.push_back(ip_memory_d); |
| } |
| } |
| } |
| |
| // Now fill in the memory list and write it. |
| size_t memory_count = memory_blocks_.size(); |
| if (!list.AllocateObjectAndArray(memory_count, |
| sizeof(MDMemoryDescriptor))) |
| return false; |
| |
| memory_list_stream->stream_type = MD_MEMORY_LIST_STREAM; |
| memory_list_stream->location = list.location(); |
| |
| list.get()->number_of_memory_ranges = static_cast<uint32_t>(memory_count); |
| |
| unsigned int i; |
| for (i = 0; i < memory_count; ++i) { |
| list.CopyIndexAfterObject(i, &memory_blocks_[i], |
| sizeof(MDMemoryDescriptor)); |
| } |
| |
| if (have_ip_memory) { |
| // Now read the memory around the instruction pointer. |
| UntypedMDRVA ip_memory(&writer_); |
| if (!ip_memory.Allocate(ip_memory_d.memory.data_size)) |
| return false; |
| |
| if (dynamic_images_) { |
| // Out-of-process. |
| vector<uint8_t> memory; |
| if (ReadTaskMemory(crashing_task_, |
| ip_memory_d.start_of_memory_range, |
| ip_memory_d.memory.data_size, |
| memory) != KERN_SUCCESS) { |
| return false; |
| } |
| |
| ip_memory.Copy(&memory[0], ip_memory_d.memory.data_size); |
| } else { |
| // In-process, just copy from local memory. |
| ip_memory.Copy( |
| reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range), |
| ip_memory_d.memory.data_size); |
| } |
| |
| ip_memory_d.memory = ip_memory.location(); |
| // Write this again now that the data location is filled in. |
| list.CopyIndexAfterObject(i - 1, &ip_memory_d, |
| sizeof(MDMemoryDescriptor)); |
| } |
| |
| return true; |
| } |
| |
| bool |
| MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) { |
| TypedMDRVA<MDRawExceptionStream> exception(&writer_); |
| |
| if (!exception.Allocate()) |
| return false; |
| |
| exception_stream->stream_type = MD_EXCEPTION_STREAM; |
| exception_stream->location = exception.location(); |
| MDRawExceptionStream *exception_ptr = exception.get(); |
| exception_ptr->thread_id = exception_thread_; |
| |
| // This naming is confusing, but it is the proper translation from |
| // mach naming to minidump naming. |
| exception_ptr->exception_record.exception_code = exception_type_; |
| exception_ptr->exception_record.exception_flags = exception_code_; |
| |
| breakpad_thread_state_data_t state; |
| mach_msg_type_number_t state_count |
| = static_cast<mach_msg_type_number_t>(sizeof(state)); |
| |
| if (!GetThreadState(exception_thread_, state, &state_count)) |
| return false; |
| |
| if (!WriteContext(state, &exception_ptr->thread_context)) |
| return false; |
| |
| if (exception_type_ == EXC_BAD_ACCESS) |
| exception_ptr->exception_record.exception_address = exception_subcode_; |
| else |
| exception_ptr->exception_record.exception_address = CurrentPCForStack(state); |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteSystemInfoStream( |
| MDRawDirectory *system_info_stream) { |
| TypedMDRVA<MDRawSystemInfo> info(&writer_); |
| |
| if (!info.Allocate()) |
| return false; |
| |
| system_info_stream->stream_type = MD_SYSTEM_INFO_STREAM; |
| system_info_stream->location = info.location(); |
| |
| // CPU Information |
| uint32_t number_of_processors; |
| size_t len = sizeof(number_of_processors); |
| sysctlbyname("hw.ncpu", &number_of_processors, &len, NULL, 0); |
| MDRawSystemInfo *info_ptr = info.get(); |
| |
| switch (cpu_type_) { |
| #ifdef HAS_ARM_SUPPORT |
| case CPU_TYPE_ARM: |
| info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_ARM; |
| break; |
| #endif |
| #ifdef HAS_ARM64_SUPPORT |
| case CPU_TYPE_ARM64: |
| info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_ARM64_OLD; |
| break; |
| #endif |
| #ifdef HAS_PPC_SUPPORT |
| case CPU_TYPE_POWERPC: |
| case CPU_TYPE_POWERPC64: |
| info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_PPC; |
| break; |
| #endif |
| #ifdef HAS_X86_SUPPORT |
| case CPU_TYPE_I386: |
| case CPU_TYPE_X86_64: |
| if (cpu_type_ == CPU_TYPE_I386) |
| info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_X86; |
| else |
| info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_AMD64; |
| #ifdef __i386__ |
| // ebx is used for PIC code, so we need |
| // to preserve it. |
| #define cpuid(op,eax,ebx,ecx,edx) \ |
| asm ("pushl %%ebx \n\t" \ |
| "cpuid \n\t" \ |
| "movl %%ebx,%1 \n\t" \ |
| "popl %%ebx" \ |
| : "=a" (eax), \ |
| "=g" (ebx), \ |
| "=c" (ecx), \ |
| "=d" (edx) \ |
| : "0" (op)) |
| #elif defined(__x86_64__) |
| |
| #define cpuid(op,eax,ebx,ecx,edx) \ |
| asm ("cpuid \n\t" \ |
| : "=a" (eax), \ |
| "=b" (ebx), \ |
| "=c" (ecx), \ |
| "=d" (edx) \ |
| : "0" (op)) |
| #endif |
| |
| #if defined(__i386__) || defined(__x86_64__) |
| int unused, unused2; |
| // get vendor id |
| cpuid(0, unused, info_ptr->cpu.x86_cpu_info.vendor_id[0], |
| info_ptr->cpu.x86_cpu_info.vendor_id[2], |
| info_ptr->cpu.x86_cpu_info.vendor_id[1]); |
| // get version and feature info |
| cpuid(1, info_ptr->cpu.x86_cpu_info.version_information, unused, unused2, |
| info_ptr->cpu.x86_cpu_info.feature_information); |
| |
| // family |
| info_ptr->processor_level = |
| (info_ptr->cpu.x86_cpu_info.version_information & 0xF00) >> 8; |
| // 0xMMSS (Model, Stepping) |
| info_ptr->processor_revision = static_cast<uint16_t>( |
| (info_ptr->cpu.x86_cpu_info.version_information & 0xF) | |
| ((info_ptr->cpu.x86_cpu_info.version_information & 0xF0) << 4)); |
| |
| // decode extended model info |
| if (info_ptr->processor_level == 0xF || |
| info_ptr->processor_level == 0x6) { |
| info_ptr->processor_revision |= |
| ((info_ptr->cpu.x86_cpu_info.version_information & 0xF0000) >> 4); |
| } |
| |
| // decode extended family info |
| if (info_ptr->processor_level == 0xF) { |
| info_ptr->processor_level += |
| ((info_ptr->cpu.x86_cpu_info.version_information & 0xFF00000) >> 20); |
| } |
| |
| #endif // __i386__ || __x86_64_ |
| break; |
| #endif // HAS_X86_SUPPORT |
| default: |
| info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN; |
| break; |
| } |
| |
| info_ptr->number_of_processors = static_cast<uint8_t>(number_of_processors); |
| #if TARGET_OS_IPHONE |
| info_ptr->platform_id = MD_OS_IOS; |
| #else |
| info_ptr->platform_id = MD_OS_MAC_OS_X; |
| #endif // TARGET_OS_IPHONE |
| |
| MDLocationDescriptor build_string_loc; |
| |
| if (!writer_.WriteString(build_string_, 0, |
| &build_string_loc)) |
| return false; |
| |
| info_ptr->csd_version_rva = build_string_loc.rva; |
| info_ptr->major_version = os_major_version_; |
| info_ptr->minor_version = os_minor_version_; |
| info_ptr->build_number = os_build_number_; |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteModuleStream(unsigned int index, |
| MDRawModule *module) { |
| if (dynamic_images_) { |
| // we're in a different process than the crashed process |
| DynamicImage *image = dynamic_images_->GetImage(index); |
| |
| if (!image) |
| return false; |
| |
| memset(module, 0, sizeof(MDRawModule)); |
| |
| MDLocationDescriptor string_location; |
| |
| string name = image->GetFilePath(); |
| if (!writer_.WriteString(name.c_str(), 0, &string_location)) |
| return false; |
| |
| module->base_of_image = image->GetVMAddr() + image->GetVMAddrSlide(); |
| module->size_of_image = static_cast<uint32_t>(image->GetVMSize()); |
| module->module_name_rva = string_location.rva; |
| |
| // We'll skip the executable module, because they don't have |
| // LC_ID_DYLIB load commands, and the crash processing server gets |
| // version information from the Plist file, anyway. |
| if (index != static_cast<uint32_t>(FindExecutableModule())) { |
| module->version_info.signature = MD_VSFIXEDFILEINFO_SIGNATURE; |
| module->version_info.struct_version |= MD_VSFIXEDFILEINFO_VERSION; |
| // Convert MAC dylib version format, which is a 32 bit number, to the |
| // format used by minidump. The mac format is <16 bits>.<8 bits>.<8 bits> |
| // so it fits nicely into the windows version with some massaging |
| // The mapping is: |
| // 1) upper 16 bits of MAC version go to lower 16 bits of product HI |
| // 2) Next most significant 8 bits go to upper 16 bits of product LO |
| // 3) Least significant 8 bits go to lower 16 bits of product LO |
| uint32_t modVersion = image->GetVersion(); |
| module->version_info.file_version_hi = 0; |
| module->version_info.file_version_hi = modVersion >> 16; |
| module->version_info.file_version_lo |= (modVersion & 0xff00) << 8; |
| module->version_info.file_version_lo |= (modVersion & 0xff); |
| } |
| |
| if (!WriteCVRecord(module, image->GetCPUType(), name.c_str(), false)) { |
| return false; |
| } |
| } else { |
| // Getting module info in the crashed process |
| const breakpad_mach_header *header; |
| header = (breakpad_mach_header*)_dyld_get_image_header(index); |
| if (!header) |
| return false; |
| |
| #ifdef __LP64__ |
| assert(header->magic == MH_MAGIC_64); |
| |
| if(header->magic != MH_MAGIC_64) |
| return false; |
| #else |
| assert(header->magic == MH_MAGIC); |
| |
| if(header->magic != MH_MAGIC) |
| return false; |
| #endif |
| |
| int cpu_type = header->cputype; |
| unsigned long slide = _dyld_get_image_vmaddr_slide(index); |
| const char* name = _dyld_get_image_name(index); |
| const struct load_command *cmd = |
| reinterpret_cast<const struct load_command *>(header + 1); |
| |
| memset(module, 0, sizeof(MDRawModule)); |
| |
| for (unsigned int i = 0; cmd && (i < header->ncmds); i++) { |
| if (cmd->cmd == LC_SEGMENT_ARCH) { |
| |
| const breakpad_mach_segment_command *seg = |
| reinterpret_cast<const breakpad_mach_segment_command *>(cmd); |
| |
| if (!strcmp(seg->segname, "__TEXT")) { |
| MDLocationDescriptor string_location; |
| |
| if (!writer_.WriteString(name, 0, &string_location)) |
| return false; |
| |
| module->base_of_image = seg->vmaddr + slide; |
| module->size_of_image = static_cast<uint32_t>(seg->vmsize); |
| module->module_name_rva = string_location.rva; |
| |
| bool in_memory = false; |
| #if TARGET_OS_IPHONE |
| in_memory = true; |
| #endif |
| if (!WriteCVRecord(module, cpu_type, name, in_memory)) |
| return false; |
| |
| return true; |
| } |
| } |
| |
| cmd = reinterpret_cast<struct load_command*>((char *)cmd + cmd->cmdsize); |
| } |
| } |
| |
| return true; |
| } |
| |
| int MinidumpGenerator::FindExecutableModule() { |
| if (dynamic_images_) { |
| int index = dynamic_images_->GetExecutableImageIndex(); |
| |
| if (index >= 0) { |
| return index; |
| } |
| } else { |
| int image_count = _dyld_image_count(); |
| const struct mach_header *header; |
| |
| for (int index = 0; index < image_count; ++index) { |
| header = _dyld_get_image_header(index); |
| |
| if (header->filetype == MH_EXECUTE) |
| return index; |
| } |
| } |
| |
| // failed - just use the first image |
| return 0; |
| } |
| |
| bool MinidumpGenerator::WriteCVRecord(MDRawModule *module, int cpu_type, |
| const char *module_path, bool in_memory) { |
| TypedMDRVA<MDCVInfoPDB70> cv(&writer_); |
| |
| // Only return the last path component of the full module path |
| const char *module_name = strrchr(module_path, '/'); |
| |
| // Increment past the slash |
| if (module_name) |
| ++module_name; |
| else |
| module_name = "<Unknown>"; |
| |
| size_t module_name_length = strlen(module_name); |
| |
| if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(uint8_t))) |
| return false; |
| |
| if (!cv.CopyIndexAfterObject(0, module_name, module_name_length)) |
| return false; |
| |
| module->cv_record = cv.location(); |
| MDCVInfoPDB70 *cv_ptr = cv.get(); |
| cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE; |
| cv_ptr->age = 0; |
| |
| // Get the module identifier |
| unsigned char identifier[16]; |
| bool result = false; |
| if (in_memory) { |
| MacFileUtilities::MachoID macho(module_path, |
| reinterpret_cast<void *>(module->base_of_image), |
| static_cast<size_t>(module->size_of_image)); |
| result = macho.UUIDCommand(cpu_type, CPU_SUBTYPE_MULTIPLE, identifier); |
| if (!result) |
| result = macho.MD5(cpu_type, CPU_SUBTYPE_MULTIPLE, identifier); |
| } |
| |
| if (!result) { |
| FileID file_id(module_path); |
| result = file_id.MachoIdentifier(cpu_type, CPU_SUBTYPE_MULTIPLE, |
| identifier); |
| } |
| |
| if (result) { |
| cv_ptr->signature.data1 = |
| static_cast<uint32_t>(identifier[0]) << 24 | |
| static_cast<uint32_t>(identifier[1]) << 16 | |
| static_cast<uint32_t>(identifier[2]) << 8 | |
| static_cast<uint32_t>(identifier[3]); |
| cv_ptr->signature.data2 = |
| static_cast<uint16_t>(identifier[4] << 8) | identifier[5]; |
| cv_ptr->signature.data3 = |
| static_cast<uint16_t>(identifier[6] << 8) | identifier[7]; |
| cv_ptr->signature.data4[0] = identifier[8]; |
| cv_ptr->signature.data4[1] = identifier[9]; |
| cv_ptr->signature.data4[2] = identifier[10]; |
| cv_ptr->signature.data4[3] = identifier[11]; |
| cv_ptr->signature.data4[4] = identifier[12]; |
| cv_ptr->signature.data4[5] = identifier[13]; |
| cv_ptr->signature.data4[6] = identifier[14]; |
| cv_ptr->signature.data4[7] = identifier[15]; |
| } |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteModuleListStream( |
| MDRawDirectory *module_list_stream) { |
| TypedMDRVA<MDRawModuleList> list(&writer_); |
| |
| uint32_t image_count = dynamic_images_ ? |
| dynamic_images_->GetImageCount() : |
| _dyld_image_count(); |
| |
| if (!list.AllocateObjectAndArray(image_count, MD_MODULE_SIZE)) |
| return false; |
| |
| module_list_stream->stream_type = MD_MODULE_LIST_STREAM; |
| module_list_stream->location = list.location(); |
| list.get()->number_of_modules = static_cast<uint32_t>(image_count); |
| |
| // Write out the executable module as the first one |
| MDRawModule module; |
| uint32_t executableIndex = FindExecutableModule(); |
| |
| if (!WriteModuleStream(static_cast<unsigned>(executableIndex), &module)) { |
| return false; |
| } |
| |
| list.CopyIndexAfterObject(0, &module, MD_MODULE_SIZE); |
| int destinationIndex = 1; // Write all other modules after this one |
| |
| for (uint32_t i = 0; i < image_count; ++i) { |
| if (i != executableIndex) { |
| if (!WriteModuleStream(static_cast<unsigned>(i), &module)) { |
| return false; |
| } |
| |
| list.CopyIndexAfterObject(destinationIndex++, &module, MD_MODULE_SIZE); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) { |
| TypedMDRVA<MDRawMiscInfo> info(&writer_); |
| |
| if (!info.Allocate()) |
| return false; |
| |
| misc_info_stream->stream_type = MD_MISC_INFO_STREAM; |
| misc_info_stream->location = info.location(); |
| |
| MDRawMiscInfo *info_ptr = info.get(); |
| info_ptr->size_of_info = static_cast<uint32_t>(sizeof(MDRawMiscInfo)); |
| info_ptr->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID | |
| MD_MISCINFO_FLAGS1_PROCESS_TIMES | |
| MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO; |
| |
| // Process ID |
| info_ptr->process_id = getpid(); |
| |
| // Times |
| struct rusage usage; |
| if (getrusage(RUSAGE_SELF, &usage) != -1) { |
| // Omit the fractional time since the MDRawMiscInfo only wants seconds |
| info_ptr->process_user_time = |
| static_cast<uint32_t>(usage.ru_utime.tv_sec); |
| info_ptr->process_kernel_time = |
| static_cast<uint32_t>(usage.ru_stime.tv_sec); |
| } |
| int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, |
| static_cast<int>(info_ptr->process_id) }; |
| uint mibsize = static_cast<uint>(sizeof(mib) / sizeof(mib[0])); |
| struct kinfo_proc proc; |
| size_t size = sizeof(proc); |
| if (sysctl(mib, mibsize, &proc, &size, NULL, 0) == 0) { |
| info_ptr->process_create_time = |
| static_cast<uint32_t>(proc.kp_proc.p_starttime.tv_sec); |
| } |
| |
| // Speed |
| uint64_t speed; |
| const uint64_t kOneMillion = 1000 * 1000; |
| size = sizeof(speed); |
| sysctlbyname("hw.cpufrequency_max", &speed, &size, NULL, 0); |
| info_ptr->processor_max_mhz = static_cast<uint32_t>(speed / kOneMillion); |
| info_ptr->processor_mhz_limit = static_cast<uint32_t>(speed / kOneMillion); |
| size = sizeof(speed); |
| sysctlbyname("hw.cpufrequency", &speed, &size, NULL, 0); |
| info_ptr->processor_current_mhz = static_cast<uint32_t>(speed / kOneMillion); |
| |
| return true; |
| } |
| |
| bool MinidumpGenerator::WriteBreakpadInfoStream( |
| MDRawDirectory *breakpad_info_stream) { |
| TypedMDRVA<MDRawBreakpadInfo> info(&writer_); |
| |
| if (!info.Allocate()) |
| return false; |
| |
| breakpad_info_stream->stream_type = MD_BREAKPAD_INFO_STREAM; |
| breakpad_info_stream->location = info.location(); |
| MDRawBreakpadInfo *info_ptr = info.get(); |
| |
| if (exception_thread_ && exception_type_) { |
| info_ptr->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | |
| MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; |
| info_ptr->dump_thread_id = handler_thread_; |
| info_ptr->requesting_thread_id = exception_thread_; |
| } else { |
| info_ptr->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID; |
| info_ptr->dump_thread_id = handler_thread_; |
| info_ptr->requesting_thread_id = 0; |
| } |
| |
| return true; |
| } |
| |
| } // namespace google_breakpad |