diff options
-rw-r--r-- | src/client/mac/Breakpad.xcodeproj/project.pbxproj | 109 | ||||
-rw-r--r-- | src/client/mac/handler/breakpad_nlist_64.cc | 411 | ||||
-rw-r--r-- | src/client/mac/handler/breakpad_nlist_64.h | 14 | ||||
-rw-r--r-- | src/client/mac/handler/dynamic_images.cc | 502 | ||||
-rw-r--r-- | src/client/mac/handler/dynamic_images.h | 196 | ||||
-rw-r--r-- | src/client/mac/handler/minidump_generator.cc | 346 | ||||
-rw-r--r-- | src/client/mac/handler/minidump_generator.h | 50 | ||||
-rw-r--r-- | src/client/mac/tests/minidump_generator_test.cc | 160 | ||||
-rw-r--r-- | src/client/mac/tests/minidump_generator_test_helper.cc | 63 | ||||
-rw-r--r-- | src/processor/minidump.cc | 2 |
10 files changed, 1231 insertions, 622 deletions
diff --git a/src/client/mac/Breakpad.xcodeproj/project.pbxproj b/src/client/mac/Breakpad.xcodeproj/project.pbxproj index 6f106aa1..7e143406 100644 --- a/src/client/mac/Breakpad.xcodeproj/project.pbxproj +++ b/src/client/mac/Breakpad.xcodeproj/project.pbxproj @@ -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 = ( diff --git a/src/client/mac/handler/breakpad_nlist_64.cc b/src/client/mac/handler/breakpad_nlist_64.cc index 4b655c79..709e8546 100644 --- a/src/client/mac/handler/breakpad_nlist_64.cc +++ b/src/client/mac/handler/breakpad_nlist_64.cc @@ -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)]; - - 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; +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; - maxlen = 500; - for (q = list, nreq = 0; + const uint32_t magic = MachBits<nlist_type>::magic; + + 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; - } + 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; } + } - if (!fap) { - free(fat_archs); - return (-1); - } - arch_offset = fap->offset; + 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); - } + /* 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; + } + } - if (*((unsigned int *)&buf) == MH_MAGIC_64) { - struct mach_header_64 mh; - struct load_command *load_commands, *lcp; - struct symtab_command *stp; - long i; + 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; + } - 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) { + 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); + 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) { + if (lcp->cmd == LC_SYMTAB) { + if (lcp->cmdsize != + sizeof(struct symtab_command)) { 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; + return -1; } - lcp = (struct load_command *) - ((char *)lcp + lcp->cmdsize); - } - if (stp == NULL) { - free(load_commands); - return (-1); + stp = (struct symtab_command *)lcp; + break; } - // 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); + lcp = (struct load_command *) + ((char *)lcp + lcp->cmdsize); } - else { - sa = N_SYMOFF(buf) + arch_offset; - ss = sa + buf.a_syms + arch_offset; - n = buf.a_syms; + 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; + } - lseek(fd, sa, SEEK_SET); + 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]; - // 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; + if (q->n_un.n_strx == 0 || q->n_type & N_STAB) + continue; - m = sizeof (space); - if (n < m) - m = n; - if (read(fd, (char *)space, m) != m) - 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) + // 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); + 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: ; - } + break; + cont: ; } - lseek(fd, savpos, SEEK_SET); } - return (nreq); + if (lseek(fd, savpos, SEEK_SET) == -1) { + return -1; + } } - -#endif /* __LP64__ */ + return nreq; +} diff --git a/src/client/mac/handler/breakpad_nlist_64.h b/src/client/mac/handler/breakpad_nlist_64.h index ee10afb9..1d2c6391 100644 --- a/src/client/mac/handler/breakpad_nlist_64.h +++ b/src/client/mac/handler/breakpad_nlist_64.h @@ -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__ */ diff --git a/src/client/mac/handler/dynamic_images.cc b/src/client/mac/handler/dynamic_images.cc index 1d5f1f9b..035afe7e 100644 --- a/src/client/mac/handler/dynamic_images.cc +++ b/src/client/mac/handler/dynamic_images.cc @@ -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,147 +137,170 @@ 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; - - r = mach_vm_read(target_task, - page_address, - page_size, - reinterpret_cast<vm_offset_t*>(&local_start), - &local_length); - - - 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; + kern_return_t 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; + + 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 - //============================================================================== -// 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; - vmsize_ = 0; - slide_ = 0; - version_ = 0; - - bool foundTextSection = false; - bool foundDylibIDCommand = false; +// 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; -#if __LP64__ - if(header->magic != MH_MAGIC_64) { - return; - } -#else - if(header->magic != MH_MAGIC) { - return; - } -#endif + const mach_header_type* header = + reinterpret_cast<const mach_header_type*>(&image.header_[0]); -#ifdef __LP64__ - const uint32_t segmentLoadCommand = LC_SEGMENT_64; -#else - const uint32_t segmentLoadCommand = LC_SEGMENT; -#endif + if(header->magic != MachBits::magic) { + return false; + } const struct load_command *cmd = - reinterpret_cast<const struct load_command *>(header + 1); + 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 (!foundTextSection) { - if (cmd->cmd == segmentLoadCommand) { - const breakpad_mach_segment_command *seg = - reinterpret_cast<const breakpad_mach_segment_command *>(cmd); + 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")) { - vmaddr_ = seg->vmaddr; - vmsize_ = seg->vmsize; - slide_ = 0; + image.vmaddr_ = seg->vmaddr; + image.vmsize_ = seg->vmsize; + image.slide_ = 0; - if (seg->fileoff == 0 && seg->filesize != 0) { - slide_ = (uintptr_t)GetLoadAddress() - (uintptr_t)seg->vmaddr; + if (seg->fileoff == 0 && seg->filesize != 0) { + image.slide_ = + (uintptr_t)image.GetLoadAddress() - (uintptr_t)seg->vmaddr; } - foundTextSection = true; + found_text_section = true; } } } - if (!foundDylibIDCommand) { + if (!found_dylib_id_command) { 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; + image.version_ = dc->dylib.current_version; + found_dylib_id_command = true; } } - if (foundDylibIDCommand && foundTextSection) { - return; + if (found_dylib_id_command && found_text_section) { + return true; } cmd = reinterpret_cast<const struct load_command *> - (reinterpret_cast<const char *>(cmd) + cmd->cmdsize); + (reinterpret_cast<const char *>(cmd) + cmd->cmdsize); } + return false; } -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()); +//============================================================================== +// Initializes vmaddr_, vmsize_, and slide_ +void DynamicImage::CalculateMemoryAndVersionInfo() { + // unless we can process the header, ensure that calls to + // IsValid() will return false + vmaddr_ = 0; + vmsize_ = 0; + slide_ = 0; + version_ = 0; + + // The function template above does all the real work. + if (Is64Bit()) + FindTextSection<MachO64>(*this); + else + FindTextSection<MachO32>(*this); +} + +//============================================================================== +// 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) ); - - // 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]; +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; - 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 + 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. - - // 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)); - - if (dyldInfo) { - // number of loaded images - int count = dyldInfo->infoArrayCount; - - // 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)); - - image_list_.reserve(count); - - for (int i = 0; i < count; ++i) { - dyld_image_info &info = infoArray[i]; - - // 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)); - - if (!header) - break; // bail on this dynamic image - - // 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; - - free(header); - - header = reinterpret_cast<breakpad_mach_header*> - (ReadTaskMemory(task_, info.load_address_, header_size, &kr)); - - // 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_)); - } +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; + + // Read the structure inside of dyld that contains information about + // loaded images. We're reading from the desired task's address space. + + // 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; - // 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_); - - if (new_image->IsValid()) { - image_list_.push_back(DynamicImageRef(new_image)); - } else { - delete new_image; - } + dyld_all_image_infos *dyldInfo = + reinterpret_cast<dyld_all_image_infos*>(&dyld_all_info_bytes[0]); - if (file_path) { - free(file_path); - } - } + // number of loaded images + int count = dyldInfo->infoArrayCount; - free(dyldInfo); - free(infoArray); + // 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; - // 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 + dyld_image_info *infoArray = + reinterpret_cast<dyld_image_info*>(&dyld_info_array_bytes[0]); + images.image_list_.reserve(count); + + for (int i = 0; i < count; ++i) { + dyld_image_info &info = infoArray[i]; + + // 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 + + mach_header_type *header = + reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]); + + // 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; + + if (ReadTaskMemory(images.task_, + info.load_address_, + header_size, + mach_header_bytes) != KERN_SUCCESS) + continue; + + header = reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]); + + // 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_); + } - vector<DynamicImageRef>::iterator it = unique(image_list_.begin(), - image_list_.end() ); - image_list_.erase(it, image_list_.end()); + // 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_); + + 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 diff --git a/src/client/mac/handler/dynamic_images.h b/src/client/mac/handler/dynamic_images.h index 72eb221b..63816bf3 100644 --- a/src/client/mac/handler/dynamic_images.h +++ b/src/client/mac/handler/dynamic_images.h @@ -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; + uint32_t infoArray; // const struct dyld_image_info* + uint32_t notification; + bool processDetachedFromSharedRegion; +} dyld_all_image_infos32; + +typedef struct dyld_all_image_infos64 { uint32_t version; // == 1 in Mac OS X 10.4 uint32_t infoArrayCount; - const struct dyld_image_info *infoArray; - void* notification; + uint64_t infoArray; // const struct dyld_image_info* + uint64_t notification; bool processDetachedFromSharedRegion; -} dyld_all_image_infos; +} 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) {} - - 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); - } +// Helper functions to deal with 32-bit/64-bit Mach-O differences. +class DynamicImage; +template<typename MachBits> +bool FindTextSection(DynamicImage& image); - 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(); - } - } - - 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) ); - - const void *imageHeader = _dyld_get_image_header(i); - header = reinterpret_cast<const breakpad_mach_header*>(imageHeader); - - MachHeader(*header).Print(); - } + // CPU type of the task + cpu_type_t GetCPUType() {return cpu_type_;} + + // Return true if the task is a 64-bit architecture. + bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; } + + // Determine the CPU type of the task being dumped. + static cpu_type_t DetermineTaskCPUType(task_t task); + + // 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 diff --git a/src/client/mac/handler/minidump_generator.cc b/src/client/mac/handler/minidump_generator.cc index 8e3faf27..2e9765ad 100644 --- a/src/client/mac/handler/minidump_generator.cc +++ b/src/client/mac/handler/minidump_generator.cc @@ -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::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); -bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state, + if (!context.Allocate()) + return false; + + *register_location = context.location(); + MDRawContextPPC64 *context_ptr = context.get(); + context_ptr->context_flags = MD_CONTEXT_PPC_BASE; + +#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a) +#define AddGPR(a) context_ptr->gpr[a] = REGISTER_FROM_THREADSTATE(machine_state, r ## a) + + AddReg(srr0); + AddReg(cr); + AddReg(xer); + AddReg(ctr); + AddReg(lr); + AddReg(vrsave); + + AddGPR(0); + AddGPR(1); + AddGPR(2); + AddGPR(3); + AddGPR(4); + AddGPR(5); + AddGPR(6); + AddGPR(7); + AddGPR(8); + AddGPR(9); + AddGPR(10); + AddGPR(11); + AddGPR(12); + AddGPR(13); + AddGPR(14); + AddGPR(15); + AddGPR(16); + AddGPR(17); + AddGPR(18); + AddGPR(19); + AddGPR(20); + AddGPR(21); + AddGPR(22); + AddGPR(23); + AddGPR(24); + AddGPR(25); + AddGPR(26); + AddGPR(27); + AddGPR(28); + AddGPR(29); + AddGPR(30); + AddGPR(31); +#undef AddReg +#undef AddGPR + + return true; +} + +bool MinidumpGenerator::WriteStackX86(breakpad_thread_state_data_t state, MDMemoryDescriptor *stack_location) { - breakpad_thread_state_t *machine_state = - reinterpret_cast<breakpad_thread_state_t *>(state); + i386_thread_state_t *machine_state = + reinterpret_cast<i386_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); +} + +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::CurrentPCForStack(breakpad_thread_state_data_t state) { - breakpad_thread_state_t *machine_state = - reinterpret_cast<breakpad_thread_state_t *>(state); +MinidumpGenerator::CurrentPCForStackX86(breakpad_thread_state_data_t state) { + i386_thread_state_t *machine_state = + reinterpret_cast<i386_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); +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(); - MinidumpContext *context_ptr = context.get(); + MDRawContextX86 *context_ptr = context.get(); #define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a) -#if TARGET_CPU_X86 + context_ptr->context_flags = MD_CONTEXT_X86; AddReg(eax); AddReg(ebx); @@ -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; diff --git a/src/client/mac/handler/minidump_generator.h b/src/client/mac/handler/minidump_generator.h index 39f28402..224ad1c2 100644 --- a/src/client/mac/handler/minidump_generator.h +++ b/src/client/mac/handler/minidump_generator.h @@ -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]; diff --git a/src/client/mac/tests/minidump_generator_test.cc b/src/client/mac/tests/minidump_generator_test.cc index 5d0d5c28..68bc1de6 100644 --- a/src/client/mac/tests/minidump_generator_test.cc +++ b/src/client/mac/tests/minidump_generator_test.cc @@ -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) + } diff --git a/src/client/mac/tests/minidump_generator_test_helper.cc b/src/client/mac/tests/minidump_generator_test_helper.cc new file mode 100644 index 00000000..41ef449d --- /dev/null +++ b/src/client/mac/tests/minidump_generator_test_helper.cc @@ -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; +} diff --git a/src/processor/minidump.cc b/src/processor/minidump.cc index f57001a2..41e7cea6 100644 --- a/src/processor/minidump.cc +++ b/src/processor/minidump.cc @@ -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; |