iOS Crash 一: 异常,信号,堆栈和符号化

XNU的一些概念

XNU

XNU是mac os和iOS的系统内核,分为三个组成部分Mach,BSD和I/O Kit


XNU

其中Mach可以看做XNU的内核,也就是内核的内核.提供调度,IPC等基础服务.
BSD和I/O Kit是更高层,BSD提供网络,进程,文件服务等,I/O是设备驱动服务.

Mach

Task: 任务, 与进程的概念接近,但是包含更多的组成部分.有着虚拟的内存空间.有一个或多个线程.

Thread: 线程,CPU调度的最小单位.

Ports: 内核对象object基于Mach实现功能,通过Mach message来互相通信,Ports即端口,通信通道,通过相关API(端口权限)来进行访问,一个端口可以接收多个发送者的消息,单同一时刻只能接收一个.

另外地址空间,内存分页,虚拟地址也是由Mach来实现的.虚拟地址可以看这篇.

BSD
位于Mach上层,提供进程,安全,文件系统,网络连接,用户管理等.

I/O Kit
I/O即input和output,用于数据交换,I/O Kit就是封装了一些基础类,用于实现具体的设备驱动程序.

异常与信号的一些概念

异常都会伴随信号,信号得不到预定的处理,就会终止程序,这些信号可能来自内核也可能是应用本身的进程.

Mach异常
是内核(kernel)异常,开发者可以捕获这一层级的异常;
比如一些内存问题, EXC_BAD_ACCESS就是访问了坏地址

UNIX信号
BSD在Mach异常机制之上构建的UNIX信号处理机制。异常信号首先被转换为Mach异常,如果没有被外界捕捉,则会被默认的异常处理ux_exception()转换为UNIX信号。
比如还是上面的访问了坏地址,会被转换成SIGSEGV信号.

UNIX信号 对应的Mach异常
SIGFPE EXC_ARITHMETIC
SIGSEGV EXC_BAD_ACCESS
SIGBUS EXC_BAD_ACCESS
SIGILL EXC_BAD_INSTRUCTION
SIGTRAP EXC_BREAKPOINT
SIGEMT EXC_EMULATION
SIGSYS EXC_UNIX_BAD_SYSCALL
SIGPIPE EXC_UNIX_BAD_PIPE
SIGABRT EXC_CRASH
SIGKILL EXC_SOFT_SIGNAL

常见的UNIX信号:

SIGABRT是系统调用abort()发出的信号,主动调用也是一样的,也就是说异常的来源可能是NSException也可能是Mach.

SIGSEGV是访问坏地址,比如访问未分配给进程的内存,或者已经释放的内存.
在堆栈中,如果异常的地址是常规数值,比如6位到10位左右的0x00002333ffff这种,这种情况一般是内存已经被释放;
如果很小的地址或者空地址,可能是未初始化的空指针.
如果是极大的数值,如0x2333ffff44ff这种,则可能是野指针,在一些情况下,指针指向的地址和预想的不同,就可以认为是野指针,比如在线程安全问题中可能会遇到.

SIGBUS是总线异常,访问或者写入非法地址, 比如向只读权限的内存写入,或者内存对齐出现问题,比如一些字符串及字符操作.一般只会由系统函数或者其他一些C函数触发.

SIGSYS是非法的系统调用.

SIGTRAP通常是swift运行错误引起,最常见的是给一个非可选类型赋值nil,或者失败的强制类型转换,DispatchSourceTimer在suspend状态时被释放等.
另外调用编译器的trap相关函数也可以主动触发,比如设置断点以及其他一些debug操作.

NSException异常
应用层异常,是OC代码运行过程中的逻辑错误抛出的异常.可以用@trycatch捕获.
OC封装了具体的NSException类型,比如:
NSInvalidArgumentException 非法参数,向数组或者字典插入了nil.
NSRangeException 越界.
NSGenericException 迭代器异常,比如快速遍历的时候修改元素.
NSInternalInconsistencyException 类型不符异常,比如可变性不符,非Mutable调用Mutable的方法.
NSFileHandleOperationException 文件操作异常,比如内存不足.
还有KVO的相关异常,比如尝试移除未注册的observer,重复移除observer,keypath是nil等.
在非主线程更新UI.
OOM异常,内存不足.
消息动态决议的unrecognized selector等等.

函数调用栈

函数调用栈就是一个线程的函数调用回溯,首先来看看函数调用的过程.

举个例子

int func1(int x, int y){
  int a;
  int b;
  int c =  func2(&a, &b);
  return c;
}

func1里面调用了func2.这种嵌套调用是非常常见的.
当调用func1时,func1入栈,虽然发生了嵌套,但是func1在栈中的布局是连续的,每个函数在栈中的布局都是类似的,这种布局叫做栈帧.

栈是从高地址向低地址填充的,首先一个函数栈帧的高位是栈底,
然后将函数内的局部变量放入栈中,但是入栈顺序与架构有关,可能是a在高位也可能是b在高位.
然后讲实参从右到左入栈,所以y先入栈,x在后面,y是高位.
最后栈顶是函数返回地址.

objc4

