iOS_Crash 四:的捕获和防护

文章目录

  • 1.Crash 捕获
    • 1.2.NSException
    • 1.2.C++异常
    • 1.3.Mach异常
    • 1.4.Unix 信号
  • 2.Crash 防护
    • 2.1.方法未实现
    • 2.2.KVC 导致 crash
    • 2.3.KVO 导致 crash
    • 2.4.集合类导致 crash
    • 2.5.其他需要注意场景:


1.Crash 捕获

根据 Crash 的不同来源,分为以下三类:

1.2.NSException

应用层的异常,未被捕获的异常,导致程序向自身发送了 SIGABRT 信号而崩溃,是应用程序自己可控的。对于未被捕获的异常,是可以通过 try-catchNSSetUncaughtExceptionHandler() 机制类捕获的。

常见的 Exception:

  • NSInvalidArgumentException:非法参数异常。加强对参数的检查,避免传入非法参数,特别是标记为 nonull 的参数。
  • NSRangeException:越界异常
  • NSGenericException:遍历的同时对原集合进行修改
  • NSInternalInconsistencyException:不一致异常。如 NSDictionaryNSMutableNSDictionary 使用。
  • NSFileHandleOperationException:文件处理异常。常见的是存储空间不足
  • NSMallocException:内存异常。如内存不足。
    系统定义的所有 Exception 见 NSExceptionName

捕获 NSExpection:

// 记录之前的Crash回调函数(如果有的话)
static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL;+ (void)registerUncaughtExceptionHandler {// 将别人之前注册的Crash回调取出并备份previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();// 然后再注册自己的NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}// 崩溃时的回调函数
static void UncaughtExceptionHandler(NSException * exception) {// 异常的堆栈信息NSArray *stackInfo = [exception callStackSymbols];// 出现异常的原因NSString *reason = [exception reason];// 异常名称NSString *name = [exception name];// 异常错误报告NSString *exceptionInfo = [NSString stringWithFormat:@"uncaughtException异常错误报告:\n name:%@\n reason:\n %@\n callStackSymbols:\n %@", name, reason, [stackInfo componentsJoinedByString:@"\n"]];// 保存Crash日志到沙盒cache目录[SKTool cacheCrashLog:exceptionInfo name:@"CrashLog(UncaughtException)"];// 在自己handler处理完后记得把别人的handler注册回去,形成规范的SOPif (previousUncaughtExceptionHandler) {previousUncaughtExceptionHandler(exception);}// 杀掉程序,这样可以防止同时抛出的SIGABRT被Signal异常捕获kill(getpid(), SIGKILL);
}

1.2.C++异常

系统捕获到 C++ 异常后会将其转换为 OC 异常抛出,此时的调用堆栈是在异常发生时的队长;但若转换失败则会调用 __cxa_throw 抛出异常,此时的调用队长是处理异常的堆栈,导致原始异常调用堆栈丢失。
捕获 C++ 异常:

  1. 设置异常处理函数:
g_originalTerminateHandler = std::set_terminate(CPPExceptionTerminate);

调用 set_terminate(CPPExceptionTerminate) 设置新的全局终止处理函数并保持旧的函数。

  1. 重写 __cxa_throw
void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*)) {// 获取调用堆栈并存储// 再调用原始的 __cxa_throw 函数
}
  1. 异常处理函数
    __cxa_throw 往后执行,进入 set_terminate 设置的异常梳理函数。判断如果是 OC 异常则什么也不多,让 OC 异常机制处理;否则获取异常信息。

1.3.Mach异常

内核层的异常。用户态开发者可以通过 Mach API 设置 threadtaskhot 的异常端口来捕获 Mach 异常。

  • tasks:资源所有权单位。每个任务由一个虚拟地址空间、一个端口权限名称控件、一个或多个线程组成。(类似于进程)
  • threads:任务中 CPU 执行的单位
  • ports:安全的单工通信通道,只能通过发生和接收功能进行访问。

