在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
在iOS中就是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃, 更多的crash分类。
更多的信号量定义在<signal.h>, 这里截取了部分常见的
Defined in header <signal.h>
#define SIGTERM /*implementation defined*/
#define SIGSEGV /*implementation defined*/
#define SIGINT /*implementation defined*/
#define SIGILL /*implementation defined*/
#define SIGABRT /*implementation defined*/
#define SIGFPE /*implementation defined*/
SIGSEGV
SIGSEGV是当一个进程执行了一个无效的内存引用,或发生段错误时发送给它的信号。意味着指针所对应的地址是无效地址,没有物理内存对应该地址。
- invalid memory access (segmentation fault) 不合法的内存访问(内存段错误)
- 访问已经释放的内存, 无效的内存地址引用信号(常见的野指针访问,所指向的
对象被释放或者收回
,但是该指针没有作任何的修改
,以至于该指针仍旧指向已经回收的内存地址
。这个指针就是野指针
) - 非ARC模式下,iOS中经常会出现在 Delegate对象野指针访问
- ARC模式下,iOS经常会出现在Block代码块内 强持有可能释放的对象
- 试图对只读映射区域进行写操作
SEGV_ACCERR, 对映射的对象没有权限(invalid permission for mapped object), 可能是野指针, 此地址的对象已经释放, 然后又调用对象的某个方法.
SEGV_MAPERR, 地址没有映射到对象(address not mapped to object)
xcode提供出的一些工具来协助复现: Xcode特性Address Sanitizer_gcs的博客-CSDN博客
SIGBUS
总线错误, 意思是该地址有效,但是总线不能读取,具体错误信息有以下三种
1) BUS_OBJERR , 硬件故障,不用说,程序员最常碰上的肯定不是这种情形。
2) BUS_ADRERR, 不存在的物理地址, Linux平台上执行malloc(),如果没有足够的RAM,Linux不是让malloc()失败返回,而是向当前进程分发SIGBUS信号。
3) BUS_ADRALN 未对齐的内存, ARM不支持非对齐的内存访问,要求对齐访问,否则向当前进程分发SIGBUS信号。
SIGBUS与SIGSEGV信号一样,可以正常捕获。SIGBUS的缺省行为是终止当前进程并产生core dump。
1) SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该指针。通常是未对齐的数据访问所致。
2) SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对应该地址。
参考资料: 关于SIGSEGV错误及处理方法(转) - 鸭子船长 - 博客园
关于SIGBUS的总结_CodeJoker的专栏-CSDN博客_sigbus
SIGINT
- external interrupt, usually initiated by the user
- 通常由用户输入产生的中断信号, 比如唤起键盘
- 在iOS中一般不会处理到该信号
SIGILL
- invalid program image, such as invalid instruction, (无效的程序映像,例如无效指令)
- 不管在任何情况下得杀死进程的信号
- 由于iOS应用程序平台的限制,在iOS APP内禁止kill掉进程,所以一般不会处理
- 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
SIGABRT
- abnormal termination condition, as is e.g. initiated by abort()
- 通常由于异常引起的中断信号,异常发生时系统会调用abort()函数发出该信号,有可能是NSException也有可能是Mach异常
- 多线程访问可变数组, 可变字典, 多线程添加/删除 元素导致,
- 一种是由于方法调用错误(调用了不能调用的方法)
- 一种是由于数组访问越界, 这样一般是先抛异常, 从异常就可以明显看出错误原因
SIGFPE, EXC_ARITHMETIC
- erroneous arithmetic operation such as divide by zero
- 浮点数异常的信号通知
- 一般是由于 除数为0引起的
SIGTERM
- 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。
- iOS中一般不会处理到这个信号
SIGPIPE
管道破裂。程序Socket发送失败中止信号, 还有可能在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
比如客户端程序向服务器端程序发送了消息,然后关闭客户端,服务器端返回消息的时候就会收到内核给的SIGPIPE信号。
TCP的全双工信道其实是两条单工信道,client端调用close的时候,虽然本意是关闭两条信道,但是其实只能关闭它发送的那一条单工信道,还是可以接受数据,server端还是可以发送数据,并不知道client端已经完全关闭了。
详细参考: Linux SIGPIPE信号产生原因与解决方法_自己的学习笔记-CSDN博客_sigpipe
SIGTRAP, EXC_BREAKPOINT
由断点指令或其它trap指令产生. 由debugger使用。
TRAP 是陷阱的意思。它并不是一个真正的崩溃信号。它会在处理器执行 trap 指令时发送。LLDB调试器通常会处理此信号,并在指定的断点处停止运行。如果你收到了原因不明的 SIGTRAP,先清除上次的输出,然后重新进行构建通常能解决这个问题。
该异常是由于打算给一个附加的调试器在执行特定的断点来中断进程时触发。你可以在自己的代码中使用__builtin_trap()方法来触发此异常。如果没有被调试器所附加,那么进程将会结束并且生成一份崩溃报告。
SIGKILL -- 程序结束接收中止信号, 此信号不可被拦截, 一般是OOM信号, 系统内存不足
OOM 其实是Out Of Memory
的简称,指的是在 iOS 设备上当前应用因为内存占用过高而被操作系统强制终止,在用户侧的感知就是 App 一瞬间的闪退,与普通的 Crash 没有明显差异。但是当我们在调试阶段遇到这种崩溃的时候,可以从 设置- 隐私 -分析与改进
中 是找不到普通类型的崩溃日志,只能够找到Jetsam
开头的日志,这种形式的日志其实就是 OOM 崩溃之后系统生成的一种专门反映内存异常问题的日志。那么下一个问题就来了,什么是Jetsam
?
Jetsam
是 iOS 操作系统为了控制内存资源过度使用而采用的一种资源管控机制。不同于MacOS
,Linux
,Windows
等桌面操作系统,出于性能方面的考虑,iOS 系统并没有设计内存交换空间的机制,所以在 iOS 中,如果设备整体内存紧张的话,系统只能将一些优先级不高或占用内存过大的进程直接终止掉。
Jetsam
机制清理策略可以总结为下面两点:
-
单个 App 物理内存占用超过上限
- 整个设备物理内存占用收到压力按照下面优先级完成清理:
-
后台应用>前台应用
-
内存占用高的应用>内存占用低的应用
-
用户应用>系统应用
-
翻阅XNU
源码的时候我们可以看到在Jetsam
机制终止进程的时候最终是通过发送SIGKILL
异常信号来完成的。
#define SIGKILL 9 kill (cannot be caught or ignored)
从系统库 signal.h 文件中我们可以找到SIGKILL
这个异常信号的解释,它不可以在当前进程被忽略或者被捕获. iOS性能优化实践:头条抖音如何实现OOM崩溃率下降50%
SIGSYS ---- 非法的系统调用。
EXC_BAD_INSTRUCTION --- 线程试图访问非法/无效的指令或将无效的参数(操作数)传递给指令
EXC_ARITMETHIC ---- 除以0或整数溢出/下溢引发的异常
EXC_SYSCALL , EXC_MACH_SYSCALL -- 应用程序访问内核服务(如文件I/O)或网络访问时发出
SIGALRM ---- 程序超时信号
SIGHUP-- 程序终端中止信号
SIGSTOP--程序键盘中止信号, 此信号不可被拦截
信号signal中断crash经常出现的位置
Apple在A12开始支持了arm64e指令集,提供了指令地址加密功能,即PAC(Pointer Authentication Code的缩写, 指针身份验证),
- 在比较新的机器上(一般iOS系统版本也比较高), Crash的概率比较大, PAC验证没通过
- 通过App Connect搜集到的原始Crash Log中Crash的Mach异常转化后的Signal是不一样的
PAC是什么
PAC是ARMv8.3 新增的功能,因为虽然系统是64位的,但是arm64指令地址根本用不满,所以把高位的部分(upper bits)拿来存一个指针地址的签名。
PAC指针验证码就是在CPU执行指令前先拿指针的高位签名和低位的实际地址部分坐下校验,失败了直接抛出异常
为了实现PAC, arm64e新增了两个指令:
- PACIASP 计算 PAC 加密并加到指针地址上
- AUTIASP 校验加密部分,并还原指针地址
PAC参考文章: iOS开发之Crash追踪之旅(一) - 简书