调用栈 储运行子程序的重要的数据结构 主要存放返回地址、本地变量、参数及环境传递 用于跟踪每个活动的子例程在完成执行后应该返回控制的点 结构示意: 调用栈 栈顶 帧内的本地变量 ---------------------- 返回地址 ---------------------- 参数 栈帧 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字符串表偏移量来获取函数 符号名称