Allow out-of-process minidump generation to work on processes of a different CPU architecture
R=mark at http://breakpad.appspot.com/241001 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@746 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
0d9bd40775
commit
0344a368de
10 changed files with 1262 additions and 653 deletions
|
@ -53,6 +53,8 @@
|
|||
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
|
||||
D23F4B2E12A7E13200686C8D /* minidump_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D23F4B2C12A7E13200686C8D /* minidump_generator_test.cc */; };
|
||||
D23F4B3312A7E17700686C8D /* libgtest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D2F9A41512131EF0002747C1 /* libgtest.a */; };
|
||||
D23F4BB112A868CB00686C8D /* minidump_generator_test_helper.cc in Sources */ = {isa = PBXBuildFile; fileRef = D23F4B9A12A8688800686C8D /* minidump_generator_test_helper.cc */; };
|
||||
D23F4BB812A868F700686C8D /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; };
|
||||
D244536A12426F00009BBCE0 /* logging.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535112426EBB009BBCE0 /* logging.cc */; };
|
||||
D244536B12426F00009BBCE0 /* minidump.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535212426EBB009BBCE0 /* minidump.cc */; };
|
||||
D244536C12426F00009BBCE0 /* pathname_stripper.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535312426EBB009BBCE0 /* pathname_stripper.cc */; };
|
||||
|
@ -324,6 +326,13 @@
|
|||
remoteGlobalIDString = D2F9A41412131EF0002747C1;
|
||||
remoteInfo = gtest;
|
||||
};
|
||||
D23F4BB912A8694C00686C8D /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D23F4BAA12A868A500686C8D;
|
||||
remoteInfo = minidump_generator_test_helper;
|
||||
};
|
||||
D2F9A44212131F80002747C1 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
|
||||
|
@ -525,6 +534,8 @@
|
|||
8B4BDAA7120124EA009C7060 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; };
|
||||
8DC2EF5B0486A6940098B216 /* Breakpad.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Breakpad.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D23F4B2C12A7E13200686C8D /* minidump_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_generator_test.cc; path = tests/minidump_generator_test.cc; sourceTree = "<group>"; };
|
||||
D23F4B9A12A8688800686C8D /* minidump_generator_test_helper.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_generator_test_helper.cc; path = tests/minidump_generator_test_helper.cc; sourceTree = "<group>"; };
|
||||
D23F4BAB12A868A500686C8D /* minidump_generator_test_helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = minidump_generator_test_helper; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D244534F12426E98009BBCE0 /* basic_code_modules.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_code_modules.cc; path = ../../processor/basic_code_modules.cc; sourceTree = SOURCE_ROOT; };
|
||||
D244535112426EBB009BBCE0 /* logging.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = logging.cc; path = ../../processor/logging.cc; sourceTree = SOURCE_ROOT; };
|
||||
D244535212426EBB009BBCE0 /* minidump.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump.cc; path = ../../processor/minidump.cc; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -656,6 +667,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D23F4BA912A868A500686C8D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D2F9A41312131EF0002747C1 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -767,6 +785,7 @@
|
|||
F93DE32C0F82C55600608B94 /* handler_test */,
|
||||
D2F9A41512131EF0002747C1 /* libgtest.a */,
|
||||
D2F9A546121383A1002747C1 /* crash_generation_server_test */,
|
||||
D23F4BAB12A868A500686C8D /* minidump_generator_test_helper */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1021,6 +1040,7 @@
|
|||
F9C77DDF0F7DD7CF0045F7DB /* tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D23F4B9A12A8688800686C8D /* minidump_generator_test_helper.cc */,
|
||||
D23F4B2C12A7E13200686C8D /* minidump_generator_test.cc */,
|
||||
D2F9A4CE121336F7002747C1 /* crash_generation_server_test.cc */,
|
||||
D2F9A3D41212F87C002747C1 /* exception_handler_test.cc */,
|
||||
|
@ -1095,6 +1115,22 @@
|
|||
productReference = 8DC2EF5B0486A6940098B216 /* Breakpad.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
D23F4BAA12A868A500686C8D /* minidump_generator_test_helper */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D23F4BB012A868C400686C8D /* Build configuration list for PBXNativeTarget "minidump_generator_test_helper" */;
|
||||
buildPhases = (
|
||||
D23F4BA812A868A500686C8D /* Sources */,
|
||||
D23F4BA912A868A500686C8D /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = minidump_generator_test_helper;
|
||||
productName = minidump_generator_test_helper;
|
||||
productReference = D23F4BAB12A868A500686C8D /* minidump_generator_test_helper */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
D2F9A41412131EF0002747C1 /* gtest */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D2F9A42D12131F0E002747C1 /* Build configuration list for PBXNativeTarget "gtest" */;
|
||||
|
@ -1192,6 +1228,7 @@
|
|||
);
|
||||
dependencies = (
|
||||
D23F4B3012A7E16200686C8D /* PBXTargetDependency */,
|
||||
D23F4BBA12A8694C00686C8D /* PBXTargetDependency */,
|
||||
);
|
||||
name = generator_test;
|
||||
productName = generator_test;
|
||||
|
@ -1325,6 +1362,7 @@
|
|||
F93DE32B0F82C55600608B94 /* handler_test */,
|
||||
D2F9A41412131EF0002747C1 /* gtest */,
|
||||
D2F9A52A121383A1002747C1 /* crash_generation_server_test */,
|
||||
D23F4BAA12A868A500686C8D /* minidump_generator_test_helper */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -1559,6 +1597,15 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D23F4BA812A868A500686C8D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D23F4BB112A868CB00686C8D /* minidump_generator_test_helper.cc in Sources */,
|
||||
D23F4BB812A868F700686C8D /* MachIPC.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D2F9A41212131EF0002747C1 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -1756,6 +1803,11 @@
|
|||
target = D2F9A41412131EF0002747C1 /* gtest */;
|
||||
targetProxy = D23F4B2F12A7E16200686C8D /* PBXContainerItemProxy */;
|
||||
};
|
||||
D23F4BBA12A8694C00686C8D /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D23F4BAA12A868A500686C8D /* minidump_generator_test_helper */;
|
||||
targetProxy = D23F4BB912A8694C00686C8D /* PBXContainerItemProxy */;
|
||||
};
|
||||
D2F9A44312131F80002747C1 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D2F9A41412131EF0002747C1 /* gtest */;
|
||||
|
@ -1953,6 +2005,10 @@
|
|||
baseConfigurationReference = 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */;
|
||||
buildSettings = {
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = NO;
|
||||
SDKROOT = macosx10.5;
|
||||
"SDKROOT[arch=i386]" = macosx10.4;
|
||||
"SDKROOT[arch=ppc]" = macosx10.4;
|
||||
"SDKROOT[arch=x86_64]" = macosx10.6;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -1964,6 +2020,49 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
D23F4BAD12A868A600686C8D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
HEADER_SEARCH_PATHS = ../..;
|
||||
INSTALL_PATH = /usr/local/bin;
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_generator_test_helper;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D23F4BAE12A868A600686C8D /* Debug With Code Coverage */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
INSTALL_PATH = /usr/local/bin;
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_generator_test_helper;
|
||||
};
|
||||
name = "Debug With Code Coverage";
|
||||
};
|
||||
D23F4BAF12A868A600686C8D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
INSTALL_PATH = /usr/local/bin;
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_generator_test_helper;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
D2F9A41612131EF0002747C1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -2387,6 +2486,16 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D23F4BB012A868C400686C8D /* Build configuration list for PBXNativeTarget "minidump_generator_test_helper" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D23F4BAD12A868A600686C8D /* Debug */,
|
||||
D23F4BAE12A868A600686C8D /* Debug With Code Coverage */,
|
||||
D23F4BAF12A868A600686C8D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D2F9A42D12131F0E002747C1 /* Build configuration list for PBXNativeTarget "gtest" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
*/
|
||||
|
||||
|
||||
/* nealsid:
|
||||
/*
|
||||
* This file was copied from libc/gen/nlist.c from Darwin's source code
|
||||
* The version of nlist used as a base is from 10.5.2, libc-498
|
||||
* http://www.opensource.apple.com/darwinsource/10.5.2/Libc-498/gen/nlist.c
|
||||
|
@ -62,24 +62,22 @@
|
|||
* The full tarball is at:
|
||||
* http://www.opensource.apple.com/darwinsource/tarballs/apsl/Libc-498.tar.gz
|
||||
*
|
||||
* I've modified it to be compatible with 64-bit images. However,
|
||||
* 32-bit compatibility has not been retained.
|
||||
* I've modified it to be compatible with 64-bit images.
|
||||
*/
|
||||
|
||||
#ifdef __LP64__
|
||||
#include "breakpad_nlist_64.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <mach/mach.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
#include "breakpad_nlist_64.h"
|
||||
#include <TargetConditionals.h>
|
||||
#include <stdio.h>
|
||||
#include <mach/mach.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Stuff lifted from <a.out.h> and <sys/exec.h> since they are gone */
|
||||
/*
|
||||
|
@ -108,44 +106,77 @@ struct exec {
|
|||
#define N_SYMOFF(x) \
|
||||
(N_TXTOFF(x) + (x).a_text+(x).a_data + (x).a_trsize+(x).a_drsize)
|
||||
|
||||
// Traits structs for specializing function templates to handle
|
||||
// 32-bit/64-bit Mach-O files.
|
||||
template<typename T>
|
||||
struct MachBits {};
|
||||
|
||||
typedef struct nlist nlist32;
|
||||
typedef struct nlist_64 nlist64;
|
||||
|
||||
template<>
|
||||
struct MachBits<nlist32> {
|
||||
typedef mach_header mach_header_type;
|
||||
typedef uint32_t word_type;
|
||||
static const uint32_t magic = MH_MAGIC;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct MachBits<nlist64> {
|
||||
typedef mach_header_64 mach_header_type;
|
||||
typedef uint64_t word_type;
|
||||
static const uint32_t magic = MH_MAGIC_64;
|
||||
};
|
||||
|
||||
template<typename nlist_type>
|
||||
int
|
||||
__breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames);
|
||||
__breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames,
|
||||
cpu_type_t cpu_type);
|
||||
|
||||
/*
|
||||
* nlist - retreive attributes from name list (string table version)
|
||||
*/
|
||||
|
||||
int
|
||||
breakpad_nlist_64(const char *name,
|
||||
breakpad_nlist *list,
|
||||
const char **symbolNames) {
|
||||
int fd, n;
|
||||
|
||||
fd = open(name, O_RDONLY, 0);
|
||||
template <typename nlist_type>
|
||||
int breakpad_nlist_common(const char *name,
|
||||
nlist_type *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type) {
|
||||
int fd = open(name, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return (-1);
|
||||
n = __breakpad_fdnlist_64(fd, list, symbolNames);
|
||||
(void)close(fd);
|
||||
return (n);
|
||||
return -1;
|
||||
int n = __breakpad_fdnlist(fd, list, symbolNames, cpu_type);
|
||||
close(fd);
|
||||
return n;
|
||||
}
|
||||
|
||||
int breakpad_nlist(const char *name,
|
||||
struct nlist *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type) {
|
||||
return breakpad_nlist_common(name, list, symbolNames, cpu_type);
|
||||
}
|
||||
|
||||
int breakpad_nlist(const char *name,
|
||||
struct nlist_64 *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type) {
|
||||
return breakpad_nlist_common(name, list, symbolNames, cpu_type);
|
||||
}
|
||||
|
||||
/* Note: __fdnlist() is called from kvm_nlist in libkvm's kvm.c */
|
||||
|
||||
int
|
||||
__breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames) {
|
||||
register breakpad_nlist *p, *q;
|
||||
breakpad_nlist space[BUFSIZ/sizeof (breakpad_nlist)];
|
||||
template<typename nlist_type>
|
||||
int __breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames,
|
||||
cpu_type_t cpu_type) {
|
||||
typedef typename MachBits<nlist_type>::mach_header_type mach_header_type;
|
||||
typedef typename MachBits<nlist_type>::word_type word_type;
|
||||
|
||||
const register char *s1, *s2;
|
||||
register register_t n, m;
|
||||
int maxlen, nreq;
|
||||
off_t sa; /* symbol address */
|
||||
off_t ss; /* start of strings */
|
||||
struct exec buf;
|
||||
unsigned arch_offset = 0;
|
||||
const uint32_t magic = MachBits<nlist_type>::magic;
|
||||
|
||||
maxlen = 500;
|
||||
for (q = list, nreq = 0;
|
||||
int maxlen = 500;
|
||||
int nreq = 0;
|
||||
for (nlist_type* q = list;
|
||||
symbolNames[q-list] && symbolNames[q-list][0];
|
||||
q++, nreq++) {
|
||||
|
||||
|
@ -156,61 +187,61 @@ __breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames) {
|
|||
q->n_un.n_strx = 0;
|
||||
}
|
||||
|
||||
struct exec buf;
|
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf) ||
|
||||
(N_BADMAG(buf) && *((long *)&buf) != MH_MAGIC &&
|
||||
(N_BADMAG(buf) && *((long *)&buf) != magic &&
|
||||
NXSwapBigLongToHost(*((long *)&buf)) != FAT_MAGIC) &&
|
||||
/* nealsid: The following is the big-endian ppc64 check */
|
||||
/* The following is the big-endian ppc64 check */
|
||||
(*((long*)&buf)) != FAT_MAGIC) {
|
||||
return (-1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Deal with fat file if necessary */
|
||||
unsigned arch_offset = 0;
|
||||
if (NXSwapBigLongToHost(*((long *)&buf)) == FAT_MAGIC ||
|
||||
/* nealsid: The following is the big-endian ppc64 check */
|
||||
/* The following is the big-endian ppc64 check */
|
||||
*((unsigned int *)&buf) == FAT_MAGIC) {
|
||||
/* Get host info */
|
||||
host_t host = mach_host_self();
|
||||
unsigned i = HOST_BASIC_INFO_COUNT;
|
||||
struct host_basic_info hbi;
|
||||
struct fat_header fh;
|
||||
struct fat_arch *fat_archs, *fap;
|
||||
unsigned i;
|
||||
host_t host;
|
||||
|
||||
/* Get our host info */
|
||||
host = mach_host_self();
|
||||
i = HOST_BASIC_INFO_COUNT;
|
||||
kern_return_t kr;
|
||||
if ((kr=host_info(host, HOST_BASIC_INFO,
|
||||
(host_info_t)(&hbi), &i)) != KERN_SUCCESS) {
|
||||
return (-1);
|
||||
if ((kr = host_info(host, HOST_BASIC_INFO,
|
||||
(host_info_t)(&hbi), &i)) != KERN_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
mach_port_deallocate(mach_task_self(), host);
|
||||
|
||||
/* Read in the fat header */
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
struct fat_header fh;
|
||||
if (lseek(fd, 0, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, (char *)&fh, sizeof(fh)) != sizeof(fh)) {
|
||||
return (-1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Convert fat_narchs to host byte order */
|
||||
fh.nfat_arch = NXSwapBigIntToHost(fh.nfat_arch);
|
||||
|
||||
/* Read in the fat archs */
|
||||
fat_archs = (struct fat_arch *)malloc(fh.nfat_arch *
|
||||
sizeof(struct fat_arch));
|
||||
struct fat_arch *fat_archs =
|
||||
(struct fat_arch *)malloc(fh.nfat_arch * sizeof(struct fat_arch));
|
||||
if (fat_archs == NULL) {
|
||||
return (-1);
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, (char *)fat_archs,
|
||||
sizeof(struct fat_arch) * fh.nfat_arch) !=
|
||||
(ssize_t)sizeof(struct fat_arch) * fh.nfat_arch) {
|
||||
free(fat_archs);
|
||||
return (-1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert archs to host byte ordering (a constraint of
|
||||
* cpusubtype_getbestarch()
|
||||
*/
|
||||
for (i = 0; i < fh.nfat_arch; i++) {
|
||||
for (unsigned i = 0; i < fh.nfat_arch; i++) {
|
||||
fat_archs[i].cputype =
|
||||
NXSwapBigIntToHost(fat_archs[i].cputype);
|
||||
fat_archs[i].cpusubtype =
|
||||
|
@ -223,159 +254,159 @@ __breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames) {
|
|||
NXSwapBigIntToHost(fat_archs[i].align);
|
||||
}
|
||||
|
||||
fap = NULL;
|
||||
for (i = 0; i < fh.nfat_arch; i++) {
|
||||
/* nealsid: Although the original Apple code uses host_info */
|
||||
/* to retrieve the CPU type, the host_info will still return */
|
||||
/* CPU_TYPE_X86 even if running as an x86_64 binary. Given that */
|
||||
/* this code isn't necessary on i386, I've decided to hardcode */
|
||||
/* looking for a 64-bit binary */
|
||||
#if TARGET_CPU_X86_64
|
||||
if (fat_archs[i].cputype == CPU_TYPE_X86_64) {
|
||||
#elif TARGET_CPU_PPC64
|
||||
if (fat_archs[i].cputype == CPU_TYPE_POWERPC64) {
|
||||
#else
|
||||
#error undefined cpu!
|
||||
{
|
||||
#endif
|
||||
fap = &fat_archs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fap) {
|
||||
free(fat_archs);
|
||||
return (-1);
|
||||
}
|
||||
arch_offset = fap->offset;
|
||||
free(fat_archs);
|
||||
|
||||
/* Read in the beginning of the architecture-specific file */
|
||||
lseek(fd, arch_offset, SEEK_SET);
|
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf)) {
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (*((unsigned int *)&buf) == MH_MAGIC_64) {
|
||||
struct mach_header_64 mh;
|
||||
struct load_command *load_commands, *lcp;
|
||||
struct symtab_command *stp;
|
||||
long i;
|
||||
|
||||
lseek(fd, arch_offset, SEEK_SET);
|
||||
if (read(fd, (char *)&mh, sizeof(mh)) != sizeof(mh)) {
|
||||
return (-1);
|
||||
}
|
||||
load_commands = (struct load_command *)malloc(mh.sizeofcmds);
|
||||
if (load_commands == NULL) {
|
||||
return (-1);
|
||||
}
|
||||
if (read(fd, (char *)load_commands, mh.sizeofcmds) !=
|
||||
mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
stp = NULL;
|
||||
lcp = load_commands;
|
||||
// nealsid:iterate through all load commands, looking for
|
||||
// LC_SYMTAB load command
|
||||
for (i = 0; i < mh.ncmds; i++) {
|
||||
if (lcp->cmdsize % sizeof(long) != 0 ||
|
||||
lcp->cmdsize <= 0 ||
|
||||
(char *)lcp + lcp->cmdsize >
|
||||
(char *)load_commands + mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
if (lcp->cmd == LC_SYMTAB) {
|
||||
if (lcp->cmdsize !=
|
||||
sizeof(struct symtab_command)) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
stp = (struct symtab_command *)lcp;
|
||||
break;
|
||||
}
|
||||
lcp = (struct load_command *)
|
||||
((char *)lcp + lcp->cmdsize);
|
||||
}
|
||||
if (stp == NULL) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
// sa points to the beginning of the symbol table
|
||||
sa = stp->symoff + arch_offset;
|
||||
// ss points to the beginning of the string table
|
||||
ss = stp->stroff + arch_offset;
|
||||
// n is the number of bytes in the symbol table
|
||||
// each symbol table entry is an nlist structure
|
||||
n = stp->nsyms * sizeof(breakpad_nlist);
|
||||
free(load_commands);
|
||||
}
|
||||
else {
|
||||
sa = N_SYMOFF(buf) + arch_offset;
|
||||
ss = sa + buf.a_syms + arch_offset;
|
||||
n = buf.a_syms;
|
||||
}
|
||||
|
||||
lseek(fd, sa, SEEK_SET);
|
||||
|
||||
// the algorithm here is to read the nlist entries in m-sized
|
||||
// chunks into q. q is then iterated over. for each entry in q,
|
||||
// use the string table index(q->n_un.n_strx) to read the symbol
|
||||
// name, then scan the nlist entries passed in by the user(via p),
|
||||
// and look for a match
|
||||
while (n) {
|
||||
long savpos;
|
||||
|
||||
m = sizeof (space);
|
||||
if (n < m)
|
||||
m = n;
|
||||
if (read(fd, (char *)space, m) != m)
|
||||
struct fat_arch *fap = NULL;
|
||||
for (unsigned i = 0; i < fh.nfat_arch; i++) {
|
||||
if (fat_archs[i].cputype == cpu_type) {
|
||||
fap = &fat_archs[i];
|
||||
break;
|
||||
n -= m;
|
||||
savpos = lseek(fd, 0, SEEK_CUR);
|
||||
for (q = space; (m -= sizeof(breakpad_nlist)) >= 0; q++) {
|
||||
char nambuf[BUFSIZ];
|
||||
|
||||
if (q->n_un.n_strx == 0 || q->n_type & N_STAB)
|
||||
continue;
|
||||
|
||||
// seek to the location in the binary where the symbol
|
||||
// name is stored & read it into memory
|
||||
lseek(fd, ss+q->n_un.n_strx, SEEK_SET);
|
||||
read(fd, nambuf, maxlen+1);
|
||||
s2 = nambuf;
|
||||
for (p = list;
|
||||
symbolNames[p-list] &&
|
||||
symbolNames[p-list][0];
|
||||
p++) {
|
||||
// get the symbol name the user has passed in that
|
||||
// corresponds to the nlist entry that we're looking at
|
||||
s1 = symbolNames[p - list];
|
||||
while (*s1) {
|
||||
if (*s1++ != *s2++)
|
||||
goto cont;
|
||||
}
|
||||
if (*s2)
|
||||
goto cont;
|
||||
|
||||
p->n_value = q->n_value;
|
||||
p->n_type = q->n_type;
|
||||
p->n_desc = q->n_desc;
|
||||
p->n_sect = q->n_sect;
|
||||
p->n_un.n_strx = q->n_un.n_strx;
|
||||
if (--nreq == 0)
|
||||
return (nreq);
|
||||
|
||||
break;
|
||||
cont: ;
|
||||
}
|
||||
}
|
||||
lseek(fd, savpos, SEEK_SET);
|
||||
}
|
||||
return (nreq);
|
||||
|
||||
if (!fap) {
|
||||
free(fat_archs);
|
||||
return -1;
|
||||
}
|
||||
arch_offset = fap->offset;
|
||||
free(fat_archs);
|
||||
|
||||
/* Read in the beginning of the architecture-specific file */
|
||||
if (lseek(fd, arch_offset, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __LP64__ */
|
||||
off_t sa; /* symbol address */
|
||||
off_t ss; /* start of strings */
|
||||
register register_t n;
|
||||
if (*((unsigned int *)&buf) == magic) {
|
||||
if (lseek(fd, arch_offset, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
mach_header_type mh;
|
||||
if (read(fd, (char *)&mh, sizeof(mh)) != sizeof(mh)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct load_command *load_commands =
|
||||
(struct load_command *)malloc(mh.sizeofcmds);
|
||||
if (load_commands == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, (char *)load_commands, mh.sizeofcmds) !=
|
||||
mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return -1;
|
||||
}
|
||||
struct symtab_command *stp = NULL;
|
||||
struct load_command *lcp = load_commands;
|
||||
// iterate through all load commands, looking for
|
||||
// LC_SYMTAB load command
|
||||
for (long i = 0; i < mh.ncmds; i++) {
|
||||
if (lcp->cmdsize % sizeof(word_type) != 0 ||
|
||||
lcp->cmdsize <= 0 ||
|
||||
(char *)lcp + lcp->cmdsize >
|
||||
(char *)load_commands + mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return -1;
|
||||
}
|
||||
if (lcp->cmd == LC_SYMTAB) {
|
||||
if (lcp->cmdsize !=
|
||||
sizeof(struct symtab_command)) {
|
||||
free(load_commands);
|
||||
return -1;
|
||||
}
|
||||
stp = (struct symtab_command *)lcp;
|
||||
break;
|
||||
}
|
||||
lcp = (struct load_command *)
|
||||
((char *)lcp + lcp->cmdsize);
|
||||
}
|
||||
if (stp == NULL) {
|
||||
free(load_commands);
|
||||
return -1;
|
||||
}
|
||||
// sa points to the beginning of the symbol table
|
||||
sa = stp->symoff + arch_offset;
|
||||
// ss points to the beginning of the string table
|
||||
ss = stp->stroff + arch_offset;
|
||||
// n is the number of bytes in the symbol table
|
||||
// each symbol table entry is an nlist structure
|
||||
n = stp->nsyms * sizeof(nlist_type);
|
||||
free(load_commands);
|
||||
} else {
|
||||
sa = N_SYMOFF(buf) + arch_offset;
|
||||
ss = sa + buf.a_syms + arch_offset;
|
||||
n = buf.a_syms;
|
||||
}
|
||||
|
||||
if (lseek(fd, sa, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// the algorithm here is to read the nlist entries in m-sized
|
||||
// chunks into q. q is then iterated over. for each entry in q,
|
||||
// use the string table index(q->n_un.n_strx) to read the symbol
|
||||
// name, then scan the nlist entries passed in by the user(via p),
|
||||
// and look for a match
|
||||
while (n) {
|
||||
nlist_type space[BUFSIZ/sizeof (nlist_type)];
|
||||
register register_t m = sizeof (space);
|
||||
|
||||
if (n < m)
|
||||
m = n;
|
||||
if (read(fd, (char *)space, m) != m)
|
||||
break;
|
||||
n -= m;
|
||||
long savpos = lseek(fd, 0, SEEK_CUR);
|
||||
if (savpos == -1) {
|
||||
return -1;
|
||||
}
|
||||
for (nlist_type* q = space; (m -= sizeof(nlist_type)) >= 0; q++) {
|
||||
char nambuf[BUFSIZ];
|
||||
|
||||
if (q->n_un.n_strx == 0 || q->n_type & N_STAB)
|
||||
continue;
|
||||
|
||||
// seek to the location in the binary where the symbol
|
||||
// name is stored & read it into memory
|
||||
if (lseek(fd, ss+q->n_un.n_strx, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, nambuf, maxlen+1) == -1) {
|
||||
return -1;
|
||||
}
|
||||
const char *s2 = nambuf;
|
||||
for (nlist_type *p = list;
|
||||
symbolNames[p-list] && symbolNames[p-list][0];
|
||||
p++) {
|
||||
// get the symbol name the user has passed in that
|
||||
// corresponds to the nlist entry that we're looking at
|
||||
const char *s1 = symbolNames[p - list];
|
||||
while (*s1) {
|
||||
if (*s1++ != *s2++)
|
||||
goto cont;
|
||||
}
|
||||
if (*s2)
|
||||
goto cont;
|
||||
|
||||
p->n_value = q->n_value;
|
||||
p->n_type = q->n_type;
|
||||
p->n_desc = q->n_desc;
|
||||
p->n_sect = q->n_sect;
|
||||
p->n_un.n_strx = q->n_un.n_strx;
|
||||
if (--nreq == 0)
|
||||
return nreq;
|
||||
|
||||
break;
|
||||
cont: ;
|
||||
}
|
||||
}
|
||||
if (lseek(fd, savpos, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return nreq;
|
||||
}
|
||||
|
|
|
@ -33,11 +33,15 @@
|
|||
|
||||
#ifndef CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__
|
||||
|
||||
typedef struct nlist_64 breakpad_nlist;
|
||||
#include <mach/machine.h>
|
||||
|
||||
int
|
||||
breakpad_nlist_64(const char *name,
|
||||
breakpad_nlist *list,
|
||||
const char **symbolNames);
|
||||
int breakpad_nlist(const char *name,
|
||||
struct nlist *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type);
|
||||
int breakpad_nlist(const char *name,
|
||||
struct nlist_64 *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type);
|
||||
|
||||
#endif /* CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__ */
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
// (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 "client/mac/handler/dynamic_images.h"
|
||||
|
||||
extern "C" { // needed to compile on Leopard
|
||||
#include <mach-o/nlist.h>
|
||||
#include <stdlib.h>
|
||||
|
@ -37,11 +39,17 @@ extern "C" { // needed to compile on Leopard
|
|||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include "client/mac/handler/dynamic_images.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
//==============================================================================
|
||||
// Returns the size of the memory region containing |address| and the
|
||||
// number of bytes from |address| to the end of the region.
|
||||
|
@ -51,7 +59,7 @@ namespace google_breakpad {
|
|||
// straddle two vm regions.
|
||||
//
|
||||
static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
|
||||
const void* address,
|
||||
const uint64_t address,
|
||||
mach_vm_size_t *size_to_end) {
|
||||
mach_vm_address_t region_base = (mach_vm_address_t)address;
|
||||
mach_vm_size_t region_size;
|
||||
|
@ -116,8 +124,8 @@ static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
|
|||
//
|
||||
// Warning! This will not read any strings longer than kMaxStringLength-1
|
||||
//
|
||||
static void* ReadTaskString(task_port_t target_task,
|
||||
const void* address) {
|
||||
static string ReadTaskString(task_port_t target_task,
|
||||
const uint64_t address) {
|
||||
// The problem is we don't know how much to read until we know how long
|
||||
// the string is. And we don't know how long the string is, until we've read
|
||||
// the memory! So, we'll try to read kMaxStringLength bytes
|
||||
|
@ -129,68 +137,140 @@ static void* ReadTaskString(task_port_t target_task,
|
|||
mach_vm_size_t size_to_read =
|
||||
size_to_end > kMaxStringLength ? kMaxStringLength : size_to_end;
|
||||
|
||||
kern_return_t kr;
|
||||
return ReadTaskMemory(target_task, address, (size_t)size_to_read, &kr);
|
||||
vector<uint8_t> bytes;
|
||||
if (ReadTaskMemory(target_task, address, (size_t)size_to_read, bytes) !=
|
||||
KERN_SUCCESS)
|
||||
return string();
|
||||
|
||||
return string(reinterpret_cast<const char*>(&bytes[0]));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return string();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Reads an address range from another task. A block of memory is malloced
|
||||
// and should be freed by the caller.
|
||||
void* ReadTaskMemory(task_port_t target_task,
|
||||
const void* address,
|
||||
size_t length,
|
||||
kern_return_t *kr) {
|
||||
void* result = NULL;
|
||||
// Reads an address range from another task. The bytes read will be returned
|
||||
// in bytes, which will be resized as necessary.
|
||||
kern_return_t ReadTaskMemory(task_port_t target_task,
|
||||
const uint64_t address,
|
||||
size_t length,
|
||||
vector<uint8_t> &bytes) {
|
||||
int systemPageSize = getpagesize();
|
||||
|
||||
// use the negative of the page size for the mask to find the page address
|
||||
mach_vm_address_t page_address =
|
||||
reinterpret_cast<mach_vm_address_t>(address) & (-systemPageSize);
|
||||
mach_vm_address_t page_address = address & (-systemPageSize);
|
||||
|
||||
mach_vm_address_t last_page_address =
|
||||
(reinterpret_cast<mach_vm_address_t>(address) + length +
|
||||
(systemPageSize - 1)) & (-systemPageSize);
|
||||
(address + length + (systemPageSize - 1)) & (-systemPageSize);
|
||||
|
||||
mach_vm_size_t page_size = last_page_address - page_address;
|
||||
uint8_t* local_start;
|
||||
uint32_t local_length;
|
||||
|
||||
kern_return_t r;
|
||||
kern_return_t r = mach_vm_read(target_task,
|
||||
page_address,
|
||||
page_size,
|
||||
reinterpret_cast<vm_offset_t*>(&local_start),
|
||||
&local_length);
|
||||
|
||||
r = mach_vm_read(target_task,
|
||||
page_address,
|
||||
page_size,
|
||||
reinterpret_cast<vm_offset_t*>(&local_start),
|
||||
&local_length);
|
||||
if (r != KERN_SUCCESS)
|
||||
return r;
|
||||
|
||||
|
||||
if (kr != NULL) {
|
||||
*kr = r;
|
||||
}
|
||||
|
||||
if (r == KERN_SUCCESS) {
|
||||
result = malloc(length);
|
||||
if (result != NULL) {
|
||||
memcpy(result,
|
||||
&local_start[(mach_vm_address_t)address - page_address],
|
||||
length);
|
||||
}
|
||||
mach_vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length);
|
||||
}
|
||||
|
||||
return result;
|
||||
bytes.resize(length);
|
||||
memcpy(&bytes[0],
|
||||
&local_start[(mach_vm_address_t)address - page_address],
|
||||
length);
|
||||
mach_vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length);
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
//==============================================================================
|
||||
// Traits structs for specializing function templates to handle
|
||||
// 32-bit/64-bit Mach-O files.
|
||||
struct MachO32 {
|
||||
typedef mach_header mach_header_type;
|
||||
typedef segment_command mach_segment_command_type;
|
||||
typedef dyld_image_info32 dyld_image_info;
|
||||
typedef dyld_all_image_infos32 dyld_all_image_infos;
|
||||
typedef struct nlist nlist_type;
|
||||
static const uint32_t magic = MH_MAGIC;
|
||||
static const uint32_t segment_load_command = LC_SEGMENT;
|
||||
};
|
||||
|
||||
struct MachO64 {
|
||||
typedef mach_header_64 mach_header_type;
|
||||
typedef segment_command_64 mach_segment_command_type;
|
||||
typedef dyld_image_info64 dyld_image_info;
|
||||
typedef dyld_all_image_infos64 dyld_all_image_infos;
|
||||
typedef struct nlist_64 nlist_type;
|
||||
static const uint32_t magic = MH_MAGIC_64;
|
||||
static const uint32_t segment_load_command = LC_SEGMENT_64;
|
||||
};
|
||||
|
||||
template<typename MachBits>
|
||||
bool FindTextSection(DynamicImage& image) {
|
||||
typedef typename MachBits::mach_header_type mach_header_type;
|
||||
typedef typename MachBits::mach_segment_command_type
|
||||
mach_segment_command_type;
|
||||
|
||||
const mach_header_type* header =
|
||||
reinterpret_cast<const mach_header_type*>(&image.header_[0]);
|
||||
|
||||
if(header->magic != MachBits::magic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const struct load_command *cmd =
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
bool found_text_section = false;
|
||||
bool found_dylib_id_command = false;
|
||||
for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) {
|
||||
if (!found_text_section) {
|
||||
if (cmd->cmd == MachBits::segment_load_command) {
|
||||
const mach_segment_command_type *seg =
|
||||
reinterpret_cast<const mach_segment_command_type *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
image.vmaddr_ = seg->vmaddr;
|
||||
image.vmsize_ = seg->vmsize;
|
||||
image.slide_ = 0;
|
||||
|
||||
if (seg->fileoff == 0 && seg->filesize != 0) {
|
||||
image.slide_ =
|
||||
(uintptr_t)image.GetLoadAddress() - (uintptr_t)seg->vmaddr;
|
||||
}
|
||||
found_text_section = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_dylib_id_command) {
|
||||
if (cmd->cmd == LC_ID_DYLIB) {
|
||||
const struct dylib_command *dc =
|
||||
reinterpret_cast<const struct dylib_command *>(cmd);
|
||||
|
||||
image.version_ = dc->dylib.current_version;
|
||||
found_dylib_id_command = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_dylib_id_command && found_text_section) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cmd = reinterpret_cast<const struct load_command *>
|
||||
(reinterpret_cast<const char *>(cmd) + cmd->cmdsize);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Initializes vmaddr_, vmsize_, and slide_
|
||||
void DynamicImage::CalculateMemoryAndVersionInfo() {
|
||||
breakpad_mach_header *header = GetMachHeader();
|
||||
|
||||
// unless we can process the header, ensure that calls to
|
||||
// IsValid() will return false
|
||||
vmaddr_ = 0;
|
||||
|
@ -198,78 +278,29 @@ void DynamicImage::CalculateMemoryAndVersionInfo() {
|
|||
slide_ = 0;
|
||||
version_ = 0;
|
||||
|
||||
bool foundTextSection = false;
|
||||
bool foundDylibIDCommand = false;
|
||||
|
||||
#if __LP64__
|
||||
if(header->magic != MH_MAGIC_64) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if(header->magic != MH_MAGIC) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __LP64__
|
||||
const uint32_t segmentLoadCommand = LC_SEGMENT_64;
|
||||
#else
|
||||
const uint32_t segmentLoadCommand = LC_SEGMENT;
|
||||
#endif
|
||||
|
||||
const struct load_command *cmd =
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) {
|
||||
if (!foundTextSection) {
|
||||
if (cmd->cmd == segmentLoadCommand) {
|
||||
const breakpad_mach_segment_command *seg =
|
||||
reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
vmaddr_ = seg->vmaddr;
|
||||
vmsize_ = seg->vmsize;
|
||||
slide_ = 0;
|
||||
|
||||
if (seg->fileoff == 0 && seg->filesize != 0) {
|
||||
slide_ = (uintptr_t)GetLoadAddress() - (uintptr_t)seg->vmaddr;
|
||||
}
|
||||
foundTextSection = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundDylibIDCommand) {
|
||||
if (cmd->cmd == LC_ID_DYLIB) {
|
||||
const struct dylib_command *dc =
|
||||
reinterpret_cast<const struct dylib_command *>(cmd);
|
||||
|
||||
version_ = dc->dylib.current_version;
|
||||
foundDylibIDCommand = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDylibIDCommand && foundTextSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd = reinterpret_cast<const struct load_command *>
|
||||
(reinterpret_cast<const char *>(cmd) + cmd->cmdsize);
|
||||
}
|
||||
|
||||
// The function template above does all the real work.
|
||||
if (Is64Bit())
|
||||
FindTextSection<MachO64>(*this);
|
||||
else
|
||||
FindTextSection<MachO32>(*this);
|
||||
}
|
||||
|
||||
void DynamicImage::Print() {
|
||||
const char *path = GetFilePath();
|
||||
if (!path) {
|
||||
path = "(unknown)";
|
||||
}
|
||||
printf("%p: %s\n", GetLoadAddress(), path);
|
||||
breakpad_mach_header *header = GetMachHeader();
|
||||
MachHeader(*header).Print();
|
||||
printf("vmaddr\t\t: %p\n", reinterpret_cast<void*>(GetVMAddr()));
|
||||
printf("vmsize\t\t: %llu\n", GetVMSize());
|
||||
printf("slide\t\t: %td\n", GetVMAddrSlide());
|
||||
//==============================================================================
|
||||
// The helper function template abstracts the 32/64-bit differences.
|
||||
template<typename MachBits>
|
||||
uint32_t GetFileTypeFromHeader(DynamicImage& image) {
|
||||
typedef typename MachBits::mach_header_type mach_header_type;
|
||||
|
||||
const mach_header_type* header =
|
||||
reinterpret_cast<const mach_header_type*>(&image.header_[0]);
|
||||
return header->filetype;
|
||||
}
|
||||
|
||||
uint32_t DynamicImage::GetFileType() {
|
||||
if (Is64Bit())
|
||||
return GetFileTypeFromHeader<MachO64>(*this);
|
||||
|
||||
return GetFileTypeFromHeader<MachO32>(*this);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
@ -277,144 +308,158 @@ void DynamicImage::Print() {
|
|||
//==============================================================================
|
||||
// Loads information about dynamically loaded code in the given task.
|
||||
DynamicImages::DynamicImages(mach_port_t task)
|
||||
: task_(task), image_list_() {
|
||||
: task_(task),
|
||||
cpu_type_(DetermineTaskCPUType(task)),
|
||||
image_list_() {
|
||||
ReadImageInfoForTask();
|
||||
}
|
||||
|
||||
void* DynamicImages::GetDyldAllImageInfosPointer() {
|
||||
const char *imageSymbolName = "_dyld_all_image_infos";
|
||||
const char *dyldPath = "/usr/lib/dyld";
|
||||
#ifndef __LP64__
|
||||
struct nlist l[8];
|
||||
memset(l, 0, sizeof(l) );
|
||||
template<typename MachBits>
|
||||
static uint64_t LookupSymbol(const char* symbol_name,
|
||||
const char* filename,
|
||||
cpu_type_t cpu_type) {
|
||||
typedef typename MachBits::nlist_type nlist_type;
|
||||
|
||||
// First we lookup the address of the "_dyld_all_image_infos" struct
|
||||
// which lives in "dyld". This structure contains information about all
|
||||
// of the loaded dynamic images.
|
||||
struct nlist &list = l[0];
|
||||
list.n_un.n_name = const_cast<char *>(imageSymbolName);
|
||||
nlist(dyldPath,&list);
|
||||
if(list.n_value) {
|
||||
return reinterpret_cast<void*>(list.n_value);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
#else
|
||||
struct nlist_64 l[8];
|
||||
struct nlist_64 &list = l[0];
|
||||
|
||||
memset(l, 0, sizeof(l) );
|
||||
|
||||
const char *symbolNames[2] = { imageSymbolName, "\0" };
|
||||
|
||||
int invalidEntriesCount = breakpad_nlist_64(dyldPath,&list,symbolNames);
|
||||
nlist_type symbol_info[8] = {};
|
||||
const char *symbolNames[2] = { symbol_name, "\0" };
|
||||
nlist_type &list = symbol_info[0];
|
||||
int invalidEntriesCount = breakpad_nlist(filename,
|
||||
&list,
|
||||
symbolNames,
|
||||
cpu_type);
|
||||
|
||||
if(invalidEntriesCount != 0) {
|
||||
return NULL;
|
||||
return 0;
|
||||
}
|
||||
assert(list.n_value);
|
||||
return reinterpret_cast<void*>(list.n_value);
|
||||
#endif
|
||||
|
||||
assert(list.n_value);
|
||||
return list.n_value;
|
||||
}
|
||||
|
||||
uint64_t DynamicImages::GetDyldAllImageInfosPointer() {
|
||||
const char *imageSymbolName = "_dyld_all_image_infos";
|
||||
const char *dyldPath = "/usr/lib/dyld";
|
||||
|
||||
if (Is64Bit())
|
||||
return LookupSymbol<MachO64>(imageSymbolName, dyldPath, cpu_type_);
|
||||
return LookupSymbol<MachO32>(imageSymbolName, dyldPath, cpu_type_);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// This code was written using dyld_debug.c (from Darwin) as a guide.
|
||||
void DynamicImages::ReadImageInfoForTask() {
|
||||
void *imageList = GetDyldAllImageInfosPointer();
|
||||
|
||||
if (imageList) {
|
||||
kern_return_t kr;
|
||||
// Read the structure inside of dyld that contains information about
|
||||
// loaded images. We're reading from the desired task's address space.
|
||||
template<typename MachBits>
|
||||
void ReadImageInfo(DynamicImages& images,
|
||||
uint64_t image_list_address) {
|
||||
typedef typename MachBits::dyld_image_info dyld_image_info;
|
||||
typedef typename MachBits::dyld_all_image_infos dyld_all_image_infos;
|
||||
typedef typename MachBits::mach_header_type mach_header_type;
|
||||
|
||||
// Here we make the assumption that dyld loaded at the same address in
|
||||
// the crashed process vs. this one. This is an assumption made in
|
||||
// "dyld_debug.c" and is said to be nearly always valid.
|
||||
dyld_all_image_infos *dyldInfo = reinterpret_cast<dyld_all_image_infos*>
|
||||
(ReadTaskMemory(task_,
|
||||
reinterpret_cast<void*>(imageList),
|
||||
sizeof(dyld_all_image_infos), &kr));
|
||||
// Read the structure inside of dyld that contains information about
|
||||
// loaded images. We're reading from the desired task's address space.
|
||||
|
||||
if (dyldInfo) {
|
||||
// number of loaded images
|
||||
int count = dyldInfo->infoArrayCount;
|
||||
// Here we make the assumption that dyld loaded at the same address in
|
||||
// the crashed process vs. this one. This is an assumption made in
|
||||
// "dyld_debug.c" and is said to be nearly always valid.
|
||||
vector<uint8_t> dyld_all_info_bytes;
|
||||
if (ReadTaskMemory(images.task_,
|
||||
image_list_address,
|
||||
sizeof(dyld_all_image_infos),
|
||||
dyld_all_info_bytes) != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
// Read an array of dyld_image_info structures each containing
|
||||
// information about a loaded image.
|
||||
dyld_image_info *infoArray = reinterpret_cast<dyld_image_info*>
|
||||
(ReadTaskMemory(task_,
|
||||
dyldInfo->infoArray,
|
||||
count*sizeof(dyld_image_info), &kr));
|
||||
dyld_all_image_infos *dyldInfo =
|
||||
reinterpret_cast<dyld_all_image_infos*>(&dyld_all_info_bytes[0]);
|
||||
|
||||
image_list_.reserve(count);
|
||||
// number of loaded images
|
||||
int count = dyldInfo->infoArrayCount;
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
dyld_image_info &info = infoArray[i];
|
||||
// Read an array of dyld_image_info structures each containing
|
||||
// information about a loaded image.
|
||||
vector<uint8_t> dyld_info_array_bytes;
|
||||
if (ReadTaskMemory(images.task_,
|
||||
dyldInfo->infoArray,
|
||||
count * sizeof(dyld_image_info),
|
||||
dyld_info_array_bytes) != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
// First read just the mach_header from the image in the task.
|
||||
breakpad_mach_header *header = reinterpret_cast<breakpad_mach_header*>
|
||||
(ReadTaskMemory(task_,
|
||||
info.load_address_,
|
||||
sizeof(breakpad_mach_header), &kr));
|
||||
dyld_image_info *infoArray =
|
||||
reinterpret_cast<dyld_image_info*>(&dyld_info_array_bytes[0]);
|
||||
images.image_list_.reserve(count);
|
||||
|
||||
if (!header)
|
||||
break; // bail on this dynamic image
|
||||
for (int i = 0; i < count; ++i) {
|
||||
dyld_image_info &info = infoArray[i];
|
||||
|
||||
// Now determine the total amount we really want to read based on the
|
||||
// size of the load commands. We need the header plus all of the
|
||||
// load commands.
|
||||
size_t header_size =
|
||||
sizeof(breakpad_mach_header) + header->sizeofcmds;
|
||||
// First read just the mach_header from the image in the task.
|
||||
vector<uint8_t> mach_header_bytes;
|
||||
if (ReadTaskMemory(images.task_,
|
||||
info.load_address_,
|
||||
sizeof(mach_header_type),
|
||||
mach_header_bytes) != KERN_SUCCESS)
|
||||
continue; // bail on this dynamic image
|
||||
|
||||
free(header);
|
||||
mach_header_type *header =
|
||||
reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]);
|
||||
|
||||
header = reinterpret_cast<breakpad_mach_header*>
|
||||
(ReadTaskMemory(task_, info.load_address_, header_size, &kr));
|
||||
// Now determine the total amount necessary to read the header
|
||||
// plus all of the load commands.
|
||||
size_t header_size =
|
||||
sizeof(mach_header_type) + header->sizeofcmds;
|
||||
|
||||
// Read the file name from the task's memory space.
|
||||
char *file_path = NULL;
|
||||
if (info.file_path_) {
|
||||
// Although we're reading kMaxStringLength bytes, it's copied in the
|
||||
// the DynamicImage constructor below with the correct string length,
|
||||
// so it's not really wasting memory.
|
||||
file_path = reinterpret_cast<char*>
|
||||
(ReadTaskString(task_, info.file_path_));
|
||||
}
|
||||
if (ReadTaskMemory(images.task_,
|
||||
info.load_address_,
|
||||
header_size,
|
||||
mach_header_bytes) != KERN_SUCCESS)
|
||||
continue;
|
||||
|
||||
// Create an object representing this image and add it to our list.
|
||||
DynamicImage *new_image;
|
||||
new_image = new DynamicImage(header,
|
||||
header_size,
|
||||
(breakpad_mach_header*)info.load_address_,
|
||||
file_path,
|
||||
info.file_mod_date_,
|
||||
task_);
|
||||
header = reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]);
|
||||
|
||||
if (new_image->IsValid()) {
|
||||
image_list_.push_back(DynamicImageRef(new_image));
|
||||
} else {
|
||||
delete new_image;
|
||||
}
|
||||
|
||||
if (file_path) {
|
||||
free(file_path);
|
||||
}
|
||||
// Read the file name from the task's memory space.
|
||||
string file_path;
|
||||
if (info.file_path_) {
|
||||
// Although we're reading kMaxStringLength bytes, it's copied in the
|
||||
// the DynamicImage constructor below with the correct string length,
|
||||
// so it's not really wasting memory.
|
||||
file_path = ReadTaskString(images.task_, info.file_path_);
|
||||
}
|
||||
|
||||
free(dyldInfo);
|
||||
free(infoArray);
|
||||
// Create an object representing this image and add it to our list.
|
||||
DynamicImage *new_image;
|
||||
new_image = new DynamicImage(&mach_header_bytes[0],
|
||||
header_size,
|
||||
info.load_address_,
|
||||
file_path,
|
||||
info.file_mod_date_,
|
||||
images.task_,
|
||||
images.cpu_type_);
|
||||
|
||||
// sorts based on loading address
|
||||
sort(image_list_.begin(), image_list_.end() );
|
||||
// remove duplicates - this happens in certain strange cases
|
||||
// You can see it in DashboardClient when Google Gadgets plugin
|
||||
// is installed. Apple's crash reporter log and gdb "info shared"
|
||||
// both show the same library multiple times at the same address
|
||||
|
||||
vector<DynamicImageRef>::iterator it = unique(image_list_.begin(),
|
||||
image_list_.end() );
|
||||
image_list_.erase(it, image_list_.end());
|
||||
if (new_image->IsValid()) {
|
||||
images.image_list_.push_back(DynamicImageRef(new_image));
|
||||
} else {
|
||||
delete new_image;
|
||||
}
|
||||
}
|
||||
|
||||
// sorts based on loading address
|
||||
sort(images.image_list_.begin(), images.image_list_.end());
|
||||
// remove duplicates - this happens in certain strange cases
|
||||
// You can see it in DashboardClient when Google Gadgets plugin
|
||||
// is installed. Apple's crash reporter log and gdb "info shared"
|
||||
// both show the same library multiple times at the same address
|
||||
|
||||
vector<DynamicImageRef>::iterator it = unique(images.image_list_.begin(),
|
||||
images.image_list_.end());
|
||||
images.image_list_.erase(it, images.image_list_.end());
|
||||
}
|
||||
|
||||
void DynamicImages::ReadImageInfoForTask() {
|
||||
uint64_t imageList = GetDyldAllImageInfosPointer();
|
||||
|
||||
if (imageList) {
|
||||
if (Is64Bit())
|
||||
ReadImageInfo<MachO64>(*this, imageList);
|
||||
else
|
||||
ReadImageInfo<MachO32>(*this, imageList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,7 +481,7 @@ int DynamicImages::GetExecutableImageIndex() {
|
|||
|
||||
for (int i = 0; i < image_count; ++i) {
|
||||
DynamicImage *image = GetImage(i);
|
||||
if (image->GetMachHeader()->filetype == MH_EXECUTE) {
|
||||
if (image->GetFileType() == MH_EXECUTE) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -444,4 +489,27 @@ int DynamicImages::GetExecutableImageIndex() {
|
|||
return -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// static
|
||||
cpu_type_t DynamicImages::DetermineTaskCPUType(task_t task) {
|
||||
if (task == mach_task_self())
|
||||
return GetNativeCPUType();
|
||||
|
||||
int mib[CTL_MAXNAME];
|
||||
size_t mibLen = CTL_MAXNAME;
|
||||
int err = sysctlnametomib("sysctl.proc_cputype", mib, &mibLen);
|
||||
if (err == 0) {
|
||||
assert(mibLen < CTL_MAXNAME);
|
||||
pid_for_task(task, &mib[mibLen]);
|
||||
mibLen += 1;
|
||||
|
||||
cpu_type_t cpu_type;
|
||||
size_t cpuTypeSize = sizeof(cpu_type);
|
||||
sysctl(mib, mibLen, &cpu_type, &cpuTypeSize, 0, 0);
|
||||
return cpu_type;
|
||||
}
|
||||
|
||||
return GetNativeCPUType();
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -41,32 +41,49 @@
|
|||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
//==============================================================================
|
||||
// The memory layout of this struct matches the dyld_image_info struct
|
||||
// defined in "dyld_gdb.h" in the darwin source.
|
||||
typedef struct dyld_image_info {
|
||||
struct mach_header *load_address_;
|
||||
char *file_path_;
|
||||
uintptr_t file_mod_date_;
|
||||
} dyld_image_info;
|
||||
typedef struct dyld_image_info32 {
|
||||
uint32_t load_address_; // struct mach_header*
|
||||
uint32_t file_path_; // char*
|
||||
uint32_t file_mod_date_;
|
||||
} dyld_image_info32;
|
||||
|
||||
typedef struct dyld_image_info64 {
|
||||
uint64_t load_address_; // struct mach_header*
|
||||
uint64_t file_path_; // char*
|
||||
uint64_t file_mod_date_;
|
||||
} dyld_image_info64;
|
||||
|
||||
//==============================================================================
|
||||
// This is as defined in "dyld_gdb.h" in the darwin source.
|
||||
// _dyld_all_image_infos (in dyld) is a structure of this type
|
||||
// which will be used to determine which dynamic code has been loaded.
|
||||
typedef struct dyld_all_image_infos {
|
||||
typedef struct dyld_all_image_infos32 {
|
||||
uint32_t version; // == 1 in Mac OS X 10.4
|
||||
uint32_t infoArrayCount;
|
||||
const struct dyld_image_info *infoArray;
|
||||
void* notification;
|
||||
uint32_t infoArray; // const struct dyld_image_info*
|
||||
uint32_t notification;
|
||||
bool processDetachedFromSharedRegion;
|
||||
} dyld_all_image_infos;
|
||||
} dyld_all_image_infos32;
|
||||
|
||||
typedef struct dyld_all_image_infos64 {
|
||||
uint32_t version; // == 1 in Mac OS X 10.4
|
||||
uint32_t infoArrayCount;
|
||||
uint64_t infoArray; // const struct dyld_image_info*
|
||||
uint64_t notification;
|
||||
bool processDetachedFromSharedRegion;
|
||||
} dyld_all_image_infos64;
|
||||
|
||||
// some typedefs to isolate 64/32 bit differences
|
||||
#ifdef __LP64__
|
||||
|
@ -77,71 +94,49 @@ typedef mach_header breakpad_mach_header;
|
|||
typedef segment_command breakpad_mach_segment_command;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
// A simple wrapper for a mach_header
|
||||
//
|
||||
// This could be fleshed out with some more interesting methods.
|
||||
class MachHeader {
|
||||
public:
|
||||
explicit MachHeader(const breakpad_mach_header &header) : header_(header) {}
|
||||
// Helper functions to deal with 32-bit/64-bit Mach-O differences.
|
||||
class DynamicImage;
|
||||
template<typename MachBits>
|
||||
bool FindTextSection(DynamicImage& image);
|
||||
|
||||
void Print() {
|
||||
printf("magic\t\t: %4x\n", header_.magic);
|
||||
printf("cputype\t\t: %d\n", header_.cputype);
|
||||
printf("cpusubtype\t: %d\n", header_.cpusubtype);
|
||||
printf("filetype\t: %d\n", header_.filetype);
|
||||
printf("ncmds\t\t: %d\n", header_.ncmds);
|
||||
printf("sizeofcmds\t: %d\n", header_.sizeofcmds);
|
||||
printf("flags\t\t: %d\n", header_.flags);
|
||||
}
|
||||
|
||||
breakpad_mach_header header_;
|
||||
};
|
||||
template<typename MachBits>
|
||||
uint32_t GetFileTypeFromHeader(DynamicImage& image);
|
||||
|
||||
//==============================================================================
|
||||
// Represents a single dynamically loaded mach-o image
|
||||
class DynamicImage {
|
||||
public:
|
||||
DynamicImage(breakpad_mach_header *header, // we take ownership
|
||||
size_t header_size, // includes load commands
|
||||
breakpad_mach_header *load_address,
|
||||
char *inFilePath,
|
||||
DynamicImage(uint8_t *header, // data is copied
|
||||
size_t header_size, // includes load commands
|
||||
uint64_t load_address,
|
||||
string file_path,
|
||||
uintptr_t image_mod_date,
|
||||
mach_port_t task)
|
||||
: header_(header),
|
||||
mach_port_t task,
|
||||
cpu_type_t cpu_type)
|
||||
: header_(header, header + header_size),
|
||||
header_size_(header_size),
|
||||
load_address_(load_address),
|
||||
vmaddr_(0),
|
||||
vmsize_(0),
|
||||
slide_(0),
|
||||
version_(0),
|
||||
file_path_(NULL),
|
||||
file_path_(file_path),
|
||||
file_mod_date_(image_mod_date),
|
||||
task_(task) {
|
||||
InitializeFilePath(inFilePath);
|
||||
task_(task),
|
||||
cpu_type_(cpu_type) {
|
||||
CalculateMemoryAndVersionInfo();
|
||||
}
|
||||
|
||||
~DynamicImage() {
|
||||
if (file_path_) {
|
||||
free(file_path_);
|
||||
}
|
||||
free(header_);
|
||||
}
|
||||
|
||||
// Returns pointer to a local copy of the mach_header plus load commands
|
||||
breakpad_mach_header *GetMachHeader() {return header_;}
|
||||
|
||||
// Size of mach_header plus load commands
|
||||
size_t GetHeaderSize() const {return header_size_;}
|
||||
size_t GetHeaderSize() const {return header_.size();}
|
||||
|
||||
// Full path to mach-o binary
|
||||
char *GetFilePath() {return file_path_;}
|
||||
string GetFilePath() {return file_path_;}
|
||||
|
||||
uintptr_t GetModDate() const {return file_mod_date_;}
|
||||
uint64_t GetModDate() const {return file_mod_date_;}
|
||||
|
||||
// Actual address where the image was loaded
|
||||
breakpad_mach_header *GetLoadAddress() const {return load_address_;}
|
||||
uint64_t GetLoadAddress() const {return load_address_;}
|
||||
|
||||
// Address where the image should be loaded
|
||||
mach_vm_address_t GetVMAddr() const {return vmaddr_;}
|
||||
|
@ -155,49 +150,49 @@ class DynamicImage {
|
|||
// Task owning this loaded image
|
||||
mach_port_t GetTask() {return task_;}
|
||||
|
||||
// CPU type of the task
|
||||
cpu_type_t GetCPUType() {return cpu_type_;}
|
||||
|
||||
// filetype from the Mach-O header.
|
||||
uint32_t GetFileType();
|
||||
|
||||
// Return true if the task is a 64-bit architecture.
|
||||
bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; }
|
||||
|
||||
uint32_t GetVersion() {return version_;}
|
||||
// For sorting
|
||||
bool operator<(const DynamicImage &inInfo) {
|
||||
return GetLoadAddress() < inInfo.GetLoadAddress();
|
||||
}
|
||||
|
||||
// Debugging
|
||||
void Print();
|
||||
// Sanity checking
|
||||
bool IsValid() {return GetVMSize() != 0;}
|
||||
|
||||
private:
|
||||
DynamicImage(const DynamicImage &);
|
||||
DynamicImage &operator=(const DynamicImage &);
|
||||
|
||||
friend class DynamicImages;
|
||||
|
||||
// Sanity checking
|
||||
bool IsValid() {return GetVMSize() != 0;}
|
||||
|
||||
// Makes local copy of file path to mach-o binary
|
||||
void InitializeFilePath(char *inFilePath) {
|
||||
if (inFilePath) {
|
||||
size_t path_size = 1 + strlen(inFilePath);
|
||||
file_path_ = reinterpret_cast<char*>(malloc(path_size));
|
||||
strlcpy(file_path_, inFilePath, path_size);
|
||||
} else {
|
||||
file_path_ = NULL;
|
||||
}
|
||||
}
|
||||
template<typename MachBits>
|
||||
friend bool FindTextSection(DynamicImage& image);
|
||||
template<typename MachBits>
|
||||
friend uint32_t GetFileTypeFromHeader(DynamicImage& image);
|
||||
|
||||
// Initializes vmaddr_, vmsize_, and slide_
|
||||
void CalculateMemoryAndVersionInfo();
|
||||
|
||||
breakpad_mach_header *header_; // our local copy of the header
|
||||
const vector<uint8_t> header_; // our local copy of the header
|
||||
size_t header_size_; // mach_header plus load commands
|
||||
breakpad_mach_header *load_address_; // base address image is mapped into
|
||||
uint64_t load_address_; // base address image is mapped into
|
||||
mach_vm_address_t vmaddr_;
|
||||
mach_vm_size_t vmsize_;
|
||||
ptrdiff_t slide_;
|
||||
uint32_t version_; // Dylib version
|
||||
char *file_path_; // path dyld used to load the image
|
||||
string file_path_; // path dyld used to load the image
|
||||
uintptr_t file_mod_date_; // time_t of image file
|
||||
|
||||
mach_port_t task_;
|
||||
cpu_type_t cpu_type_; // CPU type of task_
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
|
@ -230,6 +225,11 @@ class DynamicImageRef {
|
|||
DynamicImage *p;
|
||||
};
|
||||
|
||||
// Helper function to deal with 32-bit/64-bit Mach-O differences.
|
||||
class DynamicImages;
|
||||
template<typename MachBits>
|
||||
void ReadImageInfo(DynamicImages& images, uint64_t image_list_address);
|
||||
|
||||
//==============================================================================
|
||||
// An object of type DynamicImages may be created to allow introspection of
|
||||
// an arbitrary task's dynamically loaded mach-o binaries. This makes the
|
||||
|
@ -262,43 +262,51 @@ class DynamicImages {
|
|||
// Returns the task which we're looking at.
|
||||
mach_port_t GetTask() const {return task_;}
|
||||
|
||||
// Debugging
|
||||
void Print() {
|
||||
for (int i = 0; i < GetImageCount(); ++i) {
|
||||
image_list_[i]->Print();
|
||||
}
|
||||
}
|
||||
// CPU type of the task
|
||||
cpu_type_t GetCPUType() {return cpu_type_;}
|
||||
|
||||
void TestPrint() {
|
||||
const breakpad_mach_header *header;
|
||||
for (int i = 0; i < GetImageCount(); ++i) {
|
||||
printf("dyld: %p: name = %s\n", _dyld_get_image_header(i),
|
||||
_dyld_get_image_name(i) );
|
||||
// Return true if the task is a 64-bit architecture.
|
||||
bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; }
|
||||
|
||||
const void *imageHeader = _dyld_get_image_header(i);
|
||||
header = reinterpret_cast<const breakpad_mach_header*>(imageHeader);
|
||||
// Determine the CPU type of the task being dumped.
|
||||
static cpu_type_t DetermineTaskCPUType(task_t task);
|
||||
|
||||
MachHeader(*header).Print();
|
||||
}
|
||||
// Get the native CPU type of this task.
|
||||
static cpu_type_t GetNativeCPUType() {
|
||||
#if defined(__i386__)
|
||||
return CPU_TYPE_I386;
|
||||
#elif defined(__x86_64__)
|
||||
return CPU_TYPE_X86_64;
|
||||
#elif defined(__ppc__)
|
||||
return CPU_TYPE_POWERPC;
|
||||
#elif defined(__ppc64__)
|
||||
return CPU_TYPE_POWERPC64;
|
||||
#else
|
||||
#error "GetNativeCPUType not implemented for this architecture"
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename MachBits>
|
||||
friend void ReadImageInfo(DynamicImages& images, uint64_t image_list_address);
|
||||
|
||||
bool IsOurTask() {return task_ == mach_task_self();}
|
||||
|
||||
// Initialization
|
||||
void ReadImageInfoForTask();
|
||||
void* GetDyldAllImageInfosPointer();
|
||||
uint64_t GetDyldAllImageInfosPointer();
|
||||
|
||||
mach_port_t task_;
|
||||
cpu_type_t cpu_type_; // CPU type of task_
|
||||
vector<DynamicImageRef> image_list_;
|
||||
};
|
||||
|
||||
// Returns a malloced block containing the contents of memory at a particular
|
||||
// Fill bytes with the contents of memory at a particular
|
||||
// location in another task.
|
||||
void* ReadTaskMemory(task_port_t target_task,
|
||||
const void* address,
|
||||
size_t len,
|
||||
kern_return_t *kr);
|
||||
kern_return_t ReadTaskMemory(task_port_t target_task,
|
||||
const uint64_t address,
|
||||
size_t length,
|
||||
vector<uint8_t> &bytes);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
|
|
|
@ -31,7 +31,9 @@
|
|||
#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>
|
||||
|
@ -65,6 +67,7 @@ MinidumpGenerator::MinidumpGenerator()
|
|||
exception_thread_(0),
|
||||
crashing_task_(mach_task_self()),
|
||||
handler_thread_(mach_thread_self()),
|
||||
cpu_type_(DynamicImages::GetNativeCPUType()),
|
||||
dynamic_images_(NULL),
|
||||
memory_blocks_(&allocator_) {
|
||||
GatherSystemInformation();
|
||||
|
@ -81,12 +84,15 @@ MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
|
|||
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();
|
||||
|
@ -254,8 +260,10 @@ size_t MinidumpGenerator::CalculateStackSize(mach_vm_address_t start_addr) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if ((stack_region_base + stack_region_size) == TOP_OF_THREAD0_STACK) {
|
||||
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
|
||||
|
@ -305,20 +313,15 @@ bool MinidumpGenerator::WriteStackFromStartAddress(
|
|||
return false;
|
||||
|
||||
if (dynamic_images_) {
|
||||
|
||||
kern_return_t kr;
|
||||
|
||||
void *stack_memory = ReadTaskMemory(crashing_task_,
|
||||
(void*)start_addr,
|
||||
size,
|
||||
&kr);
|
||||
|
||||
if (stack_memory == NULL) {
|
||||
vector<uint8_t> stack_memory;
|
||||
if (ReadTaskMemory(crashing_task_,
|
||||
start_addr,
|
||||
size,
|
||||
stack_memory) != KERN_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = memory.Copy(stack_memory, size);
|
||||
free(stack_memory);
|
||||
result = memory.Copy(&stack_memory[0], size);
|
||||
} else {
|
||||
result = memory.Copy(reinterpret_cast<const void *>(start_addr), size);
|
||||
}
|
||||
|
@ -330,34 +333,99 @@ bool MinidumpGenerator::WriteStackFromStartAddress(
|
|||
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<breakpad_thread_state_t *>(state);
|
||||
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::CurrentPCForStack(breakpad_thread_state_data_t state) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
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);
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location) {
|
||||
TypedMDRVA<MinidumpContext> context(&writer_);
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
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();
|
||||
MinidumpContext *context_ptr = context.get();
|
||||
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)
|
||||
|
@ -402,57 +470,124 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
|||
AddGPR(29);
|
||||
AddGPR(30);
|
||||
AddGPR(31);
|
||||
|
||||
#if TARGET_CPU_PPC
|
||||
/* The mq register is only for PPC */
|
||||
AddReg(mq);
|
||||
#endif
|
||||
|
||||
#undef AddReg
|
||||
#undef AddGPR
|
||||
|
||||
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<breakpad_thread_state_t *>(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<breakpad_thread_state_t *>(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<MinidumpContext> context(&writer_);
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
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();
|
||||
MinidumpContext *context_ptr = context.get();
|
||||
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)
|
||||
#if TARGET_CPU_X86
|
||||
#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);
|
||||
|
@ -472,7 +607,26 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
|||
AddReg(eflags);
|
||||
|
||||
AddReg(eip);
|
||||
#else
|
||||
#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);
|
||||
|
@ -495,16 +649,38 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
|||
// 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>(machine_state->__rflags);
|
||||
context_ptr->eflags = static_cast<u_int32_t>(REGISTER_FROM_THREADSTATE(machine_state, rflags));
|
||||
AddReg(cs);
|
||||
AddReg(fs);
|
||||
AddReg(gs);
|
||||
#endif
|
||||
#undef AddReg(a)
|
||||
|
||||
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_) {
|
||||
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) {
|
||||
|
@ -512,9 +688,7 @@ bool MinidumpGenerator::WriteThreadStream(mach_port_t thread_id,
|
|||
mach_msg_type_number_t state_count
|
||||
= static_cast<mach_msg_type_number_t>(sizeof(state));
|
||||
|
||||
if (thread_get_state(thread_id, BREAKPAD_MACHINE_THREAD_STATE,
|
||||
state, &state_count) ==
|
||||
KERN_SUCCESS) {
|
||||
if (GetThreadState(thread_id, state, &state_count)) {
|
||||
if (!WriteStack(state, &thread->stack))
|
||||
return false;
|
||||
|
||||
|
@ -653,21 +827,15 @@ bool MinidumpGenerator::WriteMemoryListStream(
|
|||
|
||||
if (dynamic_images_) {
|
||||
// Out-of-process.
|
||||
kern_return_t kr;
|
||||
|
||||
void *memory =
|
||||
ReadTaskMemory(
|
||||
crashing_task_,
|
||||
reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range),
|
||||
ip_memory_d.memory.data_size,
|
||||
&kr);
|
||||
|
||||
if (memory == NULL) {
|
||||
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, ip_memory_d.memory.data_size);
|
||||
free(memory);
|
||||
ip_memory.Copy(&memory[0], ip_memory_d.memory.data_size);
|
||||
} else {
|
||||
// In-process, just copy from local memory.
|
||||
ip_memory.Copy(
|
||||
|
@ -733,23 +901,23 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
|||
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);
|
||||
size_t len = sizeof(number_of_processors);
|
||||
sysctlbyname("hw.ncpu", &number_of_processors, &len, NULL, 0);
|
||||
MDRawSystemInfo *info_ptr = info.get();
|
||||
|
||||
switch (cpu_type) {
|
||||
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:
|
||||
// hw.cputype is currently always I386 even on an x86-64 system
|
||||
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__
|
||||
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) \
|
||||
|
@ -763,7 +931,7 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
|||
"=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), \
|
||||
|
@ -837,19 +1005,12 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int 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))
|
||||
string name = image->GetFilePath();
|
||||
if (!writer_.WriteString(name.c_str(), 0, &string_location))
|
||||
return false;
|
||||
|
||||
module->base_of_image = image->GetVMAddr() + image->GetVMAddrSlide();
|
||||
|
@ -876,12 +1037,11 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
|||
module->version_info.file_version_lo |= (modVersion & 0xff);
|
||||
}
|
||||
|
||||
if (!WriteCVRecord(module, cpu_type, name)) {
|
||||
if (!WriteCVRecord(module, image->GetCPUType(), name.c_str())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// we're getting module info in the crashed process
|
||||
|
||||
// Getting module info in the crashed process
|
||||
const breakpad_mach_header *header;
|
||||
header = (breakpad_mach_header*)_dyld_get_image_header(index);
|
||||
if (!header)
|
||||
|
@ -903,7 +1063,7 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
|||
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);
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
memset(module, 0, sizeof(MDRawModule));
|
||||
|
||||
|
@ -911,7 +1071,7 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
|||
if (cmd->cmd == LC_SEGMENT_ARCH) {
|
||||
|
||||
const breakpad_mach_segment_command *seg =
|
||||
reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
|
||||
reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
MDLocationDescriptor string_location;
|
||||
|
|
|
@ -47,25 +47,8 @@ namespace google_breakpad {
|
|||
|
||||
using std::string;
|
||||
|
||||
#if TARGET_CPU_X86_64 || TARGET_CPU_PPC64
|
||||
#define TOP_OF_THREAD0_STACK 0x00007fff5fbff000
|
||||
#else
|
||||
#define TOP_OF_THREAD0_STACK 0xbffff000
|
||||
#endif
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
typedef x86_thread_state64_t breakpad_thread_state_t;
|
||||
typedef MDRawContextAMD64 MinidumpContext;
|
||||
#elif TARGET_CPU_X86
|
||||
typedef i386_thread_state_t breakpad_thread_state_t;
|
||||
typedef MDRawContextX86 MinidumpContext;
|
||||
#elif TARGET_CPU_PPC64
|
||||
typedef ppc_thread_state64_t breakpad_thread_state_t;
|
||||
typedef MDRawContextPPC64 MinidumpContext;
|
||||
#elif TARGET_CPU_PPC
|
||||
typedef ppc_thread_state_t breakpad_thread_state_t;
|
||||
typedef MDRawContextPPC MinidumpContext;
|
||||
#endif
|
||||
const u_int64_t TOP_OF_THREAD0_STACK_64BIT = 0x00007fff5fbff000LL;
|
||||
const u_int32_t TOP_OF_THREAD0_STACK_32BIT = 0xbffff000;
|
||||
|
||||
// Use the REGISTER_FROM_THREADSTATE to access a register name from the
|
||||
// breakpad_thread_state_t structure.
|
||||
|
@ -129,6 +112,8 @@ class MinidumpGenerator {
|
|||
|
||||
// Helpers
|
||||
u_int64_t CurrentPCForStack(breakpad_thread_state_data_t state);
|
||||
bool GetThreadState(thread_act_t target_thread, thread_state_t state,
|
||||
mach_msg_type_number_t *count);
|
||||
bool WriteStackFromStartAddress(mach_vm_address_t start_addr,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteStack(breakpad_thread_state_data_t state,
|
||||
|
@ -139,11 +124,31 @@ class MinidumpGenerator {
|
|||
bool WriteCVRecord(MDRawModule *module, int cpu_type,
|
||||
const char *module_path);
|
||||
bool WriteModuleStream(unsigned int index, MDRawModule *module);
|
||||
|
||||
size_t CalculateStackSize(mach_vm_address_t start_addr);
|
||||
|
||||
int FindExecutableModule();
|
||||
|
||||
// Per-CPU implementations of these methods
|
||||
bool WriteStackPPC(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteContextPPC(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location);
|
||||
u_int64_t CurrentPCForStackPPC(breakpad_thread_state_data_t state);
|
||||
bool WriteStackPPC64(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteContextPPC64(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location);
|
||||
u_int64_t CurrentPCForStackPPC64(breakpad_thread_state_data_t state);
|
||||
bool WriteStackX86(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteContextX86(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location);
|
||||
u_int64_t CurrentPCForStackX86(breakpad_thread_state_data_t state);
|
||||
bool WriteStackX86_64(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteContextX86_64(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location);
|
||||
u_int64_t CurrentPCForStackX86_64(breakpad_thread_state_data_t state);
|
||||
|
||||
// disallow copy ctor and operator=
|
||||
explicit MinidumpGenerator(const MinidumpGenerator &);
|
||||
void operator=(const MinidumpGenerator &);
|
||||
|
@ -158,6 +163,9 @@ class MinidumpGenerator {
|
|||
mach_port_t exception_thread_;
|
||||
mach_port_t crashing_task_;
|
||||
mach_port_t handler_thread_;
|
||||
|
||||
// CPU type of the task being dumped.
|
||||
cpu_type_t cpu_type_;
|
||||
|
||||
// System information
|
||||
static char build_string_[16];
|
||||
|
|
|
@ -29,10 +29,21 @@
|
|||
|
||||
// minidump_generator_test.cc: Unit tests for google_breakpad::MinidumpGenerator
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
#ifndef MAC_OS_X_VERSION_10_6
|
||||
#define MAC_OS_X_VERSION_10_6 1060
|
||||
#endif
|
||||
#include <crt_externs.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6
|
||||
#include <spawn.h>
|
||||
#endif
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/mac/handler/minidump_generator.h"
|
||||
#include "client/mac/tests/auto_tempdir.h"
|
||||
|
@ -48,6 +59,7 @@ std::ostringstream info_log;
|
|||
|
||||
namespace {
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using google_breakpad::AutoTempDir;
|
||||
using google_breakpad::MinidumpGenerator;
|
||||
using google_breakpad::MachPortSender;
|
||||
|
@ -161,7 +173,7 @@ TEST_F(MinidumpGeneratorTest, OutOfProcess) {
|
|||
const int kTimeoutMs = 2000;
|
||||
// Create a mach port to receive the child task on.
|
||||
char machPortName[128];
|
||||
sprintf(machPortName, "MinidumpGeneratorTest.%d", getpid());
|
||||
sprintf(machPortName, "MinidumpGeneratorTest.OutOfProcess.%d", getpid());
|
||||
ReceivePort parent_recv_port(machPortName);
|
||||
|
||||
// Give the child process a pipe to block on.
|
||||
|
@ -248,4 +260,150 @@ TEST_F(MinidumpGeneratorTest, OutOfProcess) {
|
|||
EXPECT_EQ(GetExecutablePath(), main_module->code_file());
|
||||
}
|
||||
|
||||
static string GetHelperPath() {
|
||||
string helper_path(GetExecutablePath());
|
||||
size_t pos = helper_path.rfind('/');
|
||||
if (pos == string::npos)
|
||||
return "";
|
||||
|
||||
helper_path.erase(pos + 1);
|
||||
helper_path += "minidump_generator_test_helper";
|
||||
return helper_path;
|
||||
}
|
||||
|
||||
// This test fails on 10.5, but I don't have easy access to a 10.5 machine,
|
||||
// so it's simpler to just limit it to 10.6 for now.
|
||||
#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \
|
||||
(defined(__x86_64__) || defined(__i386__))
|
||||
static pid_t spawn_child_process(const char** argv) {
|
||||
posix_spawnattr_t spawnattr;
|
||||
if (posix_spawnattr_init(&spawnattr) != 0)
|
||||
return (pid_t)-1;
|
||||
|
||||
cpu_type_t pref_cpu_types[2] = {
|
||||
#if defined(__x86_64__)
|
||||
CPU_TYPE_X86,
|
||||
#elif defined(__i386__)
|
||||
CPU_TYPE_X86_64,
|
||||
#endif
|
||||
CPU_TYPE_ANY
|
||||
};
|
||||
|
||||
// Set spawn attributes.
|
||||
size_t attr_count = sizeof(pref_cpu_types) / sizeof(pref_cpu_types[0]);
|
||||
size_t attr_ocount = 0;
|
||||
if (posix_spawnattr_setbinpref_np(&spawnattr,
|
||||
attr_count,
|
||||
pref_cpu_types,
|
||||
&attr_ocount) != 0 ||
|
||||
attr_ocount != attr_count) {
|
||||
posix_spawnattr_destroy(&spawnattr);
|
||||
return (pid_t)-1;
|
||||
}
|
||||
|
||||
// Create an argv array.
|
||||
vector<char*> argv_v;
|
||||
while (*argv) {
|
||||
argv_v.push_back(strdup(*argv));
|
||||
argv++;
|
||||
}
|
||||
argv_v.push_back(NULL);
|
||||
pid_t new_pid = 0;
|
||||
int result = posix_spawnp(&new_pid, argv_v[0], NULL, &spawnattr,
|
||||
&argv_v[0], *_NSGetEnviron());
|
||||
posix_spawnattr_destroy(&spawnattr);
|
||||
|
||||
for (unsigned i = 0; i < argv_v.size(); i++) {
|
||||
free(argv_v[i]);
|
||||
}
|
||||
|
||||
return result == 0 ? new_pid : -1;
|
||||
}
|
||||
|
||||
TEST_F(MinidumpGeneratorTest, CrossArchitectureDump) {
|
||||
const int kTimeoutMs = 5000;
|
||||
// Create a mach port to receive the child task on.
|
||||
char machPortName[128];
|
||||
sprintf(machPortName,
|
||||
"MinidumpGeneratorTest.CrossArchitectureDump.%d", getpid());
|
||||
|
||||
ReceivePort parent_recv_port(machPortName);
|
||||
|
||||
// Spawn a child process to dump.
|
||||
string helper_path = GetHelperPath();
|
||||
const char* argv[] = {
|
||||
helper_path.c_str(),
|
||||
machPortName,
|
||||
NULL
|
||||
};
|
||||
pid_t pid = spawn_child_process(argv);
|
||||
ASSERT_NE(-1, pid);
|
||||
|
||||
// Read the child's task port.
|
||||
MachReceiveMessage child_message;
|
||||
ASSERT_EQ(KERN_SUCCESS,
|
||||
parent_recv_port.WaitForMessage(&child_message, kTimeoutMs));
|
||||
mach_port_t child_task = child_message.GetTranslatedPort(0);
|
||||
ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task);
|
||||
|
||||
// Write a minidump of the child process.
|
||||
MinidumpGenerator generator(child_task, MACH_PORT_NULL);
|
||||
string dump_filename = MinidumpGenerator::UniqueNameInDirectory(tempDir.path,
|
||||
NULL);
|
||||
ASSERT_TRUE(generator.Write(dump_filename.c_str()));
|
||||
|
||||
// Ensure that minidump file exists and is > 0 bytes.
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(dump_filename.c_str(), &st));
|
||||
ASSERT_LT(0, st.st_size);
|
||||
|
||||
// Kill child process.
|
||||
kill(pid, SIGKILL);
|
||||
|
||||
int ret;
|
||||
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
|
||||
|
||||
const MDCPUArchitecture kExpectedArchitecture =
|
||||
#if defined(__x86_64__)
|
||||
MD_CPU_ARCHITECTURE_X86
|
||||
#elif defined(__i386__)
|
||||
MD_CPU_ARCHITECTURE_AMD64
|
||||
#endif
|
||||
;
|
||||
const u_int32_t kExpectedContext =
|
||||
#if defined(__i386__)
|
||||
MD_CONTEXT_AMD64
|
||||
#elif defined(__x86_64__)
|
||||
MD_CONTEXT_X86
|
||||
#endif
|
||||
;
|
||||
|
||||
// Read the minidump, sanity check some data.
|
||||
Minidump minidump(dump_filename.c_str());
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpSystemInfo* system_info = minidump.GetSystemInfo();
|
||||
ASSERT_TRUE(system_info);
|
||||
const MDRawSystemInfo* raw_info = system_info->system_info();
|
||||
ASSERT_TRUE(raw_info);
|
||||
EXPECT_EQ(kExpectedArchitecture, raw_info->processor_architecture);
|
||||
|
||||
MinidumpThreadList* thread_list = minidump.GetThreadList();
|
||||
ASSERT_TRUE(thread_list);
|
||||
ASSERT_EQ((unsigned int)1, thread_list->thread_count());
|
||||
|
||||
MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0);
|
||||
ASSERT_TRUE(main_thread);
|
||||
MinidumpContext* context = main_thread->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
EXPECT_EQ(kExpectedContext, context->GetContextCPU());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* main_module = module_list->GetMainModule();
|
||||
ASSERT_TRUE(main_module);
|
||||
EXPECT_EQ(helper_path, main_module->code_file());
|
||||
}
|
||||
#endif // 10.6 && (x86-64 || i386)
|
||||
|
||||
}
|
||||
|
|
63
src/client/mac/tests/minidump_generator_test_helper.cc
Normal file
63
src/client/mac/tests/minidump_generator_test_helper.cc
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2010, 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.
|
||||
|
||||
// minidump_generator_test_helper.cc: A helper program that
|
||||
// minidump_generator_test.cc can launch to test certain things
|
||||
// that require a separate executable.
|
||||
|
||||
#include "common/mac/MachIPC.h"
|
||||
#include <unistd.h>
|
||||
|
||||
using google_breakpad::MachPortSender;
|
||||
using google_breakpad::MachReceiveMessage;
|
||||
using google_breakpad::MachSendMessage;
|
||||
using google_breakpad::ReceivePort;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2)
|
||||
return 1;
|
||||
|
||||
const int kTimeoutMs = 2000;
|
||||
// Send parent process the task and thread ports.
|
||||
MachSendMessage child_message(0);
|
||||
child_message.AddDescriptor(mach_task_self());
|
||||
child_message.AddDescriptor(mach_thread_self());
|
||||
|
||||
MachPortSender child_sender(argv[1]);
|
||||
if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) {
|
||||
fprintf(stderr, "Error sending message from child process!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Loop forever.
|
||||
while (true) {
|
||||
sleep(100);
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -839,7 +839,7 @@ bool MinidumpContext::CheckAgainstSystemInfo(u_int32_t context_cpu_type) {
|
|||
|
||||
BPLOG_IF(ERROR, !return_value) << "MinidumpContext CPU " <<
|
||||
HexString(context_cpu_type) <<
|
||||
" wrong for MinidumpSysmtemInfo CPU " <<
|
||||
" wrong for MinidumpSystemInfo CPU " <<
|
||||
HexString(system_info_cpu_type);
|
||||
|
||||
return return_value;
|
||||
|
|
Loading…
Reference in a new issue