1291 lines
40 KiB
C++
1291 lines
40 KiB
C++
|
// 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/i386/thread_status.h>
|
||
|
#include <mach/mach_vm.h>
|
||
|
#include <mach/ppc/thread_status.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"
|
||
|
#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()),
|
||
|
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)
|
||
|
return;
|
||
|
|
||
|
CFDictionaryRef list = static_cast<CFDictionaryRef>
|
||
|
(CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable,
|
||
|
NULL));
|
||
|
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);
|
||
|
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<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 (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 (((cpu_type_ & CPU_ARCH_ABI64) &&
|
||
|
(stack_region_base + stack_region_size) == TOP_OF_THREAD0_STACK_64BIT) ||
|
||
|
(!(cpu_type_ & CPU_ARCH_ABI64) &&
|
||
|
(stack_region_base + stack_region_size) == TOP_OF_THREAD0_STACK_32BIT)) {
|
||
|
// 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_) {
|
||
|
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_) {
|
||
|
case CPU_TYPE_POWERPC:
|
||
|
return WriteStackPPC(state, stack_location);
|
||
|
case CPU_TYPE_POWERPC64:
|
||
|
return WriteStackPPC64(state, stack_location);
|
||
|
case CPU_TYPE_I386:
|
||
|
return WriteStackX86(state, stack_location);
|
||
|
case CPU_TYPE_X86_64:
|
||
|
return WriteStackX86_64(state, stack_location);
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||
|
MDLocationDescriptor *register_location) {
|
||
|
switch (cpu_type_) {
|
||
|
case CPU_TYPE_POWERPC:
|
||
|
return WriteContextPPC(state, register_location);
|
||
|
case CPU_TYPE_POWERPC64:
|
||
|
return WriteContextPPC64(state, register_location);
|
||
|
case CPU_TYPE_I386:
|
||
|
return WriteContextX86(state, register_location);
|
||
|
case CPU_TYPE_X86_64:
|
||
|
return WriteContextX86_64(state, register_location);
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
u_int64_t MinidumpGenerator::CurrentPCForStack(
|
||
|
breakpad_thread_state_data_t state) {
|
||
|
switch (cpu_type_) {
|
||
|
case CPU_TYPE_POWERPC:
|
||
|
return CurrentPCForStackPPC(state);
|
||
|
case CPU_TYPE_POWERPC64:
|
||
|
return CurrentPCForStackPPC64(state);
|
||
|
case CPU_TYPE_I386:
|
||
|
return CurrentPCForStackX86(state);
|
||
|
case CPU_TYPE_X86_64:
|
||
|
return CurrentPCForStackX86_64(state);
|
||
|
default:
|
||
|
assert("Unknown CPU type!");
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
u_int64_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);
|
||
|
}
|
||
|
|
||
|
u_int64_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 = 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<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 = 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;
|
||
|
}
|
||
|
|
||
|
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 = 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<i386_thread_state_t *>(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<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 = 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(a)
|
||
|
|
||
|
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 = 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<u_int32_t>(REGISTER_FROM_THREADSTATE(machine_state, rflags));
|
||
|
AddReg(cs);
|
||
|
AddReg(fs);
|
||
|
AddReg(gs);
|
||
|
#undef AddReg(a)
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
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_) {
|
||
|
case CPU_TYPE_POWERPC:
|
||
|
flavor = PPC_THREAD_STATE;
|
||
|
break;
|
||
|
case CPU_TYPE_POWERPC64:
|
||
|
flavor = PPC_THREAD_STATE64;
|
||
|
break;
|
||
|
case CPU_TYPE_I386:
|
||
|
flavor = i386_THREAD_STATE;
|
||
|
break;
|
||
|
case CPU_TYPE_X86_64:
|
||
|
flavor = x86_THREAD_STATE64;
|
||
|
break;
|
||
|
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 (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<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_) {
|
||
|
case CPU_TYPE_POWERPC:
|
||
|
case CPU_TYPE_POWERPC64:
|
||
|
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_PPC;
|
||
|
break;
|
||
|
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;
|
||
|
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;
|
||
|
|
||
|
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<u_int32_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 != (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())) {
|
||
|
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<u_int32_t>(seg->vmsize);
|
||
|
module->module_name_rva = string_location.rva;
|
||
|
|
||
|
if (!WriteCVRecord(module, cpu_type, name))
|
||
|
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) {
|
||
|
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(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<MDRawModuleList> 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<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<u_int32_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<u_int32_t>(usage.ru_utime.tv_sec);
|
||
|
info_ptr->process_kernel_time =
|
||
|
static_cast<u_int32_t>(usage.ru_stime.tv_sec);
|
||
|
}
|
||
|
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, info_ptr->process_id };
|
||
|
u_int mibsize = static_cast<u_int>(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<u_int32_t>(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<u_int32_t>(speed / kOneMillion);
|
||
|
info_ptr->processor_mhz_limit = static_cast<u_int32_t>(speed / kOneMillion);
|
||
|
size = sizeof(speed);
|
||
|
sysctlbyname("hw.cpufrequency", &speed, &size, NULL, 0);
|
||
|
info_ptr->processor_current_mhz = static_cast<u_int32_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
|