您的位置:首页 > 移动开发 > IOS开发

获取iOS任意线程调用堆栈(一)获取任意线程的调用栈地址列表

2017-05-31 12:22 519 查看


转载自:http://blog.csdn.net/jasonblog/article/details/49909163

如果要获取当前线程的调用栈,可以直接使用现有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
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.

SYNOPSIS

kern_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
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
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
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
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
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

通过了解以上不同平台的寄存器结构,我们可以编写出比较通用的回溯功能。


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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: