IOS —— Crash分析

article/2025/11/7 23:01:18

Crash分析

    • 1. Crash
    • 2. 奔溃处理
      • 2.1 选择器方法未定义
      • 2.2 容器越界
    • 2.3 NSSetUncaughtExceptionHandler

1. Crash

应用崩溃是影响 APP 体验的重要一环, 而崩溃定位也常常让开发者头疼。Crash的出现就是做了一些违背代码规则的操作,常见crash类型有:

  • 容器越界
  • 使用未初始化的变量
  • 用户授权问题
  • 选择器方法未定义
  • 子线程刷新ui
  • KVO
  • 数据类型不匹配
  • 内存溢出
  • 野指针
  • 死循环
    那么该如何高效处理Crash呢?

2. 奔溃处理

2.1 选择器方法未定义

下面这个代码明显的会崩溃,因为没有logicEdu方法。

#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.
}
- (IBAction)btnAction:(UIButton *)sender {[sender performSelector:@selector(logicEdu:)];
}
@end

那么添加下面这个方法之后还是崩溃。

- (void) logicEdu:(UIButton *)sender {}

首先看一下堆栈。这里exception_throw是异常抛出的意思,而抛出异常的地方是+[NSObject(NSObject) instanceMethodSignatureForSelector:] 也就是慢速消息转发里面。消息发送中会通过对象的isa找到类去类的方法列表里面寻找selector,如果没有找到则去类的父类里面查找,直到找到NSObject还没有找到的话就会进入消息转发流程,
在这里插入图片描述
消息转发流程里面有三个补救的机会:

  • 动态方法决议(添加方法)
  • 快速消息转发(转发给另一个对象来处理)
  • 慢速消息转发(动态签名签名一个sel)
    如果这三个都没有处理,那么就会崩溃。
    这里来创建一个NSObject分类来进行快速消息转发 -forwardingTargetForSelector;
@implementation NSObject (method)
- (id)forwardingTargetForSelector:(SEL)aSelector {id result = [self forwardingTargetForSelector:aSelector];return result;
}
@end

这里会报警告,因为分类方法名字和主类的方法名字一样,这时候就需要用到method-Swizzing进行imp交换。这里运行后发现还是崩溃的。method-Swizzing一般是在load里面实现,load在main函数之前就会被调用,并且是主动调用,但是在load写函数会影响启动速度,应该尽量不要在load中写耗时的操作。并且类和分类是懒加载的,但是如果实现了load,那么类的加载就会提前到main函数之前。影响启动速度的原因是会在底层调用更多的方法比如attchCategory等。 有的把方法交换写到initialize里面,这也是可以的,因为initialize是第一次调用方法的时候被调用的。但是不建议在initialize里面进行方法交换,因为可能不会调用方法。

+ (void)load {Method originalMethod = class_getInstanceMethod(self, @selector(forwardingTargetForSelector:));Method swizzleMethod = class_getInstanceMethod(self, @selector(LSForwardingTargetForSelector:));method_exchangeImplementations(originalMethod, swizzleMethod);
}

然后创建一个ForwardingTarget类,来重写消息转发方法。

id newDynamicMethod(id self,SEL _cmd){return [NSNull null];
}+ (BOOL)resolveInstanceMethod:(SEL)sel{class_addMethod(self.class, sel, (IMP) newDynamicMethod, "@@:");[super resolveInstanceMethod:sel];return YES;
}- (id)forwardingTargetForSelector:(SEL)aSelector {id result = [super forwardingTargetForSelector:aSelector];return result;
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{id result = [super methodSignatureForSelector:aSelector];return result;
}- (void)forwardInvocation:(NSInvocation *)anInvocation{[super forwardInvocation:anInvocation];
}

在分类中添加静态变量target然后在load里面初始化。

static ForwardingTarget *target = nil;

这样运行后点击button就不会崩溃了,这里还可以在newDynamicMethod里面打印crash的sel。

id newDynamicMethod(id self,SEL _cmd){NSLog(@"%@",NSStringFromSelector(_cmd));return [NSNull null];
}

在这里插入图片描述

2.2 容器越界

下面这个代码运行后点击button会造成容器越界的崩溃。

@interface ViewController ()
{NSArray *dataArr;
}@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.dataArr = @[@"第1个",@"第2个",@"第3个",@"第4个"];
}
- (IBAction)btnAction:(UIButton *)sender {NSLog(@"%@" ,dataArr[4]);
}@end

那么这里如何处理容器越界的崩溃呢?
这里需要写一个NSArray的分类,然后还是使用method-Swizzing来进行处理。看到堆栈里面是objectAtIndexedSubscript报错的,所以需要重写objectAtIndexedSubscript方法。
在这里插入图片描述
下面这个方法其实是不正确的,这里还需要调用class_addMethod来查看是否有objectAtIndexedSubscript方法了。

@implementation NSArray (LSArray)
+ (void)load {Method originalMethod = class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:));Method swizzleMethod = class_getInstanceMethod(self, @selector(lsobjectAtIndexedSubscript:));method_exchangeImplementations(originalMethod, swizzleMethod);}- (id)lsobjectAtIndexedSubscript:(NSUInteger)idx{if(idx < self.count) {return [self lsobjectAtIndexedSubscript:idx];}NSLog(@"越界了%lu >= %lu",idx,self.count);return nil;
}
@end

这里用didAddMethod判断是不是有了objectAtIndexedSubscript方法,如果有了则替换方法,如果没有就交换imp。

+ (void)load {Method originalMethod = class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:));Method swizzleMethod = class_getInstanceMethod(self, @selector(lsobjectAtIndexedSubscript:));bool didAddMethod = class_addMethod(self, @selector(objectAtIndexedSubscript:), method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));if (didAddMethod) {class_replaceMethod(self, @selector(lsobjectAtIndexedSubscript:),method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));} else {method_exchangeImplementations(originalMethod, swizzleMethod);}
}

这里其实还有问题,需要加一个单例来确保只交换一次。

+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Method originalMethod = class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:));Method swizzleMethod = class_getInstanceMethod(self, @selector(lsobjectAtIndexedSubscript:));bool didAddMethod = class_addMethod(self, @selector(objectAtIndexedSubscript:), method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));if (didAddMethod) {class_replaceMethod(self, @selector(lsobjectAtIndexedSubscript:),method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));} else {method_exchangeImplementations(originalMethod, swizzleMethod);}});}

但是这里运行后发现还是崩溃,这是为什么呢?方法的本质是消息,消息包含接受者和消息的主体(SEl和参数),这里的self是NSArray,但是奔溃信息里面的是__NSArrayI,所以需要把originalMethod里面的class换一下

Method originalMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedSubscript:));

这样就没有数据越界的问题了。当然,NSArray其他的类簇也需要这样的处理,比如可变数组__NSArrayM, 不可变空数组__NSArray0等。

2.3 NSSetUncaughtExceptionHandler

crash种类很多,那么有没有办法可以捕获所有的crash呢?苹果提供了一个API. NSSetUncaughtExceptionHandler。

写一个ExceptionHandler类,并添加类方法installUncaughtExceptionHandler,然后在这个方法里面调用NSSetUncaughtExceptionHandler。

+ (void)installUncaughtExceptionHandler {NSSetUncaughtExceptionHandler(&lsExceptionHandlers);
}

NSSetUncaughtExceptionHandler里面的参数是一个c函数。


void lsExceptionHandlers(NSException *exception) {NSLog(@"%s",__func__);int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);if (exceptionCount > LGUncaughtExceptionMaximum){return;}NSArray *callStack = [ExceptionHandler lsBackTrace];NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];[userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];[userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];[userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];[userInfo setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolKey];[userInfo setObject:@"LSException" forKey:LGUncaughtExceptionHandlerFileKey];[[[ExceptionHandler alloc] init]   performSelectorOnMainThread:@selector(ls_handleException:)  withObject:[NSException exceptionWithName:exception.name reason:exception.reason userInfo:userInfo] waitUntilDone:YES];
}

然后在AppDelegate里面的didFinishLaunchingWithOptions调用这个类方法,确保足够早的收集到crash信息。

[ExceptionHandler  installUncaughtExceptionHandler];

那么现在如果程序奔溃的话,那么就会到lsExceptionHandlers里面,这里面可以根据获得的exception来进行崩溃的记录,然后上传到服务器。
这里运行后点击Button会有数组越界的崩溃。看到保存的崩溃的地址,然后去到这个地址。
在这里插入图片描述
打开日志文件,看到崩溃信息保存了下来,这样就可以把这个文件传到服务器了。
在这里插入图片描述
接下来去看崩溃的堆栈。看到这里有个函数_objc_terminate。
在这里插入图片描述
在源码中搜索_objc_terminate,发现实现如下:
在这里插入图片描述

之前的文章中iOS 底层探索篇 —— dyld加载流程(上)写到_objc_init也调用了exception_init方法,那么exception_init对异常进行了什么处理呢?

看到这里调用了set方法,这里就是说,这个terminate会一直在跑,如果发生了异常,就会调用_objc_terminate回调函数。所以_objc_init就做了异常的回调处理。

void exception_init(void)
{old_terminate = std::set_terminate(&_objc_terminate);
}

回到_objc_terminate,看到上面写的如果是objc object 就会调用uncaught_handler,那么就会调用uncaught_handler方法,那么就寻找uncaught_handler,方法进行了赋值。

static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;

往下看还有对uncaught_handler赋值的地方,但是搜索确没有地方调用它,所以是上层的API。

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{objc_uncaught_exception_handler result = uncaught_handler;uncaught_handler = fn;return result;
}

看到NSSetUncaughtExceptionHandler和objc_setUncaughtExceptionHandler名字非常相似,这里就知道NSSetUncaughtExceptionHandler是对objc_setUncaughtExceptionHandler的封装。之前的调用 (*uncaught_handler)((id)e), 也就是调用外面传进来的回调函数,所以lsExceptionHandlers方法里面也有一个NSException。


http://chatgpt.dhexx.cn/article/ohK5bCPM.shtml

相关文章

iOS crash 问题分析汇总

在这里插入图片描述 iOS crash 问题分析 iOS crash 常用分析工具IOS 崩溃日志 iOS crash 原因分析1.调用悬浮指针2.数组越界访问3.调用了未实现的方法4.调用的库函数版本高于本机5.返回空cell6.类释放时未remove通知&#xff0c;之后收到通知7.类释放时delegate未置空&#xf…

安装计算机一级出现appcrash,appcrash错误如何解决

APPCRASH是Vista和Win7中特有的故障&#xff0c;导出APPCRASH问题的原因有很多&#xff0c;恶意插件&#xff0c;文件丢失或者文件错误等等。下面&#xff0c;我就给大家介绍一下APPCRASH问题的解决方法&#xff0c;有需要就一起来了解一下吧 如何解决APPCRASH错误问题呢&#…

06、app anr分析/crash/崩溃

1、anr ANR 全称&#xff1a;application not responding&#xff0c;即app无响应 分析思路&#xff1a;使用排除法去分析问题&#xff0c;对用的内容需要开发提供对应的日志监控&#xff0c;重现这个过程&#xff0c;去获取这个日志 2、crash 一般crash原因有以下几种 1&a…

Android程序Crash时的异常上报

转载请注明来源&#xff1a;http://blog.csdn.net/singwhatiwanna/article/details/17289479 前言 大家都知道&#xff0c;android应用不可避免的会发生crash&#xff0c;无论你的程序写的多完美&#xff0c;总是无法完全避免crash的发生&#xff0c;可能是由于android系统底层…

iOS Crash报告分析

文章目录 相关概念什么是 dSYM 文件dSYM 文件有什么作用 分析crash报告解析工具 umcrashtool 相关概念 什么是 dSYM 文件 Xcode编译项目后&#xff0c;我们会看到一个同名的 dSYM 文件&#xff0c;dSYM 是保存 16 进制函数地址映射信息的中转文件&#xff0c;我们调试的 symb…

Android 平台的Crash崩溃捕获-全

上层-java/kotlin&#xff1a; Android应用层java/kotlin的crash捕获相对容易。直接实现Thread.UncaughtExceptionHandler即可处理收集。Thread.UncaughtExceptionHandler&#xff1a;当某一线程因未捕获的异常而即将终止时&#xff0c;Java 虚拟机将使用 Thread.getUncaughtE…

服务器appcrash的问题怎么修复,电脑appcrash的问题怎么修复?

电脑是很复杂的程序代码设计的&#xff0c;因而有时会遇到一些奇怪的问题&#xff0c;而APPCRASH错误也是其中一种&#xff0c;如运行程序出现APPCRASH错误&#xff0c;我们在日志中查看事件名称为APPCRASH&#xff0c;这时很多朋友不知道怎么解决&#xff0c;下面小编和大家一…

iOS中“事件”的前因后果

iOS的事件是一个由触发行为到响应的过程。本文旨在表达事件如何处理响应&#xff0c;如何传递事件的。 1. 前言 国内智能手机要从2000年开始说起&#xff0c;手机的进化是飞速的&#xff0c;单从操作来看&#xff0c;最开始是数字实体键盘&#xff0c;后来出现了全字母的实体键…

AppCrash explorer问题(解决方法)

AppCrash 顾名思义 程序崩溃了 先上问题&#xff1a; 资源管理器不断地显示停止工作&#xff08;关闭后10s又出来报错&#xff09; 我也在网上找了很多资料&#xff0c;还去Google搜了一下&#xff0c;但我的错误和dll这个无关&#xff0c;修改注册表等方法都无法解决此问题&a…

