如果要获取当前线程的调用栈,可以直接使用现有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.
可以看到,我们可以通过指令指针来获取当前指令地址,以及通过栈基址指针获取当前栈帧地址。
那么问题来了,我们怎么获取到相关寄存器呢?
3. 线程执行状态
考虑到一个线程被挂起时,后续继续执行需要恢复现场,所以在挂起时相关现场需要被保存起来,比如当前执行到哪条指令了。
那么就要有相关的结构体来为线程保存运行时的状态,经过一番查阅,得到如下信息:
The function thread_get_state returns the execution state (e.g. the machine registers) of target_thread as specified by flavor。
kern_return_t thread_get_state
(
thread_act_t target_act,
thread_state_flavor_t flavor,
thread_state_t old_state,
mach_msg_type_number_t *old_stateCnt
);
所以我们可以通过这个API搭配相关参数来获得想要的寄存器信息:
bool bs_fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT
mach_msg_type_number_t state_count = BS_THREAD_STATE_COUNT;
kern_return_t kr = thread_get_state(thread, BS_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count);
return (kr == KERN_SUCCESS);
}
由于不同的架构对应的
state_count
不同,所以这里用了宏BS_THREAD_STATE_COUNT来做处理
这里引入了一个结构体叫_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;
};
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;
};
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 */
};
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 */
};
5. 线程调用栈的算法实现
NSString *_bs_backtraceOfThread(thread_t thread) {
uintptr_t backtraceBuffer[50];
int i = 0;
NSMutableString *resultString = [[NSMutableString alloc] initWithFormat:@"Backtrace of Thread %u:\n", thread];
_STRUCT_MCONTEXT machineContext;
if(!bs_fillThreadStateIntoMachineContext(thread, &machineContext)) {
return [NSString stringWithFormat:@"Fail to get information about thread: %u", thread];
}
const uintptr_t instructionAddress = bs_mach_instructionAddress(&machineContext);
backtraceBuffer[i] = instructionAddress;
++i;
uintptr_t linkRegister = bs_mach_linkRegister(&machineContext);
if (linkRegister) {
backtraceBuffer[i] = linkRegister;
i++;
}
if(instructionAddress == 0) {
return @"Fail to get instruction address";
}
BSStackFrameEntry frame = {0};
const uintptr_t framePtr = bs_mach_framePointer(&machineContext);
if(framePtr == 0 ||
bs_mach_copyMem((void *)framePtr, &frame, sizeof(frame)) != KERN_SUCCESS) {
return @"Fail to get frame pointer";
}
for(; i < 50; i++) {
backtraceBuffer[i] = frame.return_address;
if(backtraceBuffer[i] == 0 ||
frame.previous == 0 ||
bs_mach_copyMem(frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) {
break;
}
}
int backtraceLength = i;
Dl_info symbolicated[backtraceLength];
bs_symbolicate(backtraceBuffer, symbolicated, backtraceLength, 0);
for (int i = 0; i < backtraceLength; ++i) {
[resultString appendFormat:@"%@", bs_logBacktraceEntry(i, backtraceBuffer[i], &symbolicated[i])];
}
[resultString appendFormat:@"\n"];
return [resultString copy];
}