Mach 异常相关的 API 有:

  • task_get_exception_ports:获取 task 的异常端口
  • task_set_exception_ports:设置 task 的异常端口
  • mach_port_allocate:创建调用者指定的端口权限类型
  • mach_port_insert_right:将指定的端口插入目标 task

注意:避免在 Xcode 联调时监听,会死锁。


1.4.Unix 信号

又称 BSD 信号,如果开发者没有捕获 Mach 异常,则会被 host 层的方法 ux_exception() 将异常转换为对应的 Unix 信号,并通过方法 threadsignal() 将信号投递到出错线程。可以同 signal(x, SignalHandler) 来捕获 signal

信号表:

  1. SIGHUP:挂起
  2. SIGINT:程序终止信号 interrupt,在用户键入 INTR 字符(通常是 Ctrl-C)是发出,用于通知前台进程组终止进程。
  3. SIGQUIT:程序退出信号 quit,由 QUIT 字符来控制(通常是Ctrl-),程序在收到该信号退出时会生成 core 文件。
  4. SIGILL:执行非法指令
  5. SIGTRAP:由断点指令或陷阱指令
  6. SIGABRT:程序打断信号 abort。
  7. SIGBUS:非法地址
  8. SIGFPE:致命的算术运算错误
  9. SIGKILL:立即结束程序的运行。不能被阻塞、处理和忽略。
  10. SIGUSR1:用户信号1
  11. SIGSEGV:无效内存访问
  12. SIGUSR2:用户信号2
  13. SIGPIPE:管道破裂。进程间的通信,如管道的异常读写。
  14. SIGALRM:alarm 发出的信号
  15. SIGTERM:终止信号,可被阻塞和处理。通常用来要求程序自己正常退出
  16. SIGSTKFLT:栈溢出
  17. SIGCHLD:子进程退出
  18. SIGCONT:进程继续
  19. SIGSTOP:进程停止
  20. SIGTSTP:进程停止
  21. SIGTTIN:进程停止,后台进程从终端读数据时
  22. SIGTTOU:进程停止,后台进程想终端写数据时
  23. SIGURG:I/O有紧急数据达到当前进程
  24. SIGXCPU:进程的CPU时间篇到期
  25. SIGXFSZ:文件大小超出上限
  26. SIGVTALRM:虚拟时钟超时
  27. SIGPROF:profile 时钟超时
  28. SIGWINVH:窗口大小改变
  29. SIGIO:I/O相关
  30. SIGPWR:关机
  31. SIGSYS:非法的系统调用

Tips: 在终端输入 kill -l 查看所有的 signal 信号。

捕获信号:

// 一般需要捕获的信号
static const int g_fatalSignals[] = {SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGPIPE,SIGSEGV,SIGSYS,SIGTRAP,
};
void installSignalHandler() {stack_t ss;struct sigaction sa;struct timespec req, rem;long ret;// 申请一块内存空间作为可选的信号处理函数栈使用ss.ss_flags = 0;ss.ss_size = SIGSTKSZ;ss.ss_sp = malloc(ss.ss_size);// 使用 sigaltstack 函数通知系统可选的信号处理栈帧的存在及其位置sigaltstack(&ss, NULL);// 指定 SA_ONSTACK 标志通知系统这个信号处理函数应该在可选的栈帧上面执行注册的信号处理函数memset(&sa, 0, sizeof(sa));sa.sa_handler = handleSignalException;sa.sa_flags = SA_ONSTACK;sigaction(SIGABRT, &sa, NULL);
}void XXXHandleSignalException(int signal) {// 打印堆栈NSMutableString *crashInfo = [[NSMutableString alloc] init];[crashInfo appendString:[NSString stringWithFormat:@"signal:%d\n",signal]];[crashInfo appendString:@"Stack:\n"];void* callstack[128];int i, frames = backtrace(callstack, 128);char** strs = backtrace_symbols(callstack, frames);for (i = 0; i <frames; ++i) {[crashInfo appendFormat:@"%s\n", strs[I]];}NSLog(@"%@", crashInfo);// 移除其他 Crash 监听, 防止死锁NSSetUncaughtExceptionHandler(NULL);signal(SIGHUP, SIG_DFL);signal(SIGINT, SIG_DFL);signal(SIGQUIT, SIG_DFL);signal(SIGABRT, SIG_DFL);signal(SIGILL, SIG_DFL);signal(SIGSEGV, SIG_DFL);signal(SIGFPE, SIG_DFL);signal(SIGBUS, SIG_DFL);signal(SIGPIPE, SIG_DFL);
}

