diff --git a/Classes/BITCrashManager.h b/Classes/BITCrashManager.h
index 64f695d60f..37ac20711c 100644
--- a/Classes/BITCrashManager.h
+++ b/Classes/BITCrashManager.h
@@ -166,6 +166,38 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) {
@property (nonatomic, assign, getter=isMachExceptionHandlerEnabled) BOOL enableMachExceptionHandler;
+/**
+ * Enables heuristics to detect the app getting killed while being in the foreground
+ *
+ * This allows it to get a crash report if the app got killed while being in the foreground
+ * because of now of the following reasons:
+ * - The main thread was blocked for too long
+ * - The app took too long to start up
+ * - The app tried to allocate too much memory. If iOS did send a memory warning before killing the app because of this reason, `didReceiveMemoryWarningInLastSession` returns `YES`.
+ * - Permitted background duration if main thread is running in an endless loop
+ * - App failed to resume in time if main thread is running in an endless loop
+ *
+ * The following kills can _NOT_ be detected:
+ * - Terminating the app takes too long
+ * - Permitted background duration too long for all other cases
+ * - App failed to resume in time for all other cases
+ * - possibly more cases
+ *
+ * Crash reports triggered by this mechanisms do _NOT_ contain any stack traces since the time of the kill
+ * cannot be intercepted and hence no stack trace of the time of the kill event can't be gathered.
+ *
+ * Default: _NO_
+ *
+ * @warning This is a heuristic and it _MAY_ report false positives!
+ *
+ * @see wasKilledInLastSession
+ * @see didReceiveMemoryWarningInLastSession
+ * @see [Apple Technical Note TN2151](https://developer.apple.com/library/ios/technotes/tn2151/_index.html)
+ * @see [Apple Technical Q&A QA1693](https://developer.apple.com/library/ios/qa/qa1693/_index.html)
+ */
+@property (nonatomic, assign, getter = isAppKillDetectionWhileInForegroundEnabled) BOOL enableAppKillDetectionWhileInForeground;
+
+
/**
* Set the callbacks that will be executed prior to program termination after a crash has occurred
*
@@ -224,6 +256,48 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) {
@property (nonatomic, readonly) BOOL didCrashInLastSession;
+/**
+ Indicates if the app was killed while being in foreground from the iOS
+
+ If `enableDectionAppKillWhileInForeground` is enabled, use this on startup to check if the
+ app starts the first time after it was killed by iOS in the previous session.
+
+ This can happen if it consumed too much memory or the watchdog killed the app because it
+ took too long to startup or blocks the main thread for too long, or other reasons. See Apple
+ documentation: https://developer.apple.com/library/ios/qa/qa1693/_index.html
+
+ See `enableDectionAppKillWhileInForeground` for more details about which kind of kills can be detected.
+
+ @warning This property only has a correct value, once `[BITHockeyManager startManager]` was
+ invoked! In addition, it is automatically disabled while a debugger session is active!
+
+ @see enableAppKillDetectionWhileInForeground
+ @see didReceiveMemoryWarningInLastSession
+ */
+@property (nonatomic, readonly) BOOL wasKilledInLastSession;
+
+
+/**
+ Indicates if the app did receive a low memory warning in the last session
+
+ It may happen that low memory warning where send but couldn't be logged, since iOS
+ killed the app before updating the flag in the filesystem did complete.
+
+ This property may be true in case of low memory kills, but it doesn't have to be! Apps
+ can also be killed without the app ever receiving a low memory warning.
+
+ Also the app could have received a low memory warning, but the reason for being killed was
+ actually different.
+
+ @warning This property only has a correct value, once `[BITHockeyManager startManager]` was
+ invoked!
+
+ @see enableAppKillDetectionWhileInForeground
+ @see wasKilledInLastSession
+ */
+@property (nonatomic, readonly) BOOL didReceiveMemoryWarningInLastSession;
+
+
/**
Provides the time between startup and crash in seconds
diff --git a/Classes/BITCrashManager.m b/Classes/BITCrashManager.m
index fa1c8bcac9..76f2a4c811 100644
--- a/Classes/BITCrashManager.m
+++ b/Classes/BITCrashManager.m
@@ -55,6 +55,19 @@
NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
+NSString *const kBITAppWentIntoBackgroundSafely = @"BITAppWentIntoBackgroundSafely";
+NSString *const kBITAppDidReceiveLowMemoryNotification = @"BITAppDidReceiveLowMemoryNotification";
+NSString *const kBITAppVersion = @"BITAppVersion";
+NSString *const kBITAppOSVersion = @"BITAppOSVersion";
+NSString *const kBITAppUUIDs = @"BITAppUUIDs";
+
+NSString *const kBITFakeCrashUUID = @"BITFakeCrashUUID";
+NSString *const kBITFakeCrashAppVersion = @"BITFakeCrashAppVersion";
+NSString *const kBITFakeCrashAppBundleIdentifier = @"BITFakeCrashAppBundleIdentifier";
+NSString *const kBITFakeCrashOSVersion = @"BITFakeCrashOSVersion";
+NSString *const kBITFakeCrashDeviceModel = @"BITFakeCrashDeviceModel";
+NSString *const kBITFakeCrashAppBinaryUUID = @"BITFakeCrashAppBinaryUUID";
+NSString *const kBITFakeCrashReport = @"BITFakeCrashAppString";
@interface BITCrashManager ()
@@ -83,7 +96,13 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
BOOL _sendingInProgress;
BOOL _isSetup;
+ BOOL _didLogLowMemoryWarning;
+
id _appDidBecomeActiveObserver;
+ id _appWillTerminateObserver;
+ id _appDidEnterBackgroundObserver;
+ id _appWillEnterForegroundObserver;
+ id _appDidReceiveLowMemoryWarningObserver;
id _networkDidBecomeReachableObserver;
}
@@ -105,6 +124,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
_didCrashInLastSession = NO;
_timeintervalCrashInLastSessionOccured = -1;
+ _didLogLowMemoryWarning = NO;
_approvedCrashReports = [[NSMutableDictionary alloc] init];
@@ -287,20 +307,116 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
[strongSelf triggerDelayedProcessing];
}];
}
+
+ if (nil == _appWillTerminateObserver) {
+ _appWillTerminateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
+ object:nil
+ queue:NSOperationQueue.mainQueue
+ usingBlock:^(NSNotification *note) {
+ typeof(self) strongSelf = weakSelf;
+ [strongSelf leavingAppSafely];
+ }];
+ }
+
+ if (nil == _appDidEnterBackgroundObserver) {
+ _appDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification
+ object:nil
+ queue:NSOperationQueue.mainQueue
+ usingBlock:^(NSNotification *note) {
+ typeof(self) strongSelf = weakSelf;
+ [strongSelf leavingAppSafely];
+ }];
+ }
+
+ if (nil == _appWillEnterForegroundObserver) {
+ _appWillEnterForegroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
+ object:nil
+ queue:NSOperationQueue.mainQueue
+ usingBlock:^(NSNotification *note) {
+ typeof(self) strongSelf = weakSelf;
+ [strongSelf appEnteredForeground];
+ }];
+ }
+
+ if (nil == _appDidReceiveLowMemoryWarningObserver) {
+ _appDidReceiveLowMemoryWarningObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
+ object:nil
+ queue:NSOperationQueue.mainQueue
+ usingBlock:^(NSNotification *note) {
+ // we only need to log this once
+ if (!_didLogLowMemoryWarning) {
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppDidReceiveLowMemoryNotification];
+ _didLogLowMemoryWarning = YES;
+ }
+ }];
+ }
}
- (void) unregisterObservers {
- if(_appDidBecomeActiveObserver) {
- [[NSNotificationCenter defaultCenter] removeObserver:_appDidBecomeActiveObserver];
- _appDidBecomeActiveObserver = nil;
- }
+ [self unregisterObserver:_appDidBecomeActiveObserver];
+ [self unregisterObserver:_appWillTerminateObserver];
+ [self unregisterObserver:_appDidEnterBackgroundObserver];
+ [self unregisterObserver:_appWillEnterForegroundObserver];
+ [self unregisterObserver:_appDidReceiveLowMemoryWarningObserver];
- if(_networkDidBecomeReachableObserver) {
- [[NSNotificationCenter defaultCenter] removeObserver:_networkDidBecomeReachableObserver];
- _networkDidBecomeReachableObserver = nil;
+ [self unregisterObserver:_networkDidBecomeReachableObserver];
+}
+
+- (void) unregisterObserver:(id)observer {
+ if (observer) {
+ [[NSNotificationCenter defaultCenter] removeObserver:observer];
+ observer = nil;
}
}
+- (void)leavingAppSafely {
+ if (self.isAppKillDetectionWhileInForegroundEnabled)
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppWentIntoBackgroundSafely];
+}
+
+- (void)appEnteredForeground {
+ // we disable kill detection while the debugger is running, since we'd get only false positives if the app is terminated by the user using the debugger
+ if (self.isDebuggerAttached) {
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppWentIntoBackgroundSafely];
+ } else if (self.isAppKillDetectionWhileInForegroundEnabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:kBITAppWentIntoBackgroundSafely];
+
+ static dispatch_once_t predAppData;
+
+ dispatch_once(&predAppData, ^{
+ id bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
+ if (bundleVersion && [bundleVersion isKindOfClass:[NSString class]])
+ [[NSUserDefaults standardUserDefaults] setObject:bundleVersion forKey:kBITAppVersion];
+ [[NSUserDefaults standardUserDefaults] setObject:[[UIDevice currentDevice] systemVersion] forKey:kBITAppOSVersion];
+
+ NSString *uuidString =[NSString stringWithFormat:@"%@",
+ [self deviceArchitecture],
+ [self executableUUID]
+ ];
+
+ [[NSUserDefaults standardUserDefaults] setObject:uuidString forKey:kBITAppUUIDs];
+ });
+ }
+}
+
+- (NSString *)deviceArchitecture {
+ NSString *archName = @"???";
+
+ size_t size;
+ cpu_type_t type;
+ cpu_subtype_t subtype;
+ size = sizeof(type);
+ if (sysctlbyname("hw.cputype", &type, &size, NULL, 0))
+ return archName;
+
+ size = sizeof(subtype);
+ if (sysctlbyname("hw.cpusubtype", &subtype, &size, NULL, 0))
+ return archName;
+
+ archName = [BITCrashReportTextFormatter bit_archNameFromCPUType:type subType:subtype] ?: @"???";
+
+ return archName;
+}
/**
* Get the userID from the delegate which should be stored with the crash report
@@ -437,6 +553,35 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
}
}
+/**
+ * Write a meta file for a new crash report
+ *
+ * @param filename the crash reports temp filename
+ */
+- (void)storeMetaDataForCrashReportFilename:(NSString *)filename {
+ NSError *error = NULL;
+ NSMutableDictionary *metaDict = [NSMutableDictionary dictionaryWithCapacity:4];
+ NSString *applicationLog = @"";
+ NSString *errorString = nil;
+
+ [self addStringValueToKeychain:[self userNameForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", filename, kBITCrashMetaUserName]];
+ [self addStringValueToKeychain:[self userEmailForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", filename, kBITCrashMetaUserEmail]];
+ [self addStringValueToKeychain:[self userIDForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", filename, kBITCrashMetaUserID]];
+
+ if (self.delegate != nil && [self.delegate respondsToSelector:@selector(applicationLogForCrashManager:)]) {
+ applicationLog = [self.delegate applicationLogForCrashManager:self] ?: @"";
+ }
+ [metaDict setObject:applicationLog forKey:kBITCrashMetaApplicationLog];
+
+ NSData *plist = [NSPropertyListSerialization dataFromPropertyList:(id)metaDict
+ format:NSPropertyListBinaryFormat_v1_0
+ errorDescription:&errorString];
+ if (plist) {
+ [plist writeToFile:[_crashesDir stringByAppendingPathComponent: [filename stringByAppendingPathExtension:@"meta"]] atomically:YES];
+ } else {
+ BITHockeyLog(@"ERROR: Writing crash meta data failed. %@", error);
+ }
+}
#pragma mark - PLCrashReporter
@@ -481,28 +626,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
[crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES];
- // write the meta file
- NSMutableDictionary *metaDict = [NSMutableDictionary dictionaryWithCapacity:4];
- NSString *applicationLog = @"";
- NSString *errorString = nil;
-
- [self addStringValueToKeychain:[self userNameForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserName]];
- [self addStringValueToKeychain:[self userEmailForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserEmail]];
- [self addStringValueToKeychain:[self userIDForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserID]];
-
- if (self.delegate != nil && [self.delegate respondsToSelector:@selector(applicationLogForCrashManager:)]) {
- applicationLog = [self.delegate applicationLogForCrashManager:self] ?: @"";
- }
- [metaDict setObject:applicationLog forKey:kBITCrashMetaApplicationLog];
-
- NSData *plist = [NSPropertyListSerialization dataFromPropertyList:(id)metaDict
- format:NSPropertyListBinaryFormat_v1_0
- errorDescription:&errorString];
- if (plist) {
- [plist writeToFile:[NSString stringWithFormat:@"%@.meta", [_crashesDir stringByAppendingPathComponent: cacheFilename]] atomically:YES];
- } else {
- BITHockeyLog(@"ERROR: Writing crash meta data failed. %@", error);
- }
+ [self storeMetaDataForCrashReportFilename:cacheFilename];
}
}
}
@@ -734,10 +858,103 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
_isSetup = YES;
});
}
+
+ if ([[NSUserDefaults standardUserDefaults] valueForKey:kBITAppDidReceiveLowMemoryNotification])
+ _didReceiveMemoryWarningInLastSession = [[NSUserDefaults standardUserDefaults] boolForKey:kBITAppDidReceiveLowMemoryNotification];
+
+ if (!_didCrashInLastSession && self.isAppKillDetectionWhileInForegroundEnabled) {
+ BOOL didAppSwitchToBackgroundSafely = YES;
+
+ if ([[NSUserDefaults standardUserDefaults] valueForKey:kBITAppWentIntoBackgroundSafely])
+ didAppSwitchToBackgroundSafely = [[NSUserDefaults standardUserDefaults] boolForKey:kBITAppWentIntoBackgroundSafely];
+
+ if (!didAppSwitchToBackgroundSafely) {
+ NSLog(@"AppHasBeenKilled!");
+
+ [self createCrashReportForAppKill];
+
+ _wasKilledInLastSession = YES;
+ _didCrashInLastSession = YES;
+ }
+ }
+ [self appEnteredForeground];
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:kBITAppDidReceiveLowMemoryNotification];
+ [[NSUserDefaults standardUserDefaults] synchronize];
[self triggerDelayedProcessing];
}
+/**
+ * Creates a fake crash report because the app was killed while being in foreground
+ */
+- (void)createCrashReportForAppKill {
+ NSString *fakeReportUUID = bit_UUID();
+
+ NSString *fakeReportAppVersion = [[NSUserDefaults standardUserDefaults] objectForKey:kBITAppVersion];
+ if (!fakeReportAppVersion)
+ return;
+
+ NSString *fakeReportOSVersion = [[NSUserDefaults standardUserDefaults] objectForKey:kBITAppOSVersion] ?: [[UIDevice currentDevice] systemVersion];
+ NSString *fakeReportAppBundleIdentifier = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
+ NSString *fakeReportDeviceModel = [self getDevicePlatform] ?: @"Unknown";
+ NSString *fakeReportAppUUIDs = [[NSUserDefaults standardUserDefaults] objectForKey:kBITAppUUIDs] ?: @"";
+
+ NSMutableString *fakeReportString = [NSMutableString string];
+
+ [fakeReportString appendFormat:@"Incident Identifier: %@\n", fakeReportUUID];
+ [fakeReportString appendFormat:@"CrashReporter Key: %@\n", bit_appAnonID() ?: @"???"];
+ [fakeReportString appendFormat:@"Hardware Model: %@\n", fakeReportDeviceModel];
+ [fakeReportString appendFormat:@"Identifier: %@\n", fakeReportAppBundleIdentifier];
+ [fakeReportString appendFormat:@"Version: %@\n", fakeReportAppVersion];
+ [fakeReportString appendString:@"Code Type: ARM\n"];
+ [fakeReportString appendString:@"\n"];
+
+ 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]];
+
+ // we use the current date, since we don't know when the kill actually happened
+ [fakeReportString appendFormat:@"Date/Time: %@\n", [rfc3339Formatter stringFromDate:[NSDate date]]];
+ [fakeReportString appendFormat:@"OS Version: %@\n", fakeReportOSVersion];
+ [fakeReportString appendString:@"Report Version: 104\n"];
+ [fakeReportString appendString:@"\n"];
+ [fakeReportString appendString:@"Exception Type: SIGKILL\n"];
+ [fakeReportString appendString:@"Exception Codes: 00000020 at 0x8badf00d\n"];
+ [fakeReportString appendString:@"\n"];
+ [fakeReportString appendString:@"Application Specific Information:\n"];
+ [fakeReportString appendString:@"The application was killed by the iOS watchdog."];
+ if (self.didReceiveMemoryWarningInLastSession) {
+ [fakeReportString appendString:@" The app received at least one Low Memory Warning."];
+ }
+ [fakeReportString appendString:@"\n"];
+
+ NSString *fakeReportFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]];
+
+ NSString *errorString = nil;
+
+ NSMutableDictionary *rootObj = [NSMutableDictionary dictionaryWithCapacity:2];
+ [rootObj setObject:fakeReportUUID forKey:kBITFakeCrashUUID];
+ [rootObj setObject:fakeReportAppVersion forKey:kBITFakeCrashAppVersion];
+ [rootObj setObject:fakeReportAppBundleIdentifier forKey:kBITFakeCrashAppBundleIdentifier];
+ [rootObj setObject:fakeReportOSVersion forKey:kBITFakeCrashOSVersion];
+ [rootObj setObject:fakeReportDeviceModel forKey:kBITFakeCrashDeviceModel];
+ [rootObj setObject:fakeReportAppUUIDs forKey:kBITFakeCrashAppBinaryUUID];
+ [rootObj setObject:fakeReportString forKey:kBITFakeCrashReport];
+
+ NSData *plist = [NSPropertyListSerialization dataFromPropertyList:(id)rootObj
+ format:NSPropertyListBinaryFormat_v1_0
+ errorDescription:&errorString];
+ if (plist) {
+ if ([plist writeToFile:[_crashesDir stringByAppendingPathComponent:[fakeReportFilename stringByAppendingPathExtension:@"fake"]] atomically:YES]) {
+ [self storeMetaDataForCrashReportFilename:fakeReportFilename];
+ }
+ } else {
+ BITHockeyLog(@"ERROR: Writing fake crash report. %@", errorString);
+ }
+}
+
/**
* Send all approved crash reports
*
@@ -755,13 +972,49 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
NSData *crashData = [NSData dataWithContentsOfFile:filename];
if ([crashData length] > 0) {
- BITPLCrashReport *report = [[BITPLCrashReport alloc] initWithData:crashData error:&error];
+ BITPLCrashReport *report = nil;
+ NSString *crashUUID = @"";
+ NSString *installString = nil;
+ NSString *crashLogString = nil;
+ NSString *appBundleIdentifier = nil;
+ NSString *appBundleVersion = nil;
+ NSString *osVersion = nil;
+ NSString *deviceModel = nil;
+ NSString *appBinaryUUIDs = nil;
+ NSString *metaFilename = nil;
+
+ NSString *errorString = nil;
+ NSPropertyListFormat format;
+
+ if ([[cacheFilename pathExtension] isEqualToString:@"fake"]) {
+ NSDictionary *fakeReportDict = (NSDictionary *)[NSPropertyListSerialization
+ propertyListFromData:crashData
+ mutabilityOption:NSPropertyListMutableContainersAndLeaves
+ format:&format
+ errorDescription:&errorString];
+
+ crashLogString = [fakeReportDict objectForKey:kBITFakeCrashReport];
+ crashUUID = [fakeReportDict objectForKey:kBITFakeCrashUUID];
+ appBundleIdentifier = [fakeReportDict objectForKey:kBITFakeCrashAppBundleIdentifier];
+ appBundleVersion = [fakeReportDict objectForKey:kBITFakeCrashAppVersion];
+ appBinaryUUIDs = [fakeReportDict objectForKey:kBITFakeCrashAppBinaryUUID];
+ deviceModel = [fakeReportDict objectForKey:kBITFakeCrashDeviceModel];
+ osVersion = [fakeReportDict objectForKey:kBITFakeCrashOSVersion];
+
+ metaFilename = [cacheFilename stringByReplacingOccurrencesOfString:@".fake" withString:@".meta"];
+ if ([appBundleVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) {
+ _crashIdenticalCurrentVersion = YES;
+ }
+
+ } else {
+ report = [[BITPLCrashReport alloc] initWithData:crashData error:&error];
+ }
- if (report == nil) {
+ if (report == nil && crashLogString == nil) {
BITHockeyLog(@"WARNING: Could not parse crash report");
// we cannot do anything with this report, so delete it
[_fileManager removeItemAtPath:filename error:&error];
- [_fileManager removeItemAtPath:[NSString stringWithFormat:@"%@.meta", filename] error:&error];
+ [_fileManager removeItemAtPath:[NSString stringWithFormat:@"%@.meta", metaFilename] error:&error];
[self removeKeyFromKeychain:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserName]];
[self removeKeyFromKeychain:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserEmail]];
@@ -769,16 +1022,23 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
continue;
}
- NSString *crashUUID = @"";
- if (report.uuidRef != NULL) {
- crashUUID = (NSString *) CFBridgingRelease(CFUUIDCreateString(NULL, report.uuidRef));
+ if (report) {
+ if (report.uuidRef != NULL) {
+ crashUUID = (NSString *) CFBridgingRelease(CFUUIDCreateString(NULL, report.uuidRef));
+ }
+ metaFilename = [filename stringByAppendingPathExtension:@"meta"];
+ crashLogString = [BITCrashReportTextFormatter stringValueForCrashReport:report crashReporterKey:installString];
+ appBundleIdentifier = report.applicationInfo.applicationIdentifier;
+ appBundleVersion = report.applicationInfo.applicationVersion;
+ osVersion = report.systemInfo.operatingSystemVersion;
+ deviceModel = [self getDevicePlatform];
+ appBinaryUUIDs = [self extractAppUUIDs:report];
+ if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) {
+ _crashIdenticalCurrentVersion = YES;
+ }
}
- NSString *installString = bit_appAnonID() ?: @"";
- NSString *crashLogString = [BITCrashReportTextFormatter stringValueForCrashReport:report crashReporterKey:installString];
- if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) {
- _crashIdenticalCurrentVersion = YES;
- }
+ installString = bit_appAnonID() ?: @"";
if (crashes == nil) {
crashes = [NSMutableString string];
@@ -790,10 +1050,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
NSString *applicationLog = @"";
NSString *description = @"";
- NSString *errorString = nil;
- NSPropertyListFormat format;
-
- NSData *plist = [NSData dataWithContentsOfFile:[filename stringByAppendingString:@".meta"]];
+ NSData *plist = [NSData dataWithContentsOfFile:[_crashesDir stringByAppendingPathComponent:metaFilename]];
if (plist) {
NSDictionary *metaDict = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:plist
@@ -815,12 +1072,12 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
[crashes appendFormat:@"%s%@%@%@%@%@%@%@%@%@%@%@",
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String],
- [self extractAppUUIDs:report],
- report.applicationInfo.applicationIdentifier,
- report.systemInfo.operatingSystemVersion,
- [self getDevicePlatform],
+ appBinaryUUIDs,
+ appBundleIdentifier,
+ osVersion,
+ deviceModel,
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
- report.applicationInfo.applicationVersion,
+ appBundleVersion,
crashUUID,
[crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]>" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)],
userid,
diff --git a/Classes/BITCrashReportTextFormatter.h b/Classes/BITCrashReportTextFormatter.h
index 13ab19f964..5ddef4a22d 100644
--- a/Classes/BITCrashReportTextFormatter.h
+++ b/Classes/BITCrashReportTextFormatter.h
@@ -49,5 +49,6 @@
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey;
+ (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report;
++ (NSString *)bit_archNameFromCPUType:(uint64_t)cpuType subType:(uint64_t)subType;
@end
diff --git a/Classes/BITCrashReportTextFormatter.m b/Classes/BITCrashReportTextFormatter.m
index 6094affc23..c8b060d34c 100644
--- a/Classes/BITCrashReportTextFormatter.m
+++ b/Classes/BITCrashReportTextFormatter.m
@@ -464,66 +464,73 @@ static NSInteger bit_binaryImageSort(id binary1, id binary2, void *context) {
{
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;
-
- 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 (imageInfo.codeType.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;
- }
+ 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.