// 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 #include #include #include #include #include #include #include #include #include "client/mac/handler/minidump_generator.h" #ifdef HAS_ARM_SUPPORT #include #endif #ifdef HAS_PPC_SUPPORT #include #endif #ifdef HAS_X86_SUPPORT #include #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 __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()), 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()), 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); CFDataRef data; SInt32 error; CFURLCreateDataAndPropertiesFromResource(NULL, sys_vers, &data, NULL, NULL, &error); if (!data) { CFRelease(sys_vers); return; } CFDictionaryRef list = static_cast (CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable, NULL)); if (!list) { CFRelease(sys_vers); CFRelease(data); return; } CFStringRef build_version = static_cast (CFDictionaryGetValue(list, CFSTR("ProductBuildVersion"))); CFStringRef product_version = static_cast (CFDictionaryGetValue(list, CFSTR("ProductVersion"))); string build_str = ConvertToString(build_version); string product_str = ConvertToString(product_version); CFRelease(list); CFRelease(sys_vers); CFRelease(data); 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); } 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 header(&writer_); TypedMDRVA dir(&writer_); if (!header.Allocate()) return false; int writer_count = static_cast(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(&(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(&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; mach_msg_type_number_t 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 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(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_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_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; } } u_int64_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_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("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(state); mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, sp); return WriteStackFromStartAddress(start_addr, stack_location); } u_int64_t MinidumpGenerator::CurrentPCForStackARM(breakpad_thread_state_data_t state) { arm_thread_state_t *machine_state = reinterpret_cast(state); return REGISTER_FROM_THREADSTATE(machine_state, pc); } bool MinidumpGenerator::WriteContextARM(breakpad_thread_state_data_t state, MDLocationDescriptor *register_location) { TypedMDRVA context(&writer_); arm_thread_state_t *machine_state = reinterpret_cast(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 AddReg #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(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(state); mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1); return WriteStackFromStartAddress(start_addr, stack_location); } u_int64_t MinidumpGenerator::CurrentPCForStackPPC(breakpad_thread_state_data_t state) { ppc_thread_state_t *machine_state = reinterpret_cast(state); return REGISTER_FROM_THREADSTATE(machine_state, srr0); } u_int64_t MinidumpGenerator::CurrentPCForStackPPC64(breakpad_thread_state_data_t state) { ppc_thread_state64_t *machine_state = reinterpret_cast(state); return REGISTER_FROM_THREADSTATE(machine_state, srr0); } bool MinidumpGenerator::WriteContextPPC(breakpad_thread_state_data_t state, MDLocationDescriptor *register_location) { TypedMDRVA context(&writer_); ppc_thread_state_t *machine_state = reinterpret_cast(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 = REGISTER_FROM_THREADSTATE(machine_state, a) #define AddGPR(a) context_ptr->gpr[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 context(&writer_); ppc_thread_state64_t *machine_state = reinterpret_cast(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 = REGISTER_FROM_THREADSTATE(machine_state, a) #define AddGPR(a) context_ptr->gpr[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(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(state); mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, rsp); return WriteStackFromStartAddress(start_addr, stack_location); } u_int64_t MinidumpGenerator::CurrentPCForStackX86(breakpad_thread_state_data_t state) { i386_thread_state_t *machine_state = reinterpret_cast(state); return REGISTER_FROM_THREADSTATE(machine_state, eip); } u_int64_t MinidumpGenerator::CurrentPCForStackX86_64(breakpad_thread_state_data_t state) { x86_thread_state64_t *machine_state = reinterpret_cast(state); return REGISTER_FROM_THREADSTATE(machine_state, rip); } bool MinidumpGenerator::WriteContextX86(breakpad_thread_state_data_t state, MDLocationDescriptor *register_location) { TypedMDRVA context(&writer_); i386_thread_state_t *machine_state = reinterpret_cast(state); if (!context.Allocate()) return false; *register_location = context.location(); MDRawContextX86 *context_ptr = context.get(); #define AddReg(a) 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 context(&writer_); x86_thread_state64_t *machine_state = reinterpret_cast(state); if (!context.Allocate()) return false; *register_location = context.location(); MDRawContextAMD64 *context_ptr = context.get(); #define AddReg(a) 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(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) { thread_state_flavor_t flavor; switch (cpu_type_) { #ifdef HAS_ARM_SUPPORT case CPU_TYPE_ARM: flavor = ARM_THREAD_STATE; 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(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 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 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(sizeof(state)); if (thread_get_state(exception_thread_, BREAKPAD_MACHINE_THREAD_STATE, state, &stateCount) == KERN_SUCCESS) { u_int64_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 = 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; kern_return_t ret = mach_vm_region_recurse(crashing_task_, &addr, &size, &nesting_level, (vm_region_recurse_info_t)&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)); ip_memory_d.memory.data_size = end_of_range - ip_memory_d.start_of_memory_range; 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. unsigned 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 = 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 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(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 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(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 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_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 = (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 = 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(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 != (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(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(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(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((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 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 = ""; size_t module_name_length = strlen(module_name); if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_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(module->base_of_image), static_cast(module->size_of_image)); result = macho.UUIDCommand(cpu_type, identifier); if (!result) result = macho.MD5(cpu_type, identifier); } if (!result) { FileID file_id(module_path); result = file_id.MachoIdentifier(cpu_type, identifier); } if (result) { cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 | (uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 | (uint32_t)identifier[3]; cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5]; cv_ptr->signature.data3 = (uint32_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 list(&writer_); size_t image_count = dynamic_images_ ? static_cast(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 = image_count; // Write out the executable module as the first one MDRawModule module; size_t executableIndex = FindExecutableModule(); if (!WriteModuleStream(executableIndex, &module)) { return false; } list.CopyIndexAfterObject(0, &module, MD_MODULE_SIZE); int destinationIndex = 1; // Write all other modules after this one for (size_t i = 0; i < image_count; ++i) { if (i != executableIndex) { if (!WriteModuleStream(i, &module)) { return false; } list.CopyIndexAfterObject(destinationIndex++, &module, MD_MODULE_SIZE); } } return true; } bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) { TypedMDRVA 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(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(usage.ru_utime.tv_sec); info_ptr->process_kernel_time = static_cast(usage.ru_stime.tv_sec); } int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, static_cast(info_ptr->process_id) }; u_int mibsize = static_cast(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(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(speed / kOneMillion); info_ptr->processor_mhz_limit = static_cast(speed / kOneMillion); size = sizeof(speed); sysctlbyname("hw.cpufrequency", &speed, &size, NULL, 0); info_ptr->processor_current_mhz = static_cast(speed / kOneMillion); return true; } bool MinidumpGenerator::WriteBreakpadInfoStream( MDRawDirectory *breakpad_info_stream) { TypedMDRVA 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