崩溃捕获
// 1.捕获一些异常导致的崩溃
NSSetUncaughtExceptionHandler(&HandleException);
// 2.捕获非异常情况,通过signal传递出来的崩溃
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
- (void)handleException:(NSException *)exception
{
NSString *message = [NSString stringWithFormat:@"崩溃原因如下:\n%@\n%@",
[exception reason],
[[exception userInfo] objectForKey:kCaughtExceptionStackInfoKey]];
NSLog(@"%@",message);
UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:@"程序崩溃了" message:@"如果你能让程序起死回生,那你的决定是?" preferredStyle:UIAlertControllerStyleAlert];
// __weak typeof(self) weakSelf = self;
UIAlertAction *action = [UIAlertAction actionWithTitle:@"崩就蹦吧" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
//weakSelf->ignore = YES;
self->ignore = YES;
}];
UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"起死回生" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alertVc addAction:action];
[alertVc addAction:action1];
UIViewController *rootVC = [CrashHandler currentFrontViewController];
[rootVC presentViewController:alertVc animated:YES completion:nil];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!ignore) {
for (NSString *mode in (__bridge NSArray *)allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
NSSetUncaughtExceptionHandler(NULL);
signal(SIGABRT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
if ([[exception name] isEqual:kSignalExceptionName]) {
kill(getpid(), [[[exception userInfo] objectForKey:kSignalKey] intValue]);
} else {
[exception raise];
}
}
void HandleException(NSException *exception)
{
// 获取异常的堆栈信息
NSArray *callStack = [exception callStackSymbols]; /// NSThread.callStackSymbols
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setObject:callStack forKey:kCaughtExceptionStackInfoKey];
CrashHandler *crashObject = [CrashHandler sharedInstance];
NSException *customException = [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo];
[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
void SignalHandler(int signal)
{
// 这种情况的崩溃信息,就另某他法来捕获吧
NSArray *callStack = [CrashHandler backtrace];
NSLog(@"信号捕获崩溃,堆栈信息:%@",callStack);
CrashHandler *crashObject = [CrashHandler sharedInstance];
NSException *customException = [NSException exceptionWithName:kSignalExceptionName
reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]
userInfo:@{kSignalKey:[NSNumber numberWithInt:signal]}];
[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
获取各线程调用堆栈
1. 获取所有线程
thread_act_array_t threads; mach_msg_type_number_t thread_count = 0; const task_t this_task = mach_task_self();
kern_return_t kr = task_threads(this_task, &threads, &thread_count);
if(kr != KERN_SUCCESS) {
return @"Fail to get information of all threads";
}
2. 获取对应线程最顶层函数,以及esp、ebp指针
kern_return_t thread_get_state (
thread_act_t target_act, //目标线程,通过task_threads接口来获取
thread_state_flavor_t flavor, //线程状态类型,如 [ARM/x86]_THREAD_STATE64
thread_state_t old_state, //线程状态信息,可获取线程调用栈寄存器信息
mach_msg_type_number_t *old_stateCnt //线程状态信息成员数目
);
bool fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT *machineContext) {
mach_msg_type_number_t state_count = BS_THREAD_STATE_COUNT;
kern_return_t kr = thread_get_state(thread, BS_THREAD_STATE,
(thread_state_t)&machineContext->__ss, &state_count);
return (kr == KERN_SUCCESS);
}
3. 根据地址定位镜像
上一步根据栈帧指针得到的是方法的地址,首先需要判断该方法属于哪个镜像文 件。
uint64_t count = _dyld_image_count();//image数量 const struct mach_header *header = _dyld_get_image_header(index);//image mach-o header const char *name = _dyld_get_image_name(index);//image name
uint64_t slide = _dyld_get_image_vmaddr_slide(index);//ALSR偏移地址
static uint32_t imageIndexContainingAddress(const uintptr_t address)
{
const uint32_t imageCount = _dyld_image_count();
const struct mach_header* header = 0;
for(uint32_t iImg = 0; iImg < imageCount; iImg++) {
/// 遍历images
header = _dyld_get_image_header(iImg); // image mach-o header
if(header != NULL)
{
// Look for a segment command with this address within its range.
uintptr_t addressWSlide = address - (uintptr_t)_dyld_get_image_vmaddr_slide(iImg); // ALSR偏移地址
uintptr_t cmdPtr = firstCmdAfterHeader(header);
if(cmdPtr == 0) { continue; }
for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) {
/// 遍历查找mach-0下的load command 类型为SEGMENT, 找到image地址在进程中的内存
const struct load_command* loadCmd = (struct load_command*)cmdPtr;
if(loadCmd->cmd == LC_SEGMENT) {
const struct segment_command* segCmd = (struct segment_command*)cmdPtr;
if(addressWSlide >= segCmd->vmaddr
&& addressWSlide < segCmd->vmaddr + segCmd->vmsize)
{
return iImg;
}
} else if(loadCmd->cmd == LC_SEGMENT_64) {
const struct segment_command_64* segCmd = (struct segment_command_64*)cmdPtr;
if(addressWSlide >= segCmd->vmaddr
&& addressWSlide < segCmd->vmaddr + segCmd->vmsize)
{
return iImg;
}
}
cmdPtr += loadCmd->cmdsize;
}
}
}
return UINT_MAX;
}
4. 根据地址、镜像文件,定位符号
//基址 = 偏移量 + _LINKEDIT段虚拟地址 - _LINKEDIT段文件偏移地址 uintptr_t linkeditBase = (uintptr_t)slide + linkeditSegment->vmaddr - linkeditSegment->fileoff; //符号表的地址 = 基址 + 符号表偏移量 const nlist_t *symbolTable = (nlist_t *)(linkeditBase + symtabCmd- >symoff); //字符串表的地址 = 基址 + 字符串表偏移量 char *stringTab = (char *)(linkeditBase + symtabCmd->stroff); //符号数量 uint32_t symNum = symtabCmd->nsyms;
const uintptr_t addressWithSlide = address - imageVMAddrSlide;//address为调用栈内存地址
for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++) {
// If n_value is 0, the symbol refers to an external object.
if(symbolTable[iSym].n_value != 0) {
uintptr_t symbolBase = symbolTable[iSym].n_value;//获取符号的 内存地址(函数指针)
uintptr_t currentDistance = addressWithSlide - symbolBase;
if((addressWithSlide >= symbolBase) &&
(currentDistance <= bestDistance))
{
bestMatch = symbolTable + iSym;//最佳匹配符号地址
bestDistance = currentDistance;//调用栈内存地址与当前符号内存地址距离
}
}
}
if(bestMatch != NULL) {
info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddrSlide);
if(bestMatch->n_desc == 16) {
// This image has been stripped. The name is meaningless, and
// almost certainly resolves to "_mh_execute_header"
info->dli_sname = NULL;
}
else
{
info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);
if(*info->dli_sname == '_') {
info->dli_sname++; }
}
}
crash和解决方案:
- unrecognized selector sent to instance 方法找不到
- 数组越界,插入空值
[NSDictionary initWithObjects:forKeys:]使用此方法初始化字典时,objects和keys的数量不一致时- NSMutableDictionary,
setObject:forKey:或者removeObjectForKey:时,key为nil setValue:forUndefinedKey:,使用KVC对对象进行存取值时传入错误的key或者对不可变字典进行赋值- NSUserDefaults 存储时key为nil
- 对字符串操作时,传递的下标超出范围,判断是否存在前缀,后缀子串时,子串为空
- 使用C字符串初始化字符串时,传入null
- 对可变集合或字符串使用copy修饰并进行修改操作
- 在空间未添加到父元素上之前,就使用autoLayout进行布局
- KVO在对象销毁时,没有移除KVO或者多次移除KVO
- 野指针访问
- 死锁
1-9都可以利用Runtime进行拦截,然后进行一些逻辑处理,防止crash
NSInvalidArgumentException
向容器加入nil,引起的崩溃。
SIGSEGV
访问没有被开辟的内存或者已经被释放的内存。
NSRangeException
数组越界,字符串截取越界
SIGABRT
异常 这是一个让程序终止的标识,会在断言、app内部、操作系统用终止方法抛出。
SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。
SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。
SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
SIGCHLD
子进程结束时, 父进程会收到这个信号。
crash一般产生自 iOS 的微内核 Mach,然后在 BSD 层转换成 UNIX SIGABRT 信号,以标准 POSIX 信号的形式提供给用户。
NSSetUncaughtExceptionHandler 函数用于设置异常处理的回调函数,在程序终止前的最后一刻会调用我们设置的回调函数(Handler),进行崩溃日志的记录
大部分第三方 SDK 也是通过这种方式来收集异常的,当我们通过 NSSetUncaughtExceptionHandler 设置异常处理函数时,会覆盖其它 SDK 设置的回调函数,导致它们无法上报统计,反之,也可能出现我们设置的回调函数被其他人覆盖。
我们可以在调用 NSSetUncaughtExceptionHandler 注册异常处理函数之前,先通过 NSGetUncaughtExceptionHandler 拿到已有的异常处理函数并保存下来。然后在我们自己的处理函数执行之后,再调用之前保存的处理函数就可以了。
浙公网安备 33010602011771号