/* * Authors: * Landon Fuller * Damian Morris * Andreas Linde * * Copyright (c) 2008-2012 Plausible Labs Cooperative, Inc. * Copyright (c) 2010 MOSO Corporation, Pty Ltd. * Copyright (c) 2012 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 #import "BITCrashReportTextFormatter.h" @interface BITCrashReportTextFormatter (PrivateAPI) NSInteger binaryImageSort(id binary1, id binary2, void *context); + (NSString *)formatStackFrame:(PLCrashReportStackFrameInfo *)frameInfo frameIndex:(NSUInteger)frameIndex report:(PLCrashReport *)report; @end /** * Formats PLCrashReport data as human-readable text. */ @implementation BITCrashReportTextFormatter /** * 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. * * @return Returns the formatted result on success, or nil if an error occurs. */ + (NSString *)stringValueForCrashReport:(PLCrashReport *)report { NSMutableString* text = [NSMutableString string]; boolean_t lp64 = true; // quiesce GCC uninitialized value warning /* Header */ /* Map to apple style OS nane */ 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 (PLCrashReportBinaryImageInfo *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_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; } /* 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; } } } { NSString *reportGUID = @"[TODO]"; if (report.hasReportInfo && report.reportInfo.reportGUID != nil) reportGUID = report.reportInfo.reportGUID; NSString *hardwareModel = @"???"; if (report.hasMachineInfo && report.machineInfo.modelName != nil) hardwareModel = report.machineInfo.modelName; [text appendFormat: @"Incident Identifier: %@\n", reportGUID]; [text appendFormat: @"CrashReporter Key: [TODO]\n"]; [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 = [[NSNumber numberWithUnsignedInteger: report.processInfo.processID] stringValue]; /* Process Path */ if (report.processInfo.processPath != nil) { processPath = report.processInfo.processPath; /* Remove username from the path */ processPath = [processPath stringByAbbreviatingWithTildeInPath]; if ([[processPath substringToIndex:1] isEqualToString:@"~"]) processPath = [NSString stringWithFormat:@"/Users/USER%@", [processPath substringFromIndex:1]]; } /* Parent Process Name */ if (report.processInfo.parentProcessName != nil) parentProcessName = report.processInfo.parentProcessName; /* Parent Process ID */ parentProcessId = [[NSNumber numberWithUnsignedInteger: report.processInfo.parentProcessID] stringValue]; } [text appendFormat: @"Process: %@ [%@]\n", processName, processId]; [text appendFormat: @"Path: %@\n", processPath]; [text appendFormat: @"Identifier: %@\n", report.applicationInfo.applicationIdentifier]; [text appendFormat: @"Version: %@\n", report.applicationInfo.applicationVersion]; [text appendFormat: @"Code Type: %@\n", codeType]; [text appendFormat: @"Parent Process: %@ [%@]\n", parentProcessName, parentProcessId]; } [text appendString: @"\n"]; /* System info */ { NSString *osBuild = @"???"; if (report.systemInfo.operatingSystemBuild != nil) osBuild = report.systemInfo.operatingSystemBuild; [text appendFormat: @"Date/Time: %@\n", report.systemInfo.timestamp]; [text appendFormat: @"OS Version: %@ %@ (%@)\n", osName, report.systemInfo.operatingSystemVersion, osBuild]; [text appendFormat: @"Report Version: 104\n"]; } [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 (PLCrashReportThreadInfo *thread in report.threads) { if (thread.crashed) { [text appendFormat: @"Crashed Thread: %ld\n", (long) thread.threadNumber]; break; } } [text appendString: @"\n"]; /* Uncaught Exception */ if (report.hasExceptionInfo) { [text appendFormat: @"Application Specific Information:\n"]; [text appendFormat: @"*** Terminating app due to uncaught exception '%@', reason: '%@'\n", report.exceptionInfo.exceptionName, report.exceptionInfo.exceptionReason]; [text appendString: @"\n"]; } /* If an exception stack trace is available, output a pseudo-thread to provide the frame info */ if (report.exceptionInfo != nil && report.exceptionInfo.stackFrames != nil && [report.exceptionInfo.stackFrames count] > 0) { PLCrashReportExceptionInfo *exception = report.exceptionInfo; /* Write out the frames */ NSUInteger numberBlankStackFrames = 0; for (NSUInteger frame_idx = 0; frame_idx < [exception.stackFrames count]; frame_idx++) { PLCrashReportStackFrameInfo *frameInfo = [exception.stackFrames objectAtIndex: frame_idx]; NSString *formattedStackFrame = [self formatStackFrame: frameInfo frameIndex: frame_idx - numberBlankStackFrames report: report]; if (formattedStackFrame) { if (frame_idx - numberBlankStackFrames == 0) { /* Create the pseudo-thread header. We use the named thread format to mark this thread */ [text appendString: @"Last Exception Backtrace:\n"]; } [text appendString: formattedStackFrame]; } else { numberBlankStackFrames++; } } [text appendString: @"\n\n"]; } /* Threads */ PLCrashReportThreadInfo *crashed_thread = nil; NSInteger maxThreadNum = 0; for (PLCrashReportThreadInfo *thread in report.threads) { /* Write out the frames */ NSUInteger numberBlankStackFrames = 0; for (NSUInteger frame_idx = 0; frame_idx < [thread.stackFrames count]; frame_idx++) { PLCrashReportStackFrameInfo *frameInfo = [thread.stackFrames objectAtIndex: frame_idx]; NSString *formattedStackFrame = [self formatStackFrame: frameInfo frameIndex: frame_idx report: report]; if (formattedStackFrame) { if (frame_idx - numberBlankStackFrames == 0) { /* Create the thread header. */ if (thread.crashed) { [text appendFormat: @"Thread %ld Crashed:\n", (long) thread.threadNumber]; crashed_thread = thread; } else { [text appendFormat: @"Thread %ld:\n", (long) thread.threadNumber]; } } [text appendString: formattedStackFrame]; } else { numberBlankStackFrames++; } } [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 (PLCrashReportRegisterInfo *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) { PLCrashReportProcessorInfo *pinfo = report.machineInfo.processorInfo; cpu_type_t arch_type = 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"]; for (PLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: binaryImageSort context: nil]) { NSString *uuid; /* Fetch the UUID if it exists */ if (imageInfo.hasImageUUID) uuid = imageInfo.imageUUID; else uuid = @"???"; /* Determine the architecture string */ NSString *archName = @"???"; if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) { switch (imageInfo.codeType.type) { case CPU_TYPE_ARM: /* Apple includes subtype for ARM binaries. */ switch (imageInfo.codeType.subtype) { case CPU_SUBTYPE_ARM_V6: archName = @"armv6"; break; case CPU_SUBTYPE_ARM_V7: archName = @"armv7"; break; default: archName = @"arm-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; } } /* Determine if this is the main executable */ NSString *binaryDesignator = @" "; if ([imageInfo.imageName isEqual: report.processInfo.processPath]) 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 = [imageInfo.imageName stringByAbbreviatingWithTildeInPath]; if ([[imageName substringToIndex:1] isEqualToString:@"~"]) imageName = [NSString stringWithFormat:@"/Users/USER%@", [imageName substringFromIndex:1]]; [text appendFormat: fmt, imageInfo.imageBaseAddress, imageInfo.imageBaseAddress + (MAX(1, imageInfo.imageSize) - 1), // The Apple format uses an inclusive range binaryDesignator, [imageInfo.imageName lastPathComponent], archName, uuid, imageName]; } return text; } /** * 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:(PLCrashReport *)report { NSMutableArray* appUUIDs = [NSMutableArray array]; /* Images. The iPhone crash report format sorts these in ascending order, by the base address */ for (PLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: binaryImageSort context: nil]) { NSString *uuid; /* Fetch the UUID if it exists */ if (imageInfo.hasImageUUID) uuid = imageInfo.imageUUID; else uuid = @"???"; /* Determine the architecture string */ NSString *archName = @"???"; if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) { switch (imageInfo.codeType.type) { case CPU_TYPE_ARM: /* Apple includes subtype for ARM binaries. */ switch (imageInfo.codeType.subtype) { case CPU_SUBTYPE_ARM_V6: archName = @"armv6"; break; case CPU_SUBTYPE_ARM_V7: archName = @"armv7"; break; default: archName = @"arm-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; } } /* Determine if this is the app executable or app specific framework */ NSString *imagePath = [imageInfo.imageName stringByStandardizingPath]; NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent]; NSString *imageType = @""; if ([imageInfo.imageName isEqual: report.processInfo.processPath]) { imageType = @"app"; } else { imageType = @"framework"; } if ([imagePath isEqual: report.processInfo.processPath] || [imagePath hasPrefix:appBundleContentsPath]) { [appUUIDs addObject:[NSDictionary dictionaryWithObjectsAndKeys:uuid, kBITBinaryImageKeyUUID, archName, kBITBinaryImageKeyArch, imageType, kBITBinaryImageKeyType, nil]]; } } return appUUIDs; } @end @implementation BITCrashReportTextFormatter (PrivateAPI) /** * 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. * * @return Returns a formatted frame line. */ + (NSString *)formatStackFrame: (PLCrashReportStackFrameInfo *) frameInfo frameIndex: (NSUInteger) frameIndex report: (PLCrashReport *) report { /* Base image address containing instrumention 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 *symbol = nil; PLCrashReportBinaryImageInfo *imageInfo = [report imageForAddress: frameInfo.instructionPointer]; if (imageInfo != nil) { imageName = [imageInfo.imageName lastPathComponent]; baseAddress = imageInfo.imageBaseAddress; pcOffset = frameInfo.instructionPointer - imageInfo.imageBaseAddress; NSString *imagePath = [imageInfo.imageName stringByStandardizingPath]; NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent]; if (![imagePath isEqual: report.processInfo.processPath] && ![imagePath hasPrefix:appBundleContentsPath]) { symbol = frameInfo.symbolName; pcOffset = frameInfo.instructionPointer - frameInfo.symbolStart; } } /* The frame has nothing useful, so return nil so it can be filtered out */ if (frameInfo.instructionPointer == 0 && baseAddress == 0 && pcOffset == 0) { return nil; } /* Make sure UTF8/16 characters are handled correctly */ NSInteger offset = 0; NSInteger 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:36+offset withString:@" " startingAtIndex:0]; } if (symbol) { return [NSString stringWithFormat: @"%-4ld%@0x%08" PRIx64 " %@ + %" PRId64 "\n", (long) frameIndex, imageName, frameInfo.instructionPointer, symbol, pcOffset]; } else { return [NSString stringWithFormat: @"%-4ld%@0x%08" PRIx64 " 0x%" PRIx64 " + %" PRId64 "\n", (long) frameIndex, imageName, frameInfo.instructionPointer, baseAddress, pcOffset]; } } /** * Sort PLCrashReportBinaryImageInfo instances by their starting address. */ NSInteger binaryImageSort(id binary1, id binary2, void *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; } @end