2.Crash 防护

2.1.方法未实现

找不到方法的实现:unrecognized selector sent to instance,查找过程详情可见:iOS_Objective-C 消息发送(消息查找 及 消息转发)过程

解决方案:
NSObject 新增分类,实现消息转发的几个方法来规避 Crash

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if ([self respondsToSelector:aSelector]) { // 已实现不做处理return [self methodSignatureForSelector:aSelector];}return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"%@ can't responds %@", NSStringFromClass([self class]), NSStringFromSelector(anInvocation.selector));
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if ([self respondsToSelector:aSelector]) { // 已实现不做处理return [self methodSignatureForSelector:aSelector];}return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"%@ can't responds %@", NSStringFromClass([self class]), NSStringFromSelector(anInvocation.selector));
}

2.2.KVC 导致 crash

KVC 的搜索模式详情可见:iOS_KVC:Key-Value Coding-2(访问者搜索模式),当最终找不到对应的key时,会导致 crash。

常见场景:

  • 场景1:key 不存在
XXXClass * obj = [[XXXClass alloc] init];
[obj setValue:nil forKey:@"xxx"];
// reason: '[<XXXClass 0x2810bfa80> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key xxx.'id value = [obj valueForKey:@"xxx"];
// Thread 1: "[<MOPerson 0x600000c76c10> valueForUndefinedKey:]: this class is not key value coding-compliant for the key xxx."
  • 场景2:key 为 nil
XXXClass* obj = [[XXXClass alloc] init];
[obj setValue:@"value" forKey:nil];
// reason: '*** -[XXXClass setValue:forKey:]: attempt to set a value for a nil key'// 另外:value 为 nil 不会崩溃
[obj setValue:nil forKey:@"name"];

解决方案:覆写系统会抛出异常的实现:

- (id)valueForUndefinedKey:(NSString *)key {NSLog(@"Error: valueForUndefinedKey: %@", key);return nil;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {NSLog(@"Error: setValue:%@ forUndefinedKey: %@", value, key);
}

2.3.KVO 导致 crash

场景:

  • 观察者/被观察者 是局部变量
  • 未实现 observeValueForKeyPath:ofObject:changecontext:
  • 移除未注册的观察者(如:重复移除)

Tips: 重复添加观察者,不会crash,但会回调多次

解决方案:

  • addObserverremoveObserver 必须成对出现
  • 使用 Facebook 的 KVOController 实现

2.4.集合类导致 crash

常见场景:

  • 越界
NSArray *arr = [NSArray array];
id value = [arr objectAtIndex:1];
// Thread 1: "*** -[__NSArray0 objectAtIndex:]: index 1 beyond bounds for empty array"
  • 塞入 nil
NSMutableArray *arr = [NSMutableArray array];
[arr addObject:nil];
// Thread 1: "*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil"NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:nil forKey:@"xxx"];
// Thread 1: "*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: xxx)"

解决方案:

  • 使用 runtime 在这些修改方法调用前添加判空处理,详情见:Demo

2.5.其他需要注意场景:

  • performSelector: 必须先判断 respondsToSelector:
  • 调用 delegate 的方法前,必须先判断 respondsToSelector:
  • id 类型不能强转,必须先判断 isKindOfClass:
  • 访问 UIKit 时一定要 dispatch 到 main queue
  • 一个实例,不能保证线程访问安全时,记得要加读写锁
  • dispatch_group_leavedispatch_group_enter 必须成对出现
  • 检查属性的修饰方式 (assign/strong/weak/copy)
  • block 调用前必须判空
  • 遍历结合类型对象时不要同时对其进行修改
  • 耗时操作一定 dispatch 到子线程,避免触发 watchDog
  • Debug 模式开启僵尸模式,方便即时发现问题。
  • 使用 XcodeAddress Sanitizer 检测地址访问越界

参考:
iOS Crash/崩溃/异常 捕获
Linux 信号列表
浅谈 iOS 中的 Crash 捕获与防护
iOS中常见Crash总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/121122.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NReco.LambdaParser使用案例

使用案例集合&#xff1a; private async void RuleEngine_Click(object sender, EventArgs e){#region 获取变量string expression this.Rule.Text.Trim();string pattern "\$(.*?)\$";MatchCollection matches Regex.Matches(expression, pattern);foreach (Ma…

day38(VueJS)概念 开发模式 框架的特点 启动步骤 指令 以及 小案例

概念 概念&#xff1a;Vue 是一套使用 Javascript 构建用户界面的渐进式框架。 Vue 框架涉及的内容有&#xff1a;Vue.js 开发概述、环境搭建、 Vue 指令、组件化应用构建、组件通信、组件嵌套、自定义指令、 自定义过滤器、组件属性、组件的路由、路由跳转。 注意&#xff1a…

unity 基于UGUI的无限动态滚动列表

基于UGUI的动态滚动列表&#xff0c;主要支持以下功能&#xff1a; 继承自UGUI的SrollRect&#xff0c;支持ScrollRect的所有功能&#xff1b; 使用对象池来管理列表元素&#xff0c;以实现列表元素的复用&#xff1b; 支持一行多个元素或一列多个元素&#xff1b; 可使用不…

ubuntu 22.04 jammy 手动安装 python 3.6

1 准备编译器 sudo apt-get install -y make build-essential libssl-dev zlib1g-dev \ libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev \ libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev \ libgdbm-dev libnss3-dev libedit-dev libc…

vue3 项目搭建教程整理

这里写目录标题 一、node二、npm三、搭建项目四、安装插件 一、node 查node版本&#xff1a;node –v 管理nodejs的版本 二、npm 查npm版本&#xff1a; npm –v1、安装指定版本&#xff08;6.14是具体的版本号&#xff09; npm install npm6.14 -g2、安装最新版本 npm ins…

【MyBatis Plus】深入探索 MyBatis Plus 的条件构造器,自定义 SQL语句,Service 接口的实现

文章目录 前言一、条件构造器1.1 什么是条件构造器1.2 QueryWrapper1.3 UpdateWrapper1.4 LambdaWrapper 二、自定义 SQL 语句2.1 自定义 SQL 的基本用法2.2 自定义 SQL 实现多表查询 三、Service 接口3.1 对 Service 接口的认识3.2 实现 Service 接口3.3 实现增删改查功能3.4 …

el-form那些事

vue3element-plus el-form那些事 输入框后拼接文字 输入框后拼接文字 <el-form-item :label"t(location.locationLength)" prop"locationLength"><el-input v-model"form.locationLength" :placeholder"t(location.inputLocation…

Windoes定时任务、设置定时重启系统

步骤一&#xff1a; 打开计算机管理 通过&#xff1a;control(控制面板&#xff09;或者compmgmt.msc(计算机管理&#xff09;打开程序 步骤二&#xff1a;打开——>系统工具 步骤三&#xff1a; 选择——>任务计划程序 步骤四&#xff1a; 可选择创建新文件命名&…

正点原子嵌入式linux驱动开发——Linux PWM驱动

PWM是很常用到功能&#xff0c;可以通过PWM来控制电机速度&#xff0c;也可以使用PWM来控制LCD的背光亮度。本章就来学习一下如何在Linux下进行PWM驱动开发。 PWM驱动解析 不在介绍PWM是什么了&#xff0c;直接进入使用。 给LCD的背光引脚输入一个PWM信号&#xff0c;这样就…

记一次企业微信的(CorpID)和密钥(Secret)泄漏的利用案例

文章目录 一、介绍二、利用过程1、获取AccessToken2、获取企业微信接口IP段3、获取企业微信回调IP段4、通过部门ID,查看返回的ID5、通过部门ID,查看用户列表6、通过便利ID,发现用户信息泄露,可以进行提交报告7、通过添加接口,添加企业账号8、登陆企业账号进行测试三、参考…

【随机过程】布朗运动

这里写目录标题 Brownian motion Brownian motion The brownian motion 1D and brownian motion 2D functions, written with the cumsum command and without for loops, are used to generate a one-dimensional and two-dimensional Brownian motion, respectively. 使用cu…

主动调度是如何发生的

计算机主要处理计算、网络、存储三个方面。计算主要是 CPU 和内存的合作&#xff1b;网络和存储则多是和外部设备的合作&#xff1b;在操作外部设备的时候&#xff0c;往往需要让出 CPU&#xff0c;就像上面两段代码一样&#xff0c;选择调用 schedule() 函数。 上下文切换主要…

【杂记】java 大集合进行拆分

日常中需要对一个大的集合进行拆分成多个小集合&#xff0c;其主要思路为&#xff1a; 设置需要拆分多少个小集合 A大集合里面有多少条数据 B计算出每个集合里面有多个条数据 CB/A计算出看是否存在余数 DB%A采用集合(List.subList())的方法对大集合进行拆分,循环A变进行集合拆…

Kafka - 3.x 图解Broker总体工作流程

文章目录 Zk中存储的kafka的信息Kafka Broker总体工作流程1. broker启动后向zk中注册2. Controller谁先启动注册&#xff0c;谁说了算3. 由选举出来的Controller监听brokers节点的变化4. Controller决定leader选举5. Controller将节点信息上传到Zk中6. 其他Controller从zk中同步…

Fourier分析导论——第1章——Fourier分析的起源(E.M. Stein R. Shakarchi)

第 1 章 Fourier分析的起源 (The Genesis of Fourier Analysis) Regarding the researches of dAlembert and Euler could one not add that if they knew this expansion, they made but a very imperfect use of it. They were both persuaded that an arbitrary and d…

jenkins配置gitlab凭据

下载Credentials Binding插件&#xff08;默认是已经安装了&#xff09; 在凭据配置里添加凭据类型 点击保存 Username with password&#xff1a; 用户名和密码 SSH Username with private 在凭据管理里面添加gitlab账号和密码 点击全局 点击添加凭据&#xff08;版本不同…

Go RESTful API 接口开发

文章目录 什么是 RESTful APIGo 流行 Web 框架-GinGo HelloWorldGin 路由和控制器Gin 处理请求参数生成 HTTP 请求响应Gin 的学习内容实战用 Gin 框架开发 RESTful APIOAuth 2.0接口了解用 Go 开发 OAuth2.0 接口示例 编程有一个准则——Don‘t Repeat Yourself&#xff08;不要…

如何在Windows和Linux系统上监听文件夹的变动?

文章目录 如何在Windows和Linux系统上监听文件夹的变动&#xff1f;读写文件文件系统的操作缓冲和流文件改变事件 如何在Windows和Linux系统上监听文件夹的变动&#xff1f; libuv库实现了监听整个文件夹的修改。本文详细介绍libuv库文件读写和监听的的实现方法。libuv库开发了…

Unity的碰撞检测(六)

温馨提示&#xff1a;本文基于前一篇“Unity的碰撞检测(五)”继续探讨两个游戏对象具备刚体的BodyType均为Dynamic&#xff0c;但是Collision Detection属性不同的碰撞检测&#xff0c;阅读本文则默认已阅读前文。 &#xff08;一&#xff09;测试说明 在基于两个游戏对象都具…

ClickHouse UDF 官方示例Example报错解决方案

目录 一、环境版本二、官方示例三、解决方案1.Python脚本运行缺少权限2. 缺少Python3运行环境 四、参考借鉴 一、环境版本 环境版本docker clickhouse22.3.10.22 docker pull clickhouse/clickhouse-server:22.3.10.22二、官方示例 官网文档 test_function使用 XML 配置创建…