win7 APPCRASH问题解决!

真是废了老劲了。。什么清理插件&#xff0c;各种运行msconfig/启动都试了 问题&#xff1a;**.exe已停止工作 问题事件名称: APPCRASH 应用程序名: compute_image_mean.exe 应用程序版本: 0.0.0.0 应用程序时间戳: 579c50f5 故障模块名称: MSVCR120.dll 故障模块版本: 12.0.21…

mysql安装appcrash_appcrash事件怎么解决-appcrash问题解决方法 - 系统家园

在电脑上运行程序的时候常常会遇到很多的问题然后提示appcrash错误&#xff0c;为此下面就给你们带来了appcrash问题解决方法&#xff0c;一直遇到这个问题的小伙伴就快来解决一下吧。 appcrash事件怎么解决&#xff1a; 方法一&#xff1a; 1、出现appcrash错误会提示给你故障…

程序崩溃APPcrash的问题

【问题】 小编在对接扫码枪枪的时候遇到了一个问题&#xff1a;拔出扫码枪的时候&#xff0c;有状态返回但是出现了这个错误&#xff1a; 【解决办法】 小编我真是心累的“狠”&#xff0c;但经过me的不辞辛苦还是找到了问题的根本&#xff1a; 一、厂家提供的环境 二、小编…

问题事件名称: APPCRASH(解决方法)

问题事件名称: APPCRASH(解决方法)(转&#xff09; 下面分享一下解决win7或者是Vista的一个刺手的问题 APPCRASH&#xff08;app是程序的意思&#xff0c;crash是坠机的意思。就是程序崩溃了/程序撞车……&#xff09; 我们使用软件的时候有时候会出现这种情况 举个例子 Dung…

appcrash事件怎么解决?三种方法教你

我们在电脑上运行程序的时候常常会遇到很多的问题然后提示appcrash错误&#xff0c;为此下面小编就给你们带来了appcrash问题解决方法&#xff0c;有遇到这个问题的小伙伴就快来解决一下吧。 appcrash事件怎么解决&#xff1f; 方法一 1、出现appcrash错误会提示给你故障模块&a…

LoadRunner9.1下载与破解

LoadRunner9.1下载与破解 上一篇 / 下一篇 2009-03-03 18:42:39 / 个人分类&#xff1a;测试工具 查看( 918 ) / 评论( 2 ) / 评分( 0 / 0 ) 一、Loadrunner下载地址&#xff1a; http://h30302.www3.hp.com/prdownloads/T7177-15005.iso?ordernumber380397475&itemid1&…

Loadrunner11破解详解 .

使用说明&#xff1a;要以管理员的身份运行 1、正常安装完LR11后。然后双击deletelicense.exe 2、然后解压替换其中的2个DLL文件拷贝到"C:\Program Files\HP\LoadRunner\bin\"下替换原有文件 3、进入LR&#xff0c;输入以上的序列号即可 global-100: AEACFSJI-YASEKJ…

LoadRunner11的安装与破解

现在很多人都在用LoadRunner11&#xff0c;下面我就来说说自己下载和安装LR11的过程。 一、安装 1.先来下载LR11.建议大家都在正规网站下载&#xff0c;推荐一个网站http://bbs.51testing.com/thread-423695-1-1.html&#xff0c;直接将此网站复制到迅雷下载http://www.genil…

Loadrunner11在win10下的安装、汉化与破解方法

Loadrunner11安装与破解方法 工具/原料 • HP Loadrunner 11.00 • 汉化包 • 破解文件 1 安装英文版 1.1 运行“setup.exe” 点击安装&#xff0c;其中会有提示缺少“Microsoft Visual C 2005 SP1运行组件”&#xff0c; 在“\lrunner\Chs\prerequisites\vc2005_sp1_redis…

loadrunner11基础使用

其实loadrunner11只要环境装好了&#xff0c;没那么多报错 装好Loadrunner后要用管理员权限打开&#xff0c;不然可能会报错 win10可以装lr12和12.5&#xff0c;但是无法破解&#xff0c;最大并发50人&#xff0c;还只有7天试用期&#xff0c;7天过了要重新装 loadrunner11中主…

loadrunner 11下载及破解

原文地址为&#xff1a; loadrunner 11下载及破解 1.下载参照文章&#xff1a; http://www.51testing.com/?uid-4827-action-viewspace-itemid-225451 2.破解参照文章&#xff1a; http://naotang.com/index.php?optioncom_content&viewarticle&id66:loadrunner11&a…