栈
调用栈
储运行子程序的重要的数据结构
主要存放返回地址、本地变量、参数及环境传递
用于跟踪每个活动的子例程在完成执行后应该返回控制的点
结构示意:
调用栈
栈顶
帧内的本地变量
----------------------
返回地址
----------------------
参数
栈帧 1
帧内的本地变量
----------------------
返回地址
----------------------
参数
栈帧 2
...
栈帧
栈底
为若干栈帧(frame)
每个栈帧对应一个函数调用
主要包含三部分组成函数参数、返回地址、帧内的本地变量
返回地址
表示当前函数执行完后上一栈帧的帧指针
递归获取所有栈
返回地址 获取上一栈,可以递归回溯获取整个调用栈
1:callstackSymbols
当前函数调用栈
使用[NSThread callstackSymbols]来获取当前函数调用栈
缺点:
若符号被strip裁剪后,无法通过该方法获取当前线程的完整的调用栈信息
子线程中该方法无法获取主线程的栈信息,而卡顿检测都是在子线程上执行的
Mach任务
内核中的进程和线程底层实现都是基于Mach任务和线程,其中任务是线程的容器
来管理资源,比如文件、I/O设备句柄等,我们熟知的进程和线程都有对应的
Mach底层的任务和线程。因此,可以通过底层任务来获取所有包含的线程。
通过Mach内核函数
获取栈信息过程:
1 函数地址:
所有线程- 线程状态- 线程调用栈- 函数地址
通过task_threads获取所有线程
通过thread_get_state获取线程调用栈
进而获取当前线程所有的函数地址
2 定位镜像:
所有镜像- 镜像地址范围 - 函数地址匹配 - 确定镜像
dyld提供了镜像相关的接口,如获取镜像数量_dyld_image_count、名称
_dyld_get_image_name及其地址_dyld_get_image_header
Mach-O中的Mach64 Header中包含了Load Commands数量,Load
Commands加载命令中包含了LC_SEGMENT_64,该加载命令数据结构包含了命
令名称Command、虚拟地址VM Address及其大小VM Size,因此可以通过遍历
获取LC_SEGMENT_64中的各个段的虚拟起始地址及其范围,就可以比较来定位
是否在该段中,进而就可以确定是否在该镜像中
3 查找符号:
镜像 Mach-o -- 符号表和字符串表 -- 所有符合及符合名称
通过LC_SYMTAB加载命令获取符号表及字符串表的信息,如地址、数量及大
小,就可以获取符号表中的所有符号及字符串表中对应的函数名称
4 定位符号:
所有符号地址---匹配函数地址 -- 确定符号及名称
通过遍历符号表中的所有符号地址来匹配与当前函数地址最接近的,即为要寻找
的函数符号,并通过符号表中的String Table Index字符串表偏移量来获取函数
符号名称