// 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 #include "client/mac/handler/minidump_generator.h" #include "client/minidump_file_writer-inl.h" #include "common/mac/file_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()), 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), dynamic_images_(NULL), memory_blocks_(&allocator_) { if (crashing_task != mach_task_self()) { dynamic_images_ = new DynamicImages(crashing_task_); } else { dynamic_images_ = NULL; } 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) return; CFDictionaryRef list = static_cast (CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable, NULL)); if (!list) 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 (start_addr < stack_region_base) { // probably stack corruption, since mach_vm_region had to go // higher in the process address space to find a valid region. return 0; } if ((stack_region_base + stack_region_size) == TOP_OF_THREAD0_STACK) { // The stack for thread 0 needs to extend all the way to // 0xc0000000 on 32 bit and 00007fff5fc00000 on 64bit. HOWEVER, // for many processes, the stack is first created in one page // below this, and is then later extended to a much larger size by // creating a new VM region immediately below the initial page. // You can see this for yourself by running vmmap on a "hello, // world" program // Because of the above, we'll add 4k to include the original // stack frame page. // This method of finding the stack region needs to be done in // a better way; the breakpad issue 247 is tracking this. stack_region_size += 0x1000; } return result == KERN_SUCCESS ? stack_region_base + stack_region_size - start_addr : 0; } 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_) { kern_return_t kr; void *stack_memory = ReadTaskMemory(crashing_task_, (void*)start_addr, size, &kr); if (stack_memory == NULL) { return false; } result = memory.Copy(stack_memory, size); free(stack_memory); } else { result = memory.Copy(reinterpret_cast(start_addr), size); } } stack_location->start_of_memory_range = start_addr; stack_location->memory = memory.location(); return result; } #if TARGET_CPU_PPC || TARGET_CPU_PPC64 bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state, MDMemoryDescriptor *stack_location) { breakpad_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); } u_int64_t MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) { breakpad_thread_state_t *machine_state = reinterpret_cast(state); return REGISTER_FROM_THREADSTATE(machine_state, srr0); } bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state, MDLocationDescriptor *register_location) { TypedMDRVA context(&writer_); breakpad_thread_state_t *machine_state = reinterpret_cast(state); if (!context.Allocate()) return false; *register_location = context.location(); MinidumpContext *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); #if TARGET_CPU_PPC /* The mq register is only for PPC */ AddReg(mq); #endif return true; } #elif TARGET_CPU_X86 || TARGET_CPU_X86_64 bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state, MDMemoryDescriptor *stack_location) { breakpad_thread_state_t *machine_state = reinterpret_cast(state); #if TARGET_CPU_X86_64 mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, rsp); #else mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, esp); #endif return WriteStackFromStartAddress(start_addr, stack_location); } u_int64_t MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) { breakpad_thread_state_t *machine_state = reinterpret_cast(state); #if TARGET_CPU_X86_64 return REGISTER_FROM_THREADSTATE(machine_state, rip); #else return REGISTER_FROM_THREADSTATE(machine_state, eip); #endif } bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state, MDLocationDescriptor *register_location) { TypedMDRVA context(&writer_); breakpad_thread_state_t *machine_state = reinterpret_cast(state); if (!context.Allocate()) return false; *register_location = context.location(); MinidumpContext *context_ptr = context.get(); #define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a) #if TARGET_CPU_X86 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); #else 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(machine_state->__rflags); AddReg(cs); AddReg(fs); AddReg(gs); #endif #undef AddReg(a) return true; } #endif 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 (thread_get_state(thread_id, BREAKPAD_MACHINE_THREAD_STATE, state, &state_count) == KERN_SUCCESS) { 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. kern_return_t kr; void *memory = ReadTaskMemory( crashing_task_, reinterpret_cast(ip_memory_d.start_of_memory_range), ip_memory_d.memory.data_size, &kr); if (memory == NULL) { return false; } ip_memory.Copy(memory, ip_memory_d.memory.data_size); free(memory); } 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 stateCount = static_cast(sizeof(state)); if (thread_get_state(exception_thread_, BREAKPAD_MACHINE_THREAD_STATE, state, &stateCount) != KERN_SUCCESS) 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 cpu_type; size_t len = sizeof(cpu_type); sysctlbyname("hw.cputype", &cpu_type, &len, NULL, 0); uint32_t number_of_processors; len = sizeof(number_of_processors); sysctlbyname("hw.ncpu", &number_of_processors, &len, NULL, 0); MDRawSystemInfo *info_ptr = info.get(); switch (cpu_type) { case CPU_TYPE_POWERPC: info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_PPC; break; case CPU_TYPE_I386: case CPU_TYPE_X86_64: // hw.cputype is currently always I386 even on an x86-64 system #ifdef __i386__ info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_X86; // 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__) info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_AMD64; #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; default: info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN; break; } info_ptr->number_of_processors = number_of_processors; info_ptr->platform_id = MD_OS_MAC_OS_X; 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; const breakpad_mach_header *header = image->GetMachHeader(); if (!header) return false; int cpu_type = header->cputype; memset(module, 0, sizeof(MDRawModule)); MDLocationDescriptor string_location; const char* name = image->GetFilePath(); if (!writer_.WriteString(name, 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, cpu_type, name)) { return false; } } else { // we're 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; if (!WriteCVRecord(module, cpu_type, name)) 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) { 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 FileID file_id(module_path); unsigned char identifier[16]; if (file_id.MachoIdentifier(cpu_type, identifier)) { 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_); int 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 = image_count; // Write out the executable module as the first one MDRawModule module; int 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 (int 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, info_ptr->process_id }; u_int mibsize = static_cast(sizeof(mib) / sizeof(mib[0])); size_t size; if (!sysctl(mib, mibsize, NULL, &size, NULL, 0)) { mach_vm_address_t addr; if (mach_vm_allocate(mach_task_self(), &addr, size, true) == KERN_SUCCESS) { struct kinfo_proc *proc = (struct kinfo_proc *)addr; if (!sysctl(mib, mibsize, proc, &size, NULL, 0)) info_ptr->process_create_time = static_cast(proc->kp_proc.p_starttime.tv_sec); mach_vm_deallocate(mach_task_self(), addr, size); } } // 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