mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user