ios查看线程数量_iOS中线程Call Stack的捕获和解析(一)

http://www.javashuo.com/article/p-dxbeyeif-sy.html这里对上个月做的一个技术项目做部分技术小结,这篇文章描述的功能和我们在使用Xcode进行调试时点击暂停的效果类似。

一、获取任意一个线程的Call Stack

如果要获取当前线程的调用栈,可以直接使用现有API:[NSThread callStackSymbols]。

但是并没有相关API支持获取任意线程的调用栈,所以只能自己编码实现。

1. 基础结构

一个线程的调用栈是什么样的呢?

我的理解是应该包含当前线程的执行地址,并且从这个地址可以一级一级回溯到线程的入口地址,这样就反向构成了一条链:线程入口执行某个方法,然后逐级嵌套调用到当前现场。

(图片来源于维基百科)

如图所示,每一级的方法调用,都对应了一张活动记录,也称为活动帧。也就是说,调用栈是由一张张帧结构组成的,可以称之为栈帧。

我们可以看到,一张栈帧结构中包含着Return Address,也就是当前活动记录执行结束后要返回的地址(展开)。

那么,在我们获取到栈帧后,就可以通过返回地址来进行回溯了。

2. 指令指针和基址指针

我们明确了两个目标:(1)当前执行的指令,(2)当前栈帧结构。

以x86为例,寄存器用途如下:

1

2

3SP/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.

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

30Function - 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

所以我们可以通过这个API搭配相关参数来获得想要的寄存器信息:

1

2

3

4

5bool 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);

}

这里引入了一个结构体叫_STRUCT_MCONTEXT。

4. 不同平台的寄存器

_STRUCT_MCONTEXT在不同平台上的结构不同:

x86_64,如iPhone 6模拟器:

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_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模拟器:

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_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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17_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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15_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 */

};

可以对照《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. 算法实现

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/** * 关于栈帧的布局可以参考: * 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值