Add enableDectionAppKillWhileInForeground option to BITCrashManager

This option implements basic heuristics to detect if the app got killed by the iOS watchdog while running in foreground, which can only happen if:
- The app tried to allocate too much memory
- Main thread doesn't respond for some time

It is not possible to detect all cases where such kills can occur.
This commit is contained in:
Andreas Linde
2014-02-10 01:06:59 +01:00
parent a0153e4205
commit 0cfa00924c
4 changed files with 443 additions and 104 deletions

View File

@@ -166,6 +166,38 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) {
@property (nonatomic, assign, getter=isMachExceptionHandlerEnabled) BOOL enableMachExceptionHandler; @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 * 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; @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 Provides the time between startup and crash in seconds

View File

@@ -55,6 +55,19 @@
NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; 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 () @interface BITCrashManager ()
@@ -83,7 +96,13 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
BOOL _sendingInProgress; BOOL _sendingInProgress;
BOOL _isSetup; BOOL _isSetup;
BOOL _didLogLowMemoryWarning;
id _appDidBecomeActiveObserver; id _appDidBecomeActiveObserver;
id _appWillTerminateObserver;
id _appDidEnterBackgroundObserver;
id _appWillEnterForegroundObserver;
id _appDidReceiveLowMemoryWarningObserver;
id _networkDidBecomeReachableObserver; id _networkDidBecomeReachableObserver;
} }
@@ -105,6 +124,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
_didCrashInLastSession = NO; _didCrashInLastSession = NO;
_timeintervalCrashInLastSessionOccured = -1; _timeintervalCrashInLastSessionOccured = -1;
_didLogLowMemoryWarning = NO;
_approvedCrashReports = [[NSMutableDictionary alloc] init]; _approvedCrashReports = [[NSMutableDictionary alloc] init];
@@ -287,20 +307,116 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
[strongSelf triggerDelayedProcessing]; [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 { - (void) unregisterObservers {
if(_appDidBecomeActiveObserver) { [self unregisterObserver:_appDidBecomeActiveObserver];
[[NSNotificationCenter defaultCenter] removeObserver:_appDidBecomeActiveObserver]; [self unregisterObserver:_appWillTerminateObserver];
_appDidBecomeActiveObserver = nil; [self unregisterObserver:_appDidEnterBackgroundObserver];
} [self unregisterObserver:_appWillEnterForegroundObserver];
[self unregisterObserver:_appDidReceiveLowMemoryWarningObserver];
if(_networkDidBecomeReachableObserver) { [self unregisterObserver:_networkDidBecomeReachableObserver];
[[NSNotificationCenter defaultCenter] removeObserver:_networkDidBecomeReachableObserver]; }
_networkDidBecomeReachableObserver = nil;
- (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:@"<uuid type=\"app\" arch=\"%@\">%@</uuid>",
[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 * 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 #pragma mark - PLCrashReporter
@@ -481,28 +626,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
[crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES]; [crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES];
// write the meta file [self storeMetaDataForCrashReportFilename:cacheFilename];
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);
}
} }
} }
} }
@@ -734,10 +858,103 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
_isSetup = YES; _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]; [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 * Send all approved crash reports
* *
@@ -755,13 +972,49 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
NSData *crashData = [NSData dataWithContentsOfFile:filename]; NSData *crashData = [NSData dataWithContentsOfFile:filename];
if ([crashData length] > 0) { 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"); BITHockeyLog(@"WARNING: Could not parse crash report");
// we cannot do anything with this report, so delete it // we cannot do anything with this report, so delete it
[_fileManager removeItemAtPath:filename error:&error]; [_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, kBITCrashMetaUserName]];
[self removeKeyFromKeychain:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserEmail]]; [self removeKeyFromKeychain:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserEmail]];
@@ -769,16 +1022,23 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
continue; continue;
} }
NSString *crashUUID = @""; if (report) {
if (report.uuidRef != NULL) { if (report.uuidRef != NULL) {
crashUUID = (NSString *) CFBridgingRelease(CFUUIDCreateString(NULL, report.uuidRef)); 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) { installString = bit_appAnonID() ?: @"";
_crashIdenticalCurrentVersion = YES;
}
if (crashes == nil) { if (crashes == nil) {
crashes = [NSMutableString string]; crashes = [NSMutableString string];
@@ -790,10 +1050,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
NSString *applicationLog = @""; NSString *applicationLog = @"";
NSString *description = @""; NSString *description = @"";
NSString *errorString = nil; NSData *plist = [NSData dataWithContentsOfFile:[_crashesDir stringByAppendingPathComponent:metaFilename]];
NSPropertyListFormat format;
NSData *plist = [NSData dataWithContentsOfFile:[filename stringByAppendingString:@".meta"]];
if (plist) { if (plist) {
NSDictionary *metaDict = (NSDictionary *)[NSPropertyListSerialization NSDictionary *metaDict = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:plist propertyListFromData:plist
@@ -815,12 +1072,12 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
[crashes appendFormat:@"<crash><applicationname>%s</applicationname><uuids>%@</uuids><bundleidentifier>%@</bundleidentifier><systemversion>%@</systemversion><platform>%@</platform><senderversion>%@</senderversion><version>%@</version><uuid>%@</uuid><log><![CDATA[%@]]></log><userid>%@</userid><username>%@</username><contact>%@</contact><installstring>%@</installstring><description><![CDATA[%@]]></description></crash>", [crashes appendFormat:@"<crash><applicationname>%s</applicationname><uuids>%@</uuids><bundleidentifier>%@</bundleidentifier><systemversion>%@</systemversion><platform>%@</platform><senderversion>%@</senderversion><version>%@</version><uuid>%@</uuid><log><![CDATA[%@]]></log><userid>%@</userid><username>%@</username><contact>%@</contact><installstring>%@</installstring><description><![CDATA[%@]]></description></crash>",
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String], [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String],
[self extractAppUUIDs:report], appBinaryUUIDs,
report.applicationInfo.applicationIdentifier, appBundleIdentifier,
report.systemInfo.operatingSystemVersion, osVersion,
[self getDevicePlatform], deviceModel,
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
report.applicationInfo.applicationVersion, appBundleVersion,
crashUUID, crashUUID,
[crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)], [crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)],
userid, userid,

View File

@@ -49,5 +49,6 @@
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey; + (NSString *)stringValueForCrashReport:(PLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey;
+ (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report; + (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report;
+ (NSString *)bit_archNameFromCPUType:(uint64_t)cpuType subType:(uint64_t)subType;
@end @end

View File

@@ -464,66 +464,73 @@ static NSInteger bit_binaryImageSort(id binary1, id binary2, void *context) {
{ {
NSString *archName = @"???"; NSString *archName = @"???";
if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) { if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) {
switch (imageInfo.codeType.type) { archName = [BITCrashReportTextFormatter bit_archNameFromCPUType:imageInfo.codeType.type subType:imageInfo.codeType.subtype];
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;
}
} }
return archName; 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. * Format a stack frame for display in a thread backtrace.