224 lines
8.6 KiB
Objective-C
224 lines
8.6 KiB
Objective-C
#import "ThinkingExceptionHandler.h"
|
|
#include <libkern/OSAtomic.h>
|
|
#include <stdatomic.h>
|
|
#import "TDLogging.h"
|
|
#import "TDAutoTrackManager.h"
|
|
|
|
#if __has_include(<ThinkingDataCore/TDCorePresetDisableConfig.h>)
|
|
#import <ThinkingDataCore/TDCorePresetDisableConfig.h>
|
|
#else
|
|
#import "TDCorePresetDisableConfig.h"
|
|
#endif
|
|
|
|
static NSString * const TD_CRASH_REASON = @"#app_crashed_reason";
|
|
static NSUInteger const TD_PROPERTY_CRASH_LENGTH_LIMIT = 8191*2;
|
|
|
|
static NSString * const TDUncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
|
|
static NSString * const TDUncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
|
|
static int TDSignals[] = {SIGILL, SIGABRT, SIGBUS, SIGSEGV, SIGFPE, SIGPIPE, SIGTRAP};
|
|
static volatile atomic_int_fast32_t TDExceptionCount = 0;
|
|
static const atomic_int_fast32_t TDExceptionMaximum = 9;
|
|
|
|
@interface ThinkingExceptionHandler ()
|
|
@property (nonatomic) NSUncaughtExceptionHandler *td_lastExceptionHandler;
|
|
@property (nonatomic, unsafe_unretained) struct sigaction *td_signalHandlers;
|
|
|
|
@end
|
|
|
|
@implementation ThinkingExceptionHandler
|
|
|
|
+ (void)start {
|
|
[self sharedHandler];
|
|
}
|
|
|
|
+ (instancetype)sharedHandler {
|
|
static ThinkingExceptionHandler *gSharedHandler = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
gSharedHandler = [[ThinkingExceptionHandler alloc] init];
|
|
});
|
|
return gSharedHandler;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (self) {
|
|
_td_signalHandlers = calloc(NSIG, sizeof(struct sigaction));
|
|
[self setupHandlers];
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
|
// start APMStuck
|
|
Class cls = NSClassFromString(@"TAAPMStuckMonitor");
|
|
if (cls && [cls respondsToSelector:@selector(shareInstance)]) {
|
|
id ins = [cls performSelector:@selector(shareInstance)];
|
|
if (ins && [ins respondsToSelector:@selector(beginMonitor)]) {
|
|
[ins performSelector:@selector(beginMonitor)];
|
|
}
|
|
}
|
|
#pragma clang diagnostic push
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)setupHandlers {
|
|
_td_lastExceptionHandler = NSGetUncaughtExceptionHandler();
|
|
NSSetUncaughtExceptionHandler(&TDHandleException);
|
|
|
|
struct sigaction action;
|
|
sigemptyset(&action.sa_mask);
|
|
action.sa_flags = SA_SIGINFO;
|
|
action.sa_sigaction = &TDSignalHandler;
|
|
for (int i = 0; i < sizeof(TDSignals) / sizeof(int); i++) {
|
|
struct sigaction prev_action;
|
|
int err = sigaction(TDSignals[i], &action, &prev_action);
|
|
if (err == 0) {
|
|
memcpy(_td_signalHandlers + TDSignals[i], &prev_action, sizeof(prev_action));
|
|
} else {
|
|
TDLogError(@"Error Signal: %d", TDSignals[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void TDHandleException(NSException *exception) {
|
|
ThinkingExceptionHandler *handler = [ThinkingExceptionHandler sharedHandler];
|
|
|
|
atomic_int_fast32_t exceptionCount = atomic_fetch_add_explicit(&TDExceptionCount, 1, memory_order_relaxed);
|
|
if (exceptionCount <= TDExceptionMaximum) {
|
|
[handler td_handleUncaughtException:exception];
|
|
}
|
|
if (handler.td_lastExceptionHandler) {
|
|
handler.td_lastExceptionHandler(exception);
|
|
}
|
|
}
|
|
|
|
static void TDSignalHandler(int signalNumber, struct __siginfo *info, void *context) {
|
|
ThinkingExceptionHandler *handler = [ThinkingExceptionHandler sharedHandler];
|
|
NSMutableDictionary *crashInfo;
|
|
NSString *reason;
|
|
NSException *exception;
|
|
|
|
atomic_int_fast32_t exceptionCount = atomic_fetch_add_explicit(&TDExceptionCount, 1, memory_order_relaxed);
|
|
if (exceptionCount <= TDExceptionMaximum) {
|
|
[crashInfo setObject:[NSNumber numberWithInt:signalNumber] forKey:TDUncaughtExceptionHandlerSignalKey];
|
|
reason = [NSString stringWithFormat:@"Signal %d was raised.", signalNumber];
|
|
exception = [NSException exceptionWithName:TDUncaughtExceptionHandlerSignalExceptionName reason:reason userInfo:crashInfo];
|
|
[handler td_handleUncaughtException:exception];
|
|
}
|
|
|
|
struct sigaction prev_action = handler.td_signalHandlers[signalNumber];
|
|
if (prev_action.sa_handler == SIG_DFL) {
|
|
signal(signalNumber, SIG_DFL);
|
|
raise(signalNumber);
|
|
return;
|
|
}
|
|
if (prev_action.sa_flags & SA_SIGINFO) {
|
|
if (prev_action.sa_sigaction) {
|
|
prev_action.sa_sigaction(signalNumber, info, context);
|
|
}
|
|
} else if (prev_action.sa_handler) {
|
|
prev_action.sa_handler(signalNumber);
|
|
}
|
|
}
|
|
|
|
- (void)td_handleUncaughtException:(NSException *)exception {
|
|
NSDictionary *crashInfo = [ThinkingExceptionHandler crashInfoWithException:exception];
|
|
TDAutoTrackEvent *crashEvent = [[TDAutoTrackEvent alloc] initWithName:TD_APP_CRASH_EVENT];
|
|
[[TDAutoTrackManager sharedManager] trackWithEvent:crashEvent withProperties:crashInfo];
|
|
|
|
TDAutoTrackEvent *appEndEvent = [[TDAutoTrackEvent alloc] initWithName:TD_APP_END_EVENT];
|
|
[[TDAutoTrackManager sharedManager] trackWithEvent:appEndEvent withProperties:nil];
|
|
|
|
dispatch_sync([ThinkingAnalyticsSDK sharedTrackQueue], ^{});
|
|
dispatch_sync([ThinkingAnalyticsSDK sharedNetworkQueue], ^{});
|
|
|
|
NSSetUncaughtExceptionHandler(NULL);
|
|
for (int i = 0; i < sizeof(TDSignals) / sizeof(int); i++) {
|
|
signal(TDSignals[i], SIG_DFL);
|
|
}
|
|
}
|
|
|
|
+ (void)trackCrashWithMessage:(NSString *)message {
|
|
NSDictionary *crashInfo = [ThinkingExceptionHandler crashInfoWithMessage:message];
|
|
TDAutoTrackEvent *crashEvent = [[TDAutoTrackEvent alloc] initWithName:TD_APP_CRASH_EVENT];
|
|
[[TDAutoTrackManager sharedManager] trackWithEvent:crashEvent withProperties:crashInfo];
|
|
}
|
|
|
|
+ (NSMutableDictionary *)crashInfoWithMessage:(NSString *)message {
|
|
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
|
|
|
if ([TDCorePresetDisableConfig disableAppCrashedReason]) {
|
|
return properties;
|
|
}
|
|
|
|
NSString *crashStr = message;
|
|
@try {
|
|
crashStr = [crashStr stringByReplacingOccurrencesOfString:@"\n" withString:@"<br>"];
|
|
|
|
NSUInteger strLength = [((NSString *)crashStr) lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
NSUInteger strMaxLength = TD_PROPERTY_CRASH_LENGTH_LIMIT;
|
|
if (strLength > strMaxLength) {
|
|
crashStr = [NSMutableString stringWithString:[ThinkingExceptionHandler limitString:crashStr withLength:strMaxLength - 1]];
|
|
}
|
|
|
|
[properties setValue:crashStr forKey:TD_CRASH_REASON];
|
|
} @catch(NSException *exception) {
|
|
TDLogError(@"%@ error: %@", self, exception);
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
+ (NSMutableDictionary *)crashInfoWithException:(NSException *)exception {
|
|
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
|
|
|
|
|
if ([TDCorePresetDisableConfig disableAppCrashedReason]) {
|
|
return properties;
|
|
}
|
|
|
|
NSString *crashStr;
|
|
@try {
|
|
if ([exception callStackSymbols]) {
|
|
crashStr = [NSString stringWithFormat:@"Exception Reason:%@\nException Stack:%@", [exception reason], [exception callStackSymbols]];
|
|
} else {
|
|
NSString *exceptionStack = [[NSThread callStackSymbols] componentsJoinedByString:@"\n"];
|
|
crashStr = [NSString stringWithFormat:@"%@ %@", [exception reason], exceptionStack];
|
|
}
|
|
crashStr = [crashStr stringByReplacingOccurrencesOfString:@"\n" withString:@"<br>"];
|
|
|
|
NSUInteger strLength = [((NSString *)crashStr) lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
NSUInteger strMaxLength = TD_PROPERTY_CRASH_LENGTH_LIMIT;
|
|
if (strLength > strMaxLength) {
|
|
crashStr = [NSMutableString stringWithString:[ThinkingExceptionHandler limitString:crashStr withLength:strMaxLength - 1]];
|
|
}
|
|
|
|
[properties setValue:crashStr forKey:TD_CRASH_REASON];
|
|
} @catch(NSException *exception) {
|
|
TDLogError(@"%@ error: %@", self, exception);
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
+ (NSString *)limitString:(NSString *)originalString withLength:(NSInteger)length {
|
|
NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingUTF8);
|
|
NSData *originalData = [originalString dataUsingEncoding:encoding];
|
|
NSData *subData = [originalData subdataWithRange:NSMakeRange(0, length)];
|
|
NSString *limitString = [[NSString alloc] initWithData:subData encoding:encoding];
|
|
|
|
NSInteger index = 1;
|
|
while (index <= 3 && !limitString) {
|
|
if (length > index) {
|
|
subData = [originalData subdataWithRange:NSMakeRange(0, length - index)];
|
|
limitString = [[NSString alloc] initWithData:subData encoding:encoding];
|
|
}
|
|
index ++;
|
|
}
|
|
|
|
if (!limitString) {
|
|
return originalString;
|
|
}
|
|
return limitString;
|
|
}
|
|
|
|
@end
|