iOS中线程Call Stack的捕获和解析(一)
来源:互联网 发布:北邮人文学院知乎 编辑:程序博客网 时间:2024/05/16 07:08
http://blog.csdn.net/jasonblog/article/details/49909209这里对上个月做的一个技术项目做部分技术小结,这篇文章描述的功能和我们在使用Xcode进行调试时点击暂停的效果类似。
一、获取任意一个线程的Call Stack
如果要获取当前线程的调用栈,可以直接使用现有API:[NSThread callStackSymbols]
。
但是并没有相关API支持获取任意线程的调用栈,所以只能自己编码实现。
1. 基础结构
一个线程的调用栈是什么样的呢?
我的理解是应该包含当前线程的执行地址,并且从这个地址可以一级一级回溯到线程的入口地址,这样就反向构成了一条链:线程入口执行某个方法,然后逐级嵌套调用到当前现场。
(图片来源于维基百科)
如图所示,每一级的方法调用,都对应了一张活动记录,也称为活动帧。也就是说,调用栈是由一张张帧结构组成的,可以称之为栈帧。
我们可以看到,一张栈帧结构中包含着Return Address,也就是当前活动记录执行结束后要返回的地址(展开)。
那么,在我们获取到栈帧后,就可以通过返回地址来进行回溯了。
2. 指令指针和基址指针
我们明确了两个目标:(1)当前执行的指令,(2)当前栈帧结构。
以x86为例,寄存器用途如下:
SP/ESP/RSP: Stack pointer for top address of the stack.BP/EBP/RBP: Stack base pointer for holding the address of the current stack frame.IP/EIP/RIP: Instruction pointer. Holds the program counter, the current instruction address.
- 1
- 2
- 3
可以看到,我们可以通过指令指针来获取当前指令地址,以及通过栈基址指针获取当前栈帧地址。
那么问题来了,我们怎么获取到相关寄存器呢?
3. 线程执行状态
考虑到一个线程被挂起时,后续继续执行需要恢复现场,所以在挂起时相关现场需要被保存起来,比如当前执行到哪条指令了。
那么就要有相关的结构体来为线程保存运行时的状态,经过一番查阅,得到如下信息:
The function thread_get_state returns the execution state (e.g. the machine registers) of target_thread as specified by flavor.
Function - Return the execution state for a thread.SYNOPSISkern_return_t thread_get_state (thread_act_t target_thread, thread_state_flavor_t flavor, thread_state_t old_state, mach_msg_type_number_t old_state_count);/* * THREAD_STATE_FLAVOR_LIST 0 * these are the supported flavors */#define x86_THREAD_STATE32 1#define x86_FLOAT_STATE32 2#define x86_EXCEPTION_STATE32 3#define x86_THREAD_STATE64 4#define x86_FLOAT_STATE64 5#define x86_EXCEPTION_STATE64 6#define x86_THREAD_STATE 7#define x86_FLOAT_STATE 8#define x86_EXCEPTION_STATE 9#define x86_DEBUG_STATE32 10#define x86_DEBUG_STATE64 11#define x86_DEBUG_STATE 12#define THREAD_STATE_NONE 13/* 14 and 15 are used for the internal x86_SAVED_STATE flavours */#define x86_AVX_STATE32 16#define x86_AVX_STATE64 17#define x86_AVX_STATE 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
所以我们可以通过这个API搭配相关参数来获得想要的寄存器信息:
bool jdy_fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT *machineContext) { mach_msg_type_number_t state_count = x86_THREAD_STATE64_COUNT; kern_return_t kr = thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&machineContext->__ss, &state_count); return (kr == KERN_SUCCESS);}
- 1
- 2
- 3
- 4
- 5
这里引入了一个结构体叫_STRUCT_MCONTEXT
。
4. 不同平台的寄存器
_STRUCT_MCONTEXT
在不同平台上的结构不同:
x86_64,如iPhone 6模拟器:
_STRUCT_MCONTEXT64{ _STRUCT_X86_EXCEPTION_STATE64 __es; _STRUCT_X86_THREAD_STATE64 __ss; _STRUCT_X86_FLOAT_STATE64 __fs;};_STRUCT_X86_THREAD_STATE64{ __uint64_t __rax; __uint64_t __rbx; __uint64_t __rcx; __uint64_t __rdx; __uint64_t __rdi; __uint64_t __rsi; __uint64_t __rbp; __uint64_t __rsp; __uint64_t __r8; __uint64_t __r9; __uint64_t __r10; __uint64_t __r11; __uint64_t __r12; __uint64_t __r13; __uint64_t __r14; __uint64_t __r15; __uint64_t __rip; __uint64_t __rflags; __uint64_t __cs; __uint64_t __fs; __uint64_t __gs;};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
x86_32,如iPhone 4s模拟器:
_STRUCT_MCONTEXT32{ _STRUCT_X86_EXCEPTION_STATE32 __es; _STRUCT_X86_THREAD_STATE32 __ss; _STRUCT_X86_FLOAT_STATE32 __fs;};_STRUCT_X86_THREAD_STATE32{ unsigned int __eax; unsigned int __ebx; unsigned int __ecx; unsigned int __edx; unsigned int __edi; unsigned int __esi; unsigned int __ebp; unsigned int __esp; unsigned int __ss; unsigned int __eflags; unsigned int __eip; unsigned int __cs; unsigned int __ds; unsigned int __es; unsigned int __fs; unsigned int __gs;};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
ARM64,如iPhone 5s:
_STRUCT_MCONTEXT64{ _STRUCT_ARM_EXCEPTION_STATE64 __es; _STRUCT_ARM_THREAD_STATE64 __ss; _STRUCT_ARM_NEON_STATE64 __ns;};_STRUCT_ARM_THREAD_STATE64{ __uint64_t __x[29]; /* General purpose registers x0-x28 */ __uint64_t __fp; /* Frame pointer x29 */ __uint64_t __lr; /* Link register x30 */ __uint64_t __sp; /* Stack pointer x31 */ __uint64_t __pc; /* Program counter */ __uint32_t __cpsr; /* Current program status register */ __uint32_t __pad; /* Same size for 32-bit or 64-bit clients */};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
ARMv7/v6,如iPhone 4s:
_STRUCT_MCONTEXT32{ _STRUCT_ARM_EXCEPTION_STATE __es; _STRUCT_ARM_THREAD_STATE __ss; _STRUCT_ARM_VFP_STATE __fs;};_STRUCT_ARM_THREAD_STATE{ __uint32_t __r[13]; /* General purpose register r0-r12 */ __uint32_t __sp; /* Stack pointer r13 */ __uint32_t __lr; /* Link register r14 */ __uint32_t __pc; /* Program counter r15 */ __uint32_t __cpsr; /* Current program status register */};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
可以对照《iOS ABI Function Call Guide》,其中在ARM64相关章节中描述到:
The frame pointer register (x29) must always address a valid frame record, although some functions–such as leaf functions or tail calls–may elect not to create an entry in this list. As a result, stack traces will always be meaningful, even without debug information
而在ARMv7/v6上描述到:
The function calling conventions used in the ARMv6 environment are the same as those used in the Procedure Call Standard for the ARM Architecture (release 1.07), with the following exceptions:
*The stack is 4-byte aligned at the point of function calls.
Large data types (larger than 4 bytes) are 4-byte aligned.
Register R7 is used as a frame pointer
Register R9 has special usage.*
所以,通过了解以上不同平台的寄存器结构,我们可以编写出比较通用的回溯功能。
5. 算法实现
/** * 关于栈帧的布局可以参考: * https://en.wikipedia.org/wiki/Call_stack * http://www.cs.cornell.edu/courses/cs412/2008sp/lectures/lec20.pdf * http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ */typedef struct JDYStackFrame { const struct JDYStackFrame* const previous; const uintptr_t returnAddress;} JDYStackFrame;//int jdy_backtraceThread(thread_t thread, uintptr_t *backtraceBuffer, int limit) { if (limit <= 0) return 0; _STRUCT_MCONTEXT mcontext; if (!jdy_fillThreadStateIntoMachineContext(thread, &mcontext)) { return 0; } int i = 0; uintptr_t pc = jdy_programCounterOfMachineContext(&mcontext); backtraceBuffer[i++] = pc; if (i == limit) return i; uintptr_t lr = jdy_linkRegisterOfMachineContext(&mcontext); if (lr != 0) { /* 由于lr保存的也是返回地址,所以在lr有效时,应该会产生重复的地址项 */ backtraceBuffer[i++] = lr; if (i == limit) return i; } JDYStackFrame frame = {0}; uintptr_t fp = jdy_framePointerOfMachineContext(&mcontext); if (fp == 0 || jdy_copyMemory((void *)fp, &frame, sizeof(frame)) != KERN_SUCCESS) { return i; } while (i < limit) { backtraceBuffer[i++] = frame.returnAddress; if (frame.returnAddress == 0 || frame.previous == NULL || jdy_copyMemory((void *)frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) { break; } } return i;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
如上。
- iOS中线程Call Stack的捕获和解析(一)
- iOS中线程Call Stack的捕获和解析(一)
- iOS中线程Call Stack的捕获和解析
- iOS中线程Call Stack的捕获和解析(二)
- iOS:First throw call stack的解决方法
- gdb 中 dump 出所有的 java 线程的 call stack
- Android和iOS开发中异常崩溃的捕获
- iOS异常捕获-堆栈信息的解析
- C++中堆和栈(非数据结构的heap and stack)的完全解析
- ObjectiveC中打印Call Stack的若干方法
- Eclipse 线程堆栈分析实例(线程dump/堆栈dump)call stack
- 4、Volley解析(二),源码的深入分析一,缓存线程和网络请求线程
- dump call stack 的实现
- 线程中捕获错误
- 小览call stack(调用栈) (一)
- 小览call stack(调用栈) (一)
- 浅谈-----异常的捕获和处理(一)
- 捕获IOS APP中出现的异常
- Jedis远程链接报错(connect timed out)
- Pycharm Python IDE 2017 激活方式
- eclipse部署maven web项目到tomcat服务器时,没有将lib、web.xml复制过去的解决办法
- 论文阅读:Learning and Transferring Mid-Level Image Representations using Convolutional Neural Networks
- 我的雷打不动的代码风格
- iOS中线程Call Stack的捕获和解析(一)
- 流对象的异常处理
- python3 range函数
- 实用Python之字符串长度计算
- poj3274——Gold Balanced Lineup
- Java日志终极指南
- java自定义注解
- 数据结构实验之图论三:判断可达性
- every() 、fliter()、forEach()、map()、some()