这张图显示了一个函数调用栈,最下面的是最先调用的,最上面的是最后调用的,运行的是objc4源码.
首先是在main函数,
然后调用了一个OC方法,转到objc_msgSend,
快速查找失败,调用_objc_msgSend_uncached,
然后进入慢速查找lookUpImpOrForward,
然后在类对象中查找SEL,getMethodNoSuper_nolock,
最后调用objc_class的methods()函数获取method_array_t.

不过这个例子不太适用于日常开发,因为iOS程序的函数调用栈,不会出现上面几个函数.


点击按钮

这是点击按钮的函数调用栈,这期间会多次执行objc_msgSend,但是并未出现.
objc_msgSend等直接由汇编实现的函数,直接嵌在调用它的函数的栈帧中,不会开辟新的栈帧,所以调用栈里看不到.

异常堆栈

异常堆栈就是程序抛出异常的时候,使用相关API可以获取到的此时的函数调用栈,展示程序最后的行为.
对应Mach异常,UNIX信号,NSException都有不同的API.
如何实现获取异常堆栈,本篇不做详述,以后再慢慢研究,相关的库与工具有很多.

dSYM

dSYM即debug symbols,其实是一个文件夹,可以显示包内容,有一个DWARF文件夹,里面是dsym文件.mach-o类型是MH_DSYM.
可以用MachOView打开


dsym

Commands段和Sections段都没啥内容,最重要的是符号表symbols.
在这里还可以看到一个UUID,同样项目app包里的对应可执行文件(位于xxx.xcarchive/products/applications/xxx/xxx)也有一个UUID,在archive时,他们生成的值是一样的.需要相等才能对应上.


可执行文件

函数在mach-o文件中是一类符号,符号表记录了符号在内存中的偏移量,在程序加载到内存中时,会被分配到一个基地址,根据偏移量把函数加载到对应的内存,因为是虚拟内存,所以对得上号.

异常堆栈

这是bugly上的一个错误堆栈
首先能看到线程0,UNIX信号SIGTRAP

libdispatch.dylib   0x00000001afbd0e94 0x00000001afb96000 + 241300

XXXX                   0x0000000102a2688c 0x000000010278c000 + 2730124

左边表示符号所在的Mach-O,通常都是项目里的crash,一般来自xxx.app,如果项目中有其他系统库或者动态库,也可能来自这些.
这里第一行是libdispatch.dylib的函数,第二行是xxx.app的函数.
如果是某些静态库比如项目的组件xxx1.framework,或者类似AFNetworking.framework这些里出现了crash,则会显示xxx1或者AFNetworking.

0x00000001afbd0e94是栈帧的栈底
0x00000001afb96000是mach-o的基地址,一次运行中,同一个mach-o的基地址是相同的.
241300是偏移量. 0x00000001afb96000 + 241300 = 0x00000001afbd0e94.

当然考虑到不同库,有的也可能没有栈帧的地址,需要计算一下.

符号化

获取异常堆栈的时候,只能拿到程序的基地址,函数栈帧的地址,和偏移量
现在,地址有了(堆栈),符号表也有了(dsym),就可以还原成函数调用栈了,这一步叫做符号化.把地址还原成符号.
可执行文件路径下 dwarfdump --uuid xxx 查看app可执行文件的uuid
dsym路径下 dwarfdump --uuid xx.app.dSYM 查看dsym的uuid

使用atos工具符号化

~ % atos -help
Usage: atos [-p pid] [-o executable/dSYM] [-f file-of-input-addresses] [-s slide | -l loadAddress | -offset] [-arch architecture] [-printHeader] [-fullPath] [-inlineFrames] [-d delimiter] [address ...]

        -d/--delimiter     delimiter when outputting inline frames. Defaults to newline.
        --fullPath         show full path to source file
        -i/--inlineFrames  display inlined functions
        --offset           treat all following addresses as offsets into the binary

符号化: atos -arch 架构类型 -o 路径 -l 基地址 栈帧地址

trigger@T Desktop % atos -arch arm64 -o AppName.app.dSYM -l 0x00000001045f4000 0x00000001046ac7ec
-[GLPlayEngine isForbiddenCoordWithCurrentBoard:] (in AppName) (GLPlayManager.m:188)

有些时候不能定位到具体的行数,文件和行显示类似

#0 Thread
SIGTRAP

0 GLDispatchTimer.__ivar_destroyer (in AppName) (GLDispatchTimer.swift:0)
1 GLDispatchTimer.__deallocating_deinit (in AppName) (<compiler-generated>:0)

1是较先执行的函数,compiler-generated表示编译器生成文件,这里是OC和Swift混编,
__deallocating_deinit是析构函数,它既不在项目代码里,也不在某个静态库/动态库中,而是由编译器生成.

0 是最后执行的函数,定位到0行说明异常不是出现在该文件这一层,而是Mach异常,这里的UNIX信号是SIGTRAP,
__ivar_destroyer和__deallocating_deinit函数,应该是析构的时候出现了问题,
swift代码出现SIGTRAP, 除了是非可选类型被赋值nil,还有一些情况如DispatchSourceTimer在suspend状态时被释放,失败的强制类型转换等
以这些为基础基本可以找到问题所在.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容