diff --git a/Classes/BITCrashReportTextFormatter.h b/Classes/BITCrashReportTextFormatter.h index 5ddef4a22d..a1d6269b5e 100644 --- a/Classes/BITCrashReportTextFormatter.h +++ b/Classes/BITCrashReportTextFormatter.h @@ -44,11 +44,31 @@ #endif +/** + * HockeySDK Crash Reporter error domain + */ +typedef NS_ENUM (NSInteger, BITBinaryImageType) { + /** + * App binary + */ + BITBinaryImageTypeAppBinary, + /** + * App provided framework + */ + BITBinaryImageTypeAppFramework, + /** + * Image not related to the app + */ + BITBinaryImageTypeOther +}; + + @interface BITCrashReportTextFormatter : NSObject { } + (NSString *)stringValueForCrashReport:(PLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey; + (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report; + (NSString *)bit_archNameFromCPUType:(uint64_t)cpuType subType:(uint64_t)subType; ++ (BITBinaryImageType)bit_imageTypeForImagePath:(NSString *)imagePath processPath:(NSString *)processPath; @end diff --git a/Classes/BITCrashReportTextFormatter.m b/Classes/BITCrashReportTextFormatter.m index 02a1a62941..c9599b7fef 100644 --- a/Classes/BITCrashReportTextFormatter.m +++ b/Classes/BITCrashReportTextFormatter.m @@ -536,14 +536,9 @@ static const char *findSEL (const char *imageName, NSString *imageUUID, uint64_t /* Determine if this is the main executable or an app specific framework*/ NSString *binaryDesignator = @" "; - NSString *imagePath = [imageInfo.imageName stringByStandardizingPath]; - NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent]; - - // exclude iOS swift dylibs - if ([imageInfo.imageName rangeOfString:@".app/Frameworks/libswift"].location == NSNotFound) { - if ([imagePath isEqual: report.processInfo.processPath] || - [imagePath hasPrefix:appBundleContentsPath] || - [imageInfo.imageName hasPrefix:appBundleContentsPath]) // Fix issue with iOS 8 `stringByStandardizingPath` removing leading `/private` path (when not running in the debugger only) + BITBinaryImageType imageType = [[self class] bit_imageTypeForImagePath:imageInfo.imageName + processPath:report.processInfo.processPath]; + if (imageType != BITBinaryImageTypeOther) { binaryDesignator = @"+"; } @@ -635,27 +630,60 @@ static const char *findSEL (const char *imageName, NSString *imageUUID, uint64_t /* Determine the architecture string */ NSString *archName = [[self class] bit_archNameFromImageInfo:imageInfo]; - + /* 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 = @""; + BITBinaryImageType imageType = [[self class] bit_imageTypeForImagePath:imageInfo.imageName + processPath:report.processInfo.processPath]; + NSString *imageTypeString = @""; - if ([imageInfo.imageName isEqual: report.processInfo.processPath]) { - imageType = @"app"; - } else { - imageType = @"framework"; - } - - if ([imagePath isEqual: report.processInfo.processPath] || [imagePath hasPrefix:appBundleContentsPath]) { - [appUUIDs addObject:@{kBITBinaryImageKeyUUID: uuid, kBITBinaryImageKeyArch: archName, kBITBinaryImageKeyType: imageType}]; + if (imageType != BITBinaryImageTypeOther) { + 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 { + BITBinaryImageType imageType = BITBinaryImageTypeOther; + + NSString *standardizedImagePath = [[imagePath stringByStandardizingPath] lowercaseString]; + imagePath = [imagePath lowercaseString]; + processPath = [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: processPath] || + // Fix issue with iOS 8 `stringByStandardizingPath` removing leading `/private` path (when not running in the debugger or simulator only) + [imagePath hasPrefix:processPath]) { + 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) + [imagePath hasPrefix:appBundleContentsPath]) { + imageType = BITBinaryImageTypeAppFramework; + } + } + + return imageType; +} + + (NSString *)bit_archNameFromImageInfo:(BITPLCrashReportBinaryImageInfo *)imageInfo { NSString *archName = @"???"; @@ -778,12 +806,9 @@ static const char *findSEL (const char *imageName, NSString *imageUUID, uint64_t /* If symbol info is available, the format used in Apple's reports is Sym + OffsetFromSym. Otherwise, * the format used is imageBaseAddress + offsetToIP */ - NSString *imagePath = [imageInfo.imageName stringByStandardizingPath]; - NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent]; - - if (frameInfo.symbolInfo != nil && - ![imagePath isEqual: report.processInfo.processPath] && - ![imagePath hasPrefix:appBundleContentsPath]) { + 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 diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index bf4a871fbb..0babad34b1 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -89,6 +89,7 @@ 1E4CD1EA19D17EB700019DD4 /* Ok@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E4CD1E919D17EB700019DD4 /* Ok@3x.png */; }; 1E4CD1EC19D17EBE00019DD4 /* Rectangle@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E4CD1EB19D17EBE00019DD4 /* Rectangle@3x.png */; }; 1E4CD1F019D17EE400019DD4 /* authorize_denied@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E4CD1EF19D17EE400019DD4 /* authorize_denied@3x.png */; }; + 1E566D071A275C4C0070F514 /* BITCrashReportTextFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E566D061A275C4C0070F514 /* BITCrashReportTextFormatterTests.m */; }; 1E5954D315B6F24A00A03429 /* BITHockeyManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E41EB466148D7BF50015DEDC /* BITHockeyManager.m */; }; 1E5954DC15B6F24A00A03429 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E400561D148D79B500EB22B9 /* Foundation.framework */; }; 1E5954DD15B6F24A00A03429 /* CrashReporter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E41EB48B148D7C4E0015DEDC /* CrashReporter.framework */; }; @@ -279,6 +280,7 @@ 1E4CD1E919D17EB700019DD4 /* Ok@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Ok@3x.png"; sourceTree = ""; }; 1E4CD1EB19D17EBE00019DD4 /* Rectangle@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Rectangle@3x.png"; sourceTree = ""; }; 1E4CD1EF19D17EE400019DD4 /* authorize_denied@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "authorize_denied@3x.png"; sourceTree = ""; }; + 1E566D061A275C4C0070F514 /* BITCrashReportTextFormatterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITCrashReportTextFormatterTests.m; sourceTree = ""; }; 1E5954F215B6F24A00A03429 /* libHockeySDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libHockeySDK.a; sourceTree = BUILT_PRODUCTS_DIR; }; 1E59550A15B6F45800A03429 /* HockeySDKResources.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HockeySDKResources.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 1E59556015B6F80E00A03429 /* de */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/HockeySDK.strings; sourceTree = ""; }; @@ -490,6 +492,7 @@ E40E0B0817DA19DC005E38C1 /* BITHockeyAppClientTests.m */, E4507E4217F0658F00171A0D /* BITKeychainUtilsTests.m */, 1E70A23517F31B82001BB32D /* BITHockeyHelperTests.m */, + 1E566D061A275C4C0070F514 /* BITCrashReportTextFormatterTests.m */, ); path = HockeySDKTests; sourceTree = ""; @@ -1097,6 +1100,7 @@ buildActionMask = 2147483647; files = ( 1E5A459E16F0DFC200B55C04 /* BITStoreUpdateManagerTests.m in Sources */, + 1E566D071A275C4C0070F514 /* BITCrashReportTextFormatterTests.m in Sources */, 1EFF03E517F2485500A5F13C /* BITCrashManagerTests.m in Sources */, E40E0B0917DA19DC005E38C1 /* BITHockeyAppClientTests.m in Sources */, 1E70A23617F31B82001BB32D /* BITHockeyHelperTests.m in Sources */, diff --git a/Support/HockeySDKTests/BITCrashReportTextFormatterTests.m b/Support/HockeySDKTests/BITCrashReportTextFormatterTests.m new file mode 100644 index 0000000000..170fa8fd6c --- /dev/null +++ b/Support/HockeySDKTests/BITCrashReportTextFormatterTests.m @@ -0,0 +1,196 @@ +// +// BITCrashReportTextFormatterTests.m +// HockeySDK +// +// Created by Andreas Linde on 27.11.14. +// +// + +#import +#import "BITCrashReportTextFormatter.h" + +@interface BITCrashReportTextFormatterTests : XCTestCase + +@end + +@implementation BITCrashReportTextFormatterTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wimplicit" + __gcov_flush(); +# pragma clang diagnostic pop + + [super tearDown]; +} + +- (void)testOSXImages { + NSString *processPath = nil; + NSString *appBundlePath = nil; + + appBundlePath = @"/Applications/MyTestApp.App"; + + // Test with default OS X app path + processPath = [appBundlePath stringByAppendingString:@"/Contents/MacOS/MyApp"]; + [self testOSXNonAppSpecificImagesForProcessPath:processPath]; + [self testAppBinaryWithImagePath:processPath processPath:processPath]; + + // Test with OS X LoginItems app helper path + processPath = [appBundlePath stringByAppendingString:@"/Contents/Library/LoginItems/net.hockeyapp.helper.app/Contents/MacOS/Helper"]; + [self testOSXNonAppSpecificImagesForProcessPath:processPath]; + [self testAppBinaryWithImagePath:processPath processPath:processPath]; + + // Test with OS X app in Resources folder + processPath = @"/Applications/MyTestApp.App/Contents/Resources/Helper"; + [self testOSXNonAppSpecificImagesForProcessPath:processPath]; + [self testAppBinaryWithImagePath:processPath processPath:processPath]; +} + +- (void)testiOSImages { + NSString *processPath = nil; + NSString *appBundlePath = nil; + + appBundlePath = @"/private/var/mobile/Containers/Bundle/Application/9107B4E2-CD8C-486E-A3B2-82A5B818F2A0/MyApp.app"; + + // Test with iOS App + processPath = [appBundlePath stringByAppendingString:@"/MyApp"]; + [self testiOSNonAppSpecificImagesForProcessPath:processPath]; + [self testAppBinaryWithImagePath:processPath processPath:processPath]; + [self testiOSAppFrameworkAtProcessPath:processPath appBundlePath:appBundlePath]; + + // Test with iOS App Extension + processPath = [appBundlePath stringByAppendingString:@"/Plugins/MyAppExtension.appex/MyAppExtension"]; + [self testiOSNonAppSpecificImagesForProcessPath:processPath]; + [self testAppBinaryWithImagePath:processPath processPath:processPath]; + [self testiOSAppFrameworkAtProcessPath:processPath appBundlePath:appBundlePath]; +} + + +#pragma mark - Test Helper + +- (void)testAppBinaryWithImagePath:(NSString *)imagePath processPath:(NSString *)processPath { + BITBinaryImageType imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:imagePath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeAppBinary), @"Test app %@ with process %@", imagePath, processPath); +} + + +#pragma mark - OS X Test Helper + +- (void)testOSXAppFrameworkAtProcessPath:(NSString *)processPath appBundlePath:(NSString *)appBundlePath { + NSString *frameworkPath = [appBundlePath stringByAppendingString:@"/Contents/Frameworks/MyFrameworkLib.framework/Versions/A/MyFrameworkLib"]; + BITBinaryImageType imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:frameworkPath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeAppFramework), @"Test framework %@ with process %@", frameworkPath, processPath); + + frameworkPath = [appBundlePath stringByAppendingString:@"/Contents/Frameworks/libSwiftMyLib.framework/Versions/A/libSwiftMyLib"]; + imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:frameworkPath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeAppFramework), @"Test framework %@ with process %@", frameworkPath, processPath); + + NSMutableArray *swiftFrameworkPaths = [NSMutableArray new]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Contents/Frameworks/libswiftCore.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Contents/Frameworks/libswiftDarwin.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Contents/Frameworks/libswiftDispatch.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Contents/Frameworks/libswiftFoundation.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Contents/Frameworks/libswiftObjectiveC.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Contents/Frameworks/libswiftSecurity.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Contents/Frameworks/libswiftCoreGraphics.dylib"]]; + + for (NSString *imagePath in swiftFrameworkPaths) { + BITBinaryImageType imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:imagePath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeOther), @"Test swift image %@ with process %@", imagePath, processPath); + } +} + +- (void)testOSXNonAppSpecificImagesForProcessPath:(NSString *)processPath { + // system test paths + NSMutableArray *nonAppSpecificImagePaths = [NSMutableArray new]; + + // OS X frameworks + [nonAppSpecificImagePaths addObject:@"cl_kernels"]; + [nonAppSpecificImagePaths addObject:@""]; + [nonAppSpecificImagePaths addObject:@"???"]; + [nonAppSpecificImagePaths addObject:@"/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork"]; + [nonAppSpecificImagePaths addObject:@"/usr/lib/system/libsystem_platform.dylib"]; + [nonAppSpecificImagePaths addObject:@"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib"]; + [nonAppSpecificImagePaths addObject:@"/System/Library/PrivateFrameworks/Sharing.framework/Versions/A/Sharing"]; + [nonAppSpecificImagePaths addObject:@"/usr/lib/libbsm.0.dylib"]; + + for (NSString *imagePath in nonAppSpecificImagePaths) { + BITBinaryImageType imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:imagePath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeOther), @"Test other image %@ with process %@", imagePath, processPath); + } +} + + +#pragma mark - iOS Test Helper + +- (void)testiOSAppFrameworkAtProcessPath:(NSString *)processPath appBundlePath:(NSString *)appBundlePath { + NSString *frameworkPath = [appBundlePath stringByAppendingString:@"/Frameworks/MyFrameworkLib.framework/MyFrameworkLib"]; + BITBinaryImageType imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:frameworkPath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeAppFramework), @"Test framework %@ with process %@", frameworkPath, processPath); + + frameworkPath = [appBundlePath stringByAppendingString:@"/Frameworks/libSwiftMyLib.framework/libSwiftMyLib"]; + imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:frameworkPath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeAppFramework), @"Test framework %@ with process %@", frameworkPath, processPath); + + NSMutableArray *swiftFrameworkPaths = [NSMutableArray new]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Frameworks/libswiftCore.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Frameworks/libswiftDarwin.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Frameworks/libswiftDispatch.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Frameworks/libswiftFoundation.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Frameworks/libswiftObjectiveC.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Frameworks/libswiftSecurity.dylib"]]; + [swiftFrameworkPaths addObject:[appBundlePath stringByAppendingString:@"/Frameworks/libswiftCoreGraphics.dylib"]]; + + for (NSString *imagePath in swiftFrameworkPaths) { + BITBinaryImageType imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:imagePath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeOther), @"Test swift image %@ with process %@", imagePath, processPath); + } +} + +- (void)testiOSNonAppSpecificImagesForProcessPath:(NSString *)processPath { + // system test paths + NSMutableArray *nonAppSpecificImagePaths = [NSMutableArray new]; + + // iOS frameworks + [nonAppSpecificImagePaths addObject:@"/System/Library/AccessibilityBundles/AccessibilitySettingsLoader.bundle/AccessibilitySettingsLoader"]; + [nonAppSpecificImagePaths addObject:@"/System/Library/Frameworks/AVFoundation.framework/AVFoundation"]; + [nonAppSpecificImagePaths addObject:@"/System/Library/Frameworks/AVFoundation.framework/libAVFAudio.dylib"]; + [nonAppSpecificImagePaths addObject:@"/System/Library/PrivateFrameworks/AOSNotification.framework/AOSNotification"]; + [nonAppSpecificImagePaths addObject:@"/System/Library/PrivateFrameworks/Accessibility.framework/Frameworks/AccessibilityUI.framework/AccessibilityUI"]; + [nonAppSpecificImagePaths addObject:@"/System/Library/PrivateFrameworks/Accessibility.framework/Frameworks/AccessibilityUIUtilities.framework/AccessibilityUIUtilities"]; + [nonAppSpecificImagePaths addObject:@"/usr/lib/libAXSafeCategoryBundle.dylib"]; + [nonAppSpecificImagePaths addObject:@"/usr/lib/libAXSpeechManager.dylib"]; + [nonAppSpecificImagePaths addObject:@"/usr/lib/libAccessibility.dylib"]; + [nonAppSpecificImagePaths addObject:@"/usr/lib/system/libcache.dylib"]; + [nonAppSpecificImagePaths addObject:@"/usr/lib/system/libcommonCrypto.dylib"]; + [nonAppSpecificImagePaths addObject:@"/usr/lib/system/libcompiler_rt.dylib"]; + + // iOS Jailbreak libraries + [nonAppSpecificImagePaths addObject:@"/Library/MobileSubstrate/MobileSubstrate.dylib"]; + [nonAppSpecificImagePaths addObject:@"/Library/MobileSubstrate/DynamicLibraries/WeeLoader.dylib"]; + [nonAppSpecificImagePaths addObject:@"/Library/Frameworks/CydiaSubstrate.framework/Libraries/SubstrateLoader.dylib"]; + [nonAppSpecificImagePaths addObject:@"/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate"]; + [nonAppSpecificImagePaths addObject:@"/Library/MobileSubstrate/DynamicLibraries/WinterBoard.dylib"]; + + for (NSString *imagePath in nonAppSpecificImagePaths) { + BITBinaryImageType imageType = [BITCrashReportTextFormatter bit_imageTypeForImagePath:imagePath + processPath:processPath]; + XCTAssert((imageType == BITBinaryImageTypeOther), @"Test other image %@ with process %@", imagePath, processPath); + } +} + + +@end