/* * Authors: * Landon Fuller * Damian Morris * Andreas Linde * * Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc. * Copyright (c) 2010 MOSO Corporation, Pty Ltd. * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. * All rights reserved. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #import "HockeySDK.h" #import "HockeySDKPrivate.h" #if HOCKEYSDK_FEATURE_CRASH_REPORTER #import #import #import #import #import #import #if defined(__OBJC2__) #define SEL_NAME_SECT "__objc_methname" #else #define SEL_NAME_SECT "__cstring" #endif #import "BITCrashReportTextFormatter.h" /* * XXX: The ARM64 CPU type, and ARM_V7S and ARM_V8 Mach-O CPU subtypes are not * defined in the Mac OS X 10.8 headers. */ #ifndef CPU_SUBTYPE_ARM_V7S # define CPU_SUBTYPE_ARM_V7S 11 #endif #ifndef CPU_TYPE_ARM64 #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) #endif #ifndef CPU_SUBTYPE_ARM_V8 # define CPU_SUBTYPE_ARM_V8 13 #endif /** * Sort PLCrashReportBinaryImageInfo instances by their starting address. */ static NSInteger bit_binaryImageSort(id binary1, id binary2, void *__unused context) { uint64_t addr1 = [binary1 imageBaseAddress]; uint64_t addr2 = [binary2 imageBaseAddress]; if (addr1 < addr2) return NSOrderedAscending; else if (addr1 > addr2) return NSOrderedDescending; else return NSOrderedSame; } /** * Validates that the given @a string terminates prior to @a limit. */ static const char *safer_string_read (const char *string, const char *limit) { const char *p = string; do { if (p >= limit || p+1 >= limit) { return NULL; } p++; } while (*p != '\0'); return string; } /* * The relativeAddress should be ` - `, extracted from the crash report's thread * and binary image list. * * For the (architecture-specific) registers to attempt, see: * http://sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html */ static const char *findSEL (const char *imageName, NSString *imageUUID, uint64_t relativeAddress) { unsigned int images_count = _dyld_image_count(); for (unsigned int i = 0; i < images_count; ++i) { intptr_t slide = _dyld_get_image_vmaddr_slide(i); const struct mach_header *header = _dyld_get_image_header(i); const struct mach_header_64 *header64 = (const struct mach_header_64 *) header; const char *name = _dyld_get_image_name(i); /* Image disappeared? */ if (name == NULL || header == NULL) continue; /* Check if this is the correct image. If we were being even more careful, we'd check the LC_UUID */ if (strcmp(name, imageName) != 0) continue; /* Determine whether this is a 64-bit or 32-bit Mach-O file */ BOOL m64 = NO; if (header->magic == MH_MAGIC_64) m64 = YES; NSString *uuidString = nil; const uint8_t *command; uint32_t ncmds; if (m64) { command = (const uint8_t *)(header64 + 1); ncmds = header64->ncmds; } else { command = (const uint8_t *)(header + 1); ncmds = header->ncmds; } for (uint32_t idx = 0; idx < ncmds; ++idx) { const struct load_command *load_command = (const struct load_command *)command; if (load_command->cmd == LC_UUID) { const struct uuid_command *uuid_command = (const struct uuid_command *)command; const uint8_t *uuid = uuid_command->uuid; uuidString = [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]] lowercaseString]; break; } else { command += load_command->cmdsize; } } // Check if this is the correct image by comparing the UUIDs if (!uuidString || ![uuidString isEqualToString:imageUUID]) continue; /* Fetch the __objc_methname section */ const char *methname_sect; uint64_t methname_sect_size; if (m64) { methname_sect = getsectdatafromheader_64(header64, SEG_TEXT, SEL_NAME_SECT, &methname_sect_size); } else { uint32_t meth_size_32; methname_sect = getsectdatafromheader(header, SEG_TEXT, SEL_NAME_SECT, &meth_size_32); methname_sect_size = meth_size_32; } /* Apply the slide, as per getsectdatafromheader(3) */ methname_sect += slide; if (methname_sect == NULL) { return NULL; } /* Calculate the target address within this image, and verify that it is within __objc_methname */ const char *target = ((const char *)header) + relativeAddress; const char *limit = methname_sect + methname_sect_size; if (target < methname_sect || target >= limit) { return NULL; } /* Read the actual method name */ return safer_string_read(target, limit); } return NULL; } /** * Formats PLCrashReport data as human-readable text. */ @implementation BITCrashReportTextFormatter static NSString *const BITXamarinStackTraceDelimiter = @"Xamarin Exception Stack:"; /** * Formats the provided @a report as human-readable text in the given @a textFormat, and return * the formatted result as a string. * * @param report The report to format. * @param crashReporterKey The crash reporter key. * * @return Returns the formatted result on success, or nil if an error occurs. */ + (NSString *)stringValueForCrashReport:(BITPLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey { NSMutableString* text = [NSMutableString string]; boolean_t lp64 = true; // quiesce GCC uninitialized value warning /* Header */ /* Map to apple style OS name */ NSString *osName; switch (report.systemInfo.operatingSystem) { case PLCrashReportOperatingSystemMacOSX: osName = @"Mac OS X"; break; case PLCrashReportOperatingSystemiPhoneOS: osName = @"iPhone OS"; break; case PLCrashReportOperatingSystemiPhoneSimulator: osName = @"Mac OS X"; break; default: osName = [NSString stringWithFormat: @"Unknown (%d)", report.systemInfo.operatingSystem]; break; } /* Map to Apple-style code type, and mark whether architecture is LP64 (64-bit) */ NSString *codeType = nil; { /* Attempt to derive the code type from the binary images */ for (BITPLCrashReportBinaryImageInfo *image in report.images) { /* Skip images with no specified type */ if (image.codeType == nil) continue; /* Skip unknown encodings */ if (image.codeType.typeEncoding != PLCrashReportProcessorTypeEncodingMach) continue; switch (image.codeType.type) { case CPU_TYPE_ARM: codeType = @"ARM"; lp64 = false; break; case CPU_TYPE_ARM64: codeType = @"ARM-64"; lp64 = true; break; case CPU_TYPE_X86: codeType = @"X86"; lp64 = false; break; case CPU_TYPE_X86_64: codeType = @"X86-64"; lp64 = true; break; case CPU_TYPE_POWERPC: codeType = @"PPC"; lp64 = false; break; default: // Do nothing, handled below. break; } /* Stop immediately if code type was discovered */ if (codeType != nil) break; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" /* If we were unable to determine the code type, fall back on the legacy architecture value. */ if (codeType == nil) { switch (report.systemInfo.architecture) { case PLCrashReportArchitectureARMv6: case PLCrashReportArchitectureARMv7: codeType = @"ARM"; lp64 = false; break; case PLCrashReportArchitectureX86_32: codeType = @"X86"; lp64 = false; break; case PLCrashReportArchitectureX86_64: codeType = @"X86-64"; lp64 = true; break; case PLCrashReportArchitecturePPC: codeType = @"PPC"; lp64 = false; break; default: codeType = [NSString stringWithFormat: @"Unknown (%d)", report.systemInfo.architecture]; lp64 = true; break; } } #pragma GCC diagnostic pop } { NSString *reporterKey = @"???"; if (crashReporterKey && [crashReporterKey length] > 0) reporterKey = crashReporterKey; NSString *hardwareModel = @"???"; if (report.hasMachineInfo && report.machineInfo.modelName != nil) hardwareModel = report.machineInfo.modelName; NSString *incidentIdentifier = @"???"; if (report.uuidRef != NULL) { incidentIdentifier = (NSString *) CFBridgingRelease(CFUUIDCreateString(NULL, report.uuidRef)); } [text appendFormat: @"Incident Identifier: %@\n", incidentIdentifier]; [text appendFormat: @"CrashReporter Key: %@\n", reporterKey]; [text appendFormat: @"Hardware Model: %@\n", hardwareModel]; } /* Application and process info */ { NSString *unknownString = @"???"; NSString *processName = unknownString; NSString *processId = unknownString; NSString *processPath = unknownString; NSString *parentProcessName = unknownString; NSString *parentProcessId = unknownString; /* Process information was not available in earlier crash report versions */ if (report.hasProcessInfo) { /* Process Name */ if (report.processInfo.processName != nil) processName = report.processInfo.processName; /* PID */ processId = [@(report.processInfo.processID) stringValue]; /* Process Path */ if (report.processInfo.processPath != nil) { processPath = report.processInfo.processPath; /* Remove username from the path */ #if TARGET_OS_SIMULATOR processPath = [self anonymizedPathFromPath:processPath]; #endif } /* Parent Process Name */ if (report.processInfo.parentProcessName != nil) parentProcessName = report.processInfo.parentProcessName; /* Parent Process ID */ parentProcessId = [@(report.processInfo.parentProcessID) stringValue]; } [text appendFormat: @"Process: %@ [%@]\n", processName, processId]; [text appendFormat: @"Path: %@\n", processPath]; [text appendFormat: @"Identifier: %@\n", report.applicationInfo.applicationIdentifier]; NSString *marketingVersion = report.applicationInfo.applicationMarketingVersion; NSString *appVersion = report.applicationInfo.applicationVersion; NSString *versionString = marketingVersion ? [NSString stringWithFormat:@"%@ (%@)", marketingVersion, appVersion] : appVersion; [text appendFormat: @"Version: %@\n", versionString]; [text appendFormat: @"Code Type: %@\n", codeType]; [text appendFormat: @"Parent Process: %@ [%@]\n", parentProcessName, parentProcessId]; } [text appendString: @"\n"]; NSString *xamarinTrace; NSString *exceptionReason; /* System info */ { NSString *osBuild = @"???"; if (report.systemInfo.operatingSystemBuild != nil) osBuild = report.systemInfo.operatingSystemBuild; NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; NSDateFormatter *rfc3339Formatter = [[NSDateFormatter alloc] init]; [rfc3339Formatter setLocale:enUSPOSIXLocale]; [rfc3339Formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"]; [rfc3339Formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; [text appendFormat: @"Date/Time: %@\n", [rfc3339Formatter stringFromDate:report.systemInfo.timestamp]]; if ([report.processInfo respondsToSelector:@selector(processStartTime)]) { if (report.systemInfo.timestamp && report.processInfo.processStartTime) { [text appendFormat: @"Launch Time: %@\n", [rfc3339Formatter stringFromDate:report.processInfo.processStartTime]]; } } [text appendFormat: @"OS Version: %@ %@ (%@)\n", osName, report.systemInfo.operatingSystemVersion, osBuild]; // Check if exception data contains xamarin stacktrace in order to determine report version if (report.hasExceptionInfo) { exceptionReason = report.exceptionInfo.exceptionReason; NSInteger xamarinTracePosition = [exceptionReason rangeOfString:BITXamarinStackTraceDelimiter].location; if (xamarinTracePosition != NSNotFound) { xamarinTrace = [exceptionReason substringFromIndex:xamarinTracePosition]; xamarinTrace = [xamarinTrace stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; xamarinTrace = [xamarinTrace stringByReplacingOccurrencesOfString:@"<---\n\n--->" withString:@"<---\n--->"]; exceptionReason = [exceptionReason substringToIndex:xamarinTracePosition]; exceptionReason = [exceptionReason stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } } NSString *reportVersion = (xamarinTrace) ? @"104-Xamarin" : @"104"; [text appendFormat: @"Report Version: %@\n", reportVersion]; } [text appendString: @"\n"]; /* Exception code */ [text appendFormat: @"Exception Type: %@\n", report.signalInfo.name]; [text appendFormat: @"Exception Codes: %@ at 0x%" PRIx64 "\n", report.signalInfo.code, report.signalInfo.address]; for (BITPLCrashReportThreadInfo *thread in report.threads) { if (thread.crashed) { [text appendFormat: @"Crashed Thread: %ld\n", (long) thread.threadNumber]; break; } } [text appendString: @"\n"]; BITPLCrashReportThreadInfo *crashed_thread = nil; for (BITPLCrashReportThreadInfo *thread in report.threads) { if (thread.crashed) { crashed_thread = thread; break; } } /* Uncaught Exception */ if (report.hasExceptionInfo) { [text appendFormat: @"Application Specific Information:\n"]; [text appendFormat: @"*** Terminating app due to uncaught exception '%@', reason: '%@'\n", report.exceptionInfo.exceptionName, exceptionReason]; [text appendString: @"\n"]; /* Xamarin Exception */ if (xamarinTrace) { [text appendFormat:@"%@\n", xamarinTrace]; [text appendString: @"\n"]; } } else if (crashed_thread != nil) { // try to find the selector in case this was a crash in obj_msgSend // we search this whether the crash happened in obj_msgSend or not since we don't have the symbol! NSString *foundSelector = nil; // search the registers value for the current arch #if TARGET_OS_SIMULATOR if (lp64) { foundSelector = [[self class] selectorForRegisterWithName:@"rsi" ofThread:crashed_thread report:report]; if (foundSelector == NULL) foundSelector = [[self class] selectorForRegisterWithName:@"rdx" ofThread:crashed_thread report:report]; } else { foundSelector = [[self class] selectorForRegisterWithName:@"ecx" ofThread:crashed_thread report:report]; } #else if (lp64) { foundSelector = [[self class] selectorForRegisterWithName:@"x1" ofThread:crashed_thread report:report]; } else { foundSelector = [[self class] selectorForRegisterWithName:@"r1" ofThread:crashed_thread report:report]; if (foundSelector == NULL) foundSelector = [[self class] selectorForRegisterWithName:@"r2" ofThread:crashed_thread report:report]; } #endif if (foundSelector) { [text appendFormat: @"Application Specific Information:\n"]; [text appendFormat: @"Selector name found in current argument registers: %@\n", foundSelector]; [text appendString: @"\n"]; } } /* If an exception stack trace is available, output an Apple-compatible backtrace. */ if (report.exceptionInfo != nil && report.exceptionInfo.stackFrames != nil && [report.exceptionInfo.stackFrames count] > 0) { BITPLCrashReportExceptionInfo *exception = report.exceptionInfo; /* Create the header. */ [text appendString: @"Last Exception Backtrace:\n"]; /* Write out the frames. In raw reports, Apple writes this out as a simple list of PCs. In the minimally * post-processed report, Apple writes this out as full frame entries. We use the latter format. */ for (NSUInteger frame_idx = 0; frame_idx < [exception.stackFrames count]; frame_idx++) { BITPLCrashReportStackFrameInfo *frameInfo = exception.stackFrames[frame_idx]; [text appendString: [[self class] bit_formatStackFrame: frameInfo frameIndex: frame_idx report: report lp64: lp64]]; } [text appendString: @"\n"]; } /* Threads */ NSInteger maxThreadNum = 0; for (BITPLCrashReportThreadInfo *thread in report.threads) { if (thread.crashed) { [text appendFormat: @"Thread %ld Crashed:\n", (long) thread.threadNumber]; } else { [text appendFormat: @"Thread %ld:\n", (long) thread.threadNumber]; } for (NSUInteger frame_idx = 0; frame_idx < [thread.stackFrames count]; frame_idx++) { BITPLCrashReportStackFrameInfo *frameInfo = thread.stackFrames[frame_idx]; [text appendString:[[self class] bit_formatStackFrame:frameInfo frameIndex:frame_idx report:report lp64:lp64]]; } [text appendString: @"\n"]; /* Track the highest thread number */ maxThreadNum = MAX(maxThreadNum, thread.threadNumber); } /* Registers */ if (crashed_thread != nil) { [text appendFormat: @"Thread %ld crashed with %@ Thread State:\n", (long) crashed_thread.threadNumber, codeType]; int regColumn = 0; for (BITPLCrashReportRegisterInfo *reg in crashed_thread.registers) { NSString *reg_fmt; /* Use 32-bit or 64-bit fixed width format for the register values */ if (lp64) reg_fmt = @"%6s: 0x%016" PRIx64 " "; else reg_fmt = @"%6s: 0x%08" PRIx64 " "; /* Remap register names to match Apple's crash reports */ NSString *regName = reg.registerName; if (report.machineInfo != nil && report.machineInfo.processorInfo.typeEncoding == PLCrashReportProcessorTypeEncodingMach) { BITPLCrashReportProcessorInfo *pinfo = report.machineInfo.processorInfo; cpu_type_t arch_type = (cpu_type_t)(pinfo.type & ~CPU_ARCH_MASK); /* Apple uses 'ip' rather than 'r12' on ARM */ if (arch_type == CPU_TYPE_ARM && [regName isEqual: @"r12"]) { regName = @"ip"; } } [text appendFormat: reg_fmt, [regName UTF8String], reg.registerValue]; regColumn++; if (regColumn == 4) { [text appendString: @"\n"]; regColumn = 0; } } if (regColumn != 0) [text appendString: @"\n"]; [text appendString: @"\n"]; } /* Images. The iPhone crash report format sorts these in ascending order, by the base address */ [text appendString: @"Binary Images:\n"]; NSMutableArray *addedImagesBaseAddresses = @[].mutableCopy; for (BITPLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: bit_binaryImageSort context: nil]) { // Make sure we don't add duplicates if ([addedImagesBaseAddresses containsObject:@(imageInfo.imageBaseAddress)]) { continue; } else { [addedImagesBaseAddresses addObject:@(imageInfo.imageBaseAddress)]; } NSString *uuid; /* Fetch the UUID if it exists */ if (imageInfo.hasImageUUID) uuid = imageInfo.imageUUID; else uuid = @"???"; /* Determine the architecture string */ NSString *archName = [[self class] bit_archNameFromImageInfo:imageInfo]; /* Determine if this is the main executable or an app specific framework*/ NSString *binaryDesignator = @" "; BITBinaryImageType imageType = [[self class] bit_imageTypeForImagePath:imageInfo.imageName processPath:report.processInfo.processPath]; if (imageType != BITBinaryImageTypeOther) { binaryDesignator = @"+"; } /* base_address - terminating_address [designator]file_name arch file_path */ NSString *fmt = nil; if (lp64) { fmt = @"%18#" PRIx64 " - %18#" PRIx64 " %@%@ %@ <%@> %@\n"; } else { fmt = @"%10#" PRIx64 " - %10#" PRIx64 " %@%@ %@ <%@> %@\n"; } /* Remove username from the image path */ NSString *imageName = @""; if (imageInfo.imageName && [imageInfo.imageName length] > 0) { #if TARGET_OS_SIMULATOR imageName = [imageInfo.imageName stringByAbbreviatingWithTildeInPath]; #else imageName = imageInfo.imageName; #endif } #if TARGET_OS_SIMULATOR imageName = [self anonymizedPathFromPath:imageName]; #endif [text appendFormat: fmt, imageInfo.imageBaseAddress, imageInfo.imageBaseAddress + (MAX(1U, imageInfo.imageSize) - 1), // The Apple format uses an inclusive range binaryDesignator, [imageInfo.imageName lastPathComponent], archName, uuid, imageName]; } return text; } /** * Return the selector string of a given register name * * @param regName The name of the register to use for getting the address * @param thread The crashed thread * @param report The crash report. * * @return The selector as a C string or NULL if no selector was found */ + (NSString *)selectorForRegisterWithName:(NSString *)regName ofThread:(BITPLCrashReportThreadInfo *)thread report:(BITPLCrashReport *)report { // get the address for the register uint64_t regAddress = 0; for (BITPLCrashReportRegisterInfo *reg in thread.registers) { if ([reg.registerName isEqualToString:regName]) { regAddress = reg.registerValue; break; } } if (regAddress == 0) return nil; BITPLCrashReportBinaryImageInfo *imageForRegAddress = [report imageForAddress:regAddress]; if (imageForRegAddress) { // get the SEL const char *foundSelector = findSEL([imageForRegAddress.imageName UTF8String], imageForRegAddress.imageUUID, regAddress - (uint64_t)imageForRegAddress.imageBaseAddress); if (foundSelector != NULL) { return [NSString stringWithUTF8String:foundSelector]; } } return nil; } /** * Returns an array of app UUIDs and their architecture * As a dictionary for each element * * @param report The report to format. * * @return Returns the formatted result on success, or nil if an error occurs. */ + (NSArray *)arrayOfAppUUIDsForCrashReport:(BITPLCrashReport *)report { NSMutableArray* appUUIDs = [NSMutableArray array]; /* Images. The iPhone crash report format sorts these in ascending order, by the base address */ for (BITPLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: bit_binaryImageSort context: nil]) { NSString *uuid; /* Fetch the UUID if it exists */ uuid = imageInfo.hasImageUUID ? imageInfo.imageUUID : @"???"; /* Determine the architecture string */ NSString *archName = [[self class] bit_archNameFromImageInfo:imageInfo]; /* Determine if this is the app executable or app specific framework */ BITBinaryImageType imageType = [[self class] bit_imageTypeForImagePath:imageInfo.imageName processPath:report.processInfo.processPath]; if (imageType != BITBinaryImageTypeOther) { NSString *imageTypeString; if (imageType == BITBinaryImageTypeAppBinary) { imageTypeString = @"app"; } else { imageTypeString = @"framework"; } [appUUIDs addObject:@{kBITBinaryImageKeyUUID: uuid, kBITBinaryImageKeyArch: archName, kBITBinaryImageKeyType: imageTypeString} ]; } } return appUUIDs; } /* Determine if in binary image is the app executable or app specific framework */ + (BITBinaryImageType)bit_imageTypeForImagePath:(NSString *)imagePath processPath:(NSString *)processPath { if (!imagePath || !processPath) { return BITBinaryImageTypeOther; } BITBinaryImageType imageType = BITBinaryImageTypeOther; NSString *standardizedImagePath = [[imagePath stringByStandardizingPath] lowercaseString]; NSString *lowercaseImagePath = [imagePath lowercaseString]; NSString *lowercaseProcessPath = [processPath lowercaseString]; NSRange appRange = [standardizedImagePath rangeOfString: @".app/"]; // Exclude iOS swift dylibs. These are provided as part of the app binary by Xcode for now, but we never get a dSYM for those. NSRange swiftLibRange = [standardizedImagePath rangeOfString:@"frameworks/libswift"]; BOOL dylibSuffix = [standardizedImagePath hasSuffix:@".dylib"]; if (appRange.location != NSNotFound && !(swiftLibRange.location != NSNotFound && dylibSuffix)) { NSString *appBundleContentsPath = [standardizedImagePath substringToIndex:appRange.location + 5]; if ([standardizedImagePath isEqual: lowercaseProcessPath] || // Fix issue with iOS 8 `stringByStandardizingPath` removing leading `/private` path (when not running in the debugger or simulator only) [lowercaseImagePath hasPrefix:lowercaseProcessPath]) { imageType = BITBinaryImageTypeAppBinary; } else if ([standardizedImagePath hasPrefix:appBundleContentsPath] || // Fix issue with iOS 8 `stringByStandardizingPath` removing leading `/private` path (when not running in the debugger or simulator only) [lowercaseImagePath hasPrefix:appBundleContentsPath]) { imageType = BITBinaryImageTypeAppFramework; } } return imageType; } + (NSString *)bit_archNameFromImageInfo:(BITPLCrashReportBinaryImageInfo *)imageInfo { NSString *archName = @"???"; if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) { archName = [BITCrashReportTextFormatter bit_archNameFromCPUType:imageInfo.codeType.type subType:imageInfo.codeType.subtype]; } return archName; } + (NSString *)bit_archNameFromCPUType:(uint64_t)cpuType subType:(uint64_t)subType { NSString *archName = @"???"; switch (cpuType) { case CPU_TYPE_ARM: /* Apple includes subtype for ARM binaries. */ switch (subType) { case CPU_SUBTYPE_ARM_V6: archName = @"armv6"; break; case CPU_SUBTYPE_ARM_V7: archName = @"armv7"; break; case CPU_SUBTYPE_ARM_V7S: archName = @"armv7s"; break; default: archName = @"arm-unknown"; break; } break; case CPU_TYPE_ARM64: /* Apple includes subtype for ARM64 binaries. */ switch (subType) { case CPU_SUBTYPE_ARM_ALL: archName = @"arm64"; break; case CPU_SUBTYPE_ARM_V8: archName = @"arm64"; break; default: archName = @"arm64-unknown"; break; } break; case CPU_TYPE_X86: archName = @"i386"; break; case CPU_TYPE_X86_64: archName = @"x86_64"; break; case CPU_TYPE_POWERPC: archName = @"powerpc"; break; default: // Use the default archName value (initialized above). break; } return archName; } /** * Format a stack frame for display in a thread backtrace. * * @param frameInfo The stack frame to format * @param frameIndex The frame's index * @param report The report from which this frame was acquired. * @param lp64 If YES, the report was generated by an LP64 system. * * @return Returns a formatted frame line. */ + (NSString *)bit_formatStackFrame: (BITPLCrashReportStackFrameInfo *) frameInfo frameIndex: (NSUInteger) frameIndex report: (BITPLCrashReport *) report lp64: (boolean_t) lp64 { /* Base image address containing instrumentation pointer, offset of the IP from that base * address, and the associated image name */ uint64_t baseAddress = 0x0; uint64_t pcOffset = 0x0; NSString *imageName = @"\?\?\?"; NSString *symbolString = nil; BITPLCrashReportBinaryImageInfo *imageInfo = [report imageForAddress: frameInfo.instructionPointer]; if (imageInfo != nil) { imageName = [imageInfo.imageName lastPathComponent]; baseAddress = imageInfo.imageBaseAddress; pcOffset = frameInfo.instructionPointer - imageInfo.imageBaseAddress; } /* Make sure UTF8/16 characters are handled correctly */ NSInteger offset = 0; NSUInteger index = 0; for (index = 0; index < [imageName length]; index++) { NSRange range = [imageName rangeOfComposedCharacterSequenceAtIndex:index]; if (range.length > 1) { offset += range.length - 1; index += range.length - 1; } if (index > 32) { imageName = [NSString stringWithFormat:@"%@... ", [imageName substringToIndex:index - 1]]; index += 3; break; } } if (index-offset < 36) { imageName = [imageName stringByPaddingToLength:(NSUInteger)(36 + offset) withString:@" " startingAtIndex:0]; } /* If symbol info is available, the format used in Apple's reports is Sym + OffsetFromSym. Otherwise, * the format used is imageBaseAddress + offsetToIP */ BITBinaryImageType imageType = [[self class] bit_imageTypeForImagePath:imageInfo.imageName processPath:report.processInfo.processPath]; if (frameInfo.symbolInfo != nil && imageType == BITBinaryImageTypeOther) { NSString *symbolName = frameInfo.symbolInfo.symbolName; /* Apple strips the _ symbol prefix in their reports. Only OS X makes use of an * underscore symbol prefix by default. */ if ([symbolName rangeOfString: @"_"].location == 0 && [symbolName length] > 1) { switch (report.systemInfo.operatingSystem) { case PLCrashReportOperatingSystemMacOSX: case PLCrashReportOperatingSystemiPhoneOS: case PLCrashReportOperatingSystemiPhoneSimulator: symbolName = [symbolName substringFromIndex: 1]; break; default: BITHockeyLogDebug(@"Symbol prefix rules are unknown for this OS!"); break; } } uint64_t symOffset = frameInfo.instructionPointer - frameInfo.symbolInfo.startAddress; symbolString = [NSString stringWithFormat: @"%@ + %" PRId64, symbolName, symOffset]; } else { symbolString = [NSString stringWithFormat: @"0x%" PRIx64 " + %" PRId64, baseAddress, pcOffset]; } /* Note that width specifiers are ignored for %@, but work for C strings. * UTF-8 is not correctly handled with %s (it depends on the system encoding), but * UTF-16 is supported via %S, so we use it here */ return [NSString stringWithFormat: @"%-4ld%-35S 0x%0*" PRIx64 " %@\n", (long) frameIndex, (const uint16_t *)[imageName cStringUsingEncoding: NSUTF16StringEncoding], lp64 ? 16 : 8, frameInfo.instructionPointer, symbolString]; } /** * Remove the user's name from a crash's process path. * This is only necessary when sending crashes from the simulator as the path * then contains the username of the Mac the simulator is running on. * * @param path A string containing the username. * * @return An anonymized string where the real username is replaced by "USER" */ + (NSString *)anonymizedPathFromPath:(NSString *)path { NSString *anonymizedProcessPath = [NSString string]; if (([path length] > 0) && [path hasPrefix:@"/Users/"]) { NSError *error = nil; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(/Users/[^/]+/)" options:0 error:&error]; anonymizedProcessPath = [regex stringByReplacingMatchesInString:path options:0 range:NSMakeRange(0, [path length]) withTemplate:@"/Users/USER/"]; if (error) { BITHockeyLogError(@"ERROR: String replacing failed - %@", error.localizedDescription); } } else if(([path length] > 0) && (![path containsString:@"Users"])) { return path; } return anonymizedProcessPath; } @end #endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */