登录 立即注册
金钱:

Code4App-iOS开发-iOS 开源代码库-iOS代码实例搜索-iOS特效示例-iOS代码例子下载-Code4App.com

查看: 333|回复: 6

iOS中线程Call Stack的捕获和解析(一),call stack窗口

[复制链接]

293

主题

594

帖子

2386

金钱

手工艺人

发表于 2018-1-11 11:39:15 | 显示全部楼层 |阅读模式

        

        


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

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

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


1. 基础结构

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

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

        

(图片来源于维基百科)

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

        我们可以看到,一张栈帧结构中包含着Return Address,也就是当前活动记录执行结束后要返回的地址(展开)。vcD4NCjxwPsTHw7SjrNTaztLDx7vxyKG1vdW71qG686Osvs2/ydLUzai5/be1u9i12Na3wLS9+NDQu9jL3cHLoaM8L3A+DQo8aDIgaWQ9"2-指令指针和基址指针">2. 指令指针和基址指针</h2>

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

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

[Java] 查看源文件 复制代码
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.

[Java] 查看源文件 复制代码
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

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

[Java] 查看源文件 复制代码
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)&amp;machineContext->__ss, &amp;state_count);
    return (kr == KERN_SUCCESS);
}

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


4. 不同平台的寄存器

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

        x86_64,如iPhone 6模拟器:

[Java] 查看源文件 复制代码
_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模拟器:

[Java] 查看源文件 复制代码
_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:

[Java] 查看源文件 复制代码
_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:

[Java] 查看源文件 复制代码
_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&ndash;such as leaf functions or tail calls&ndash;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. 算法实现
[mw_shl_code=java,true]
/**
* 关于栈帧的布局可以参考:
* https://en.wikipedia.org/wiki/Call_stack
* https://www.cs.cornell.edu/courses/cs412/2008sp/lectures/lec20.pdf
* https://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

21

主题

7196

帖子

2620

金钱

手工艺人

发表于 2018-1-11 11:48:57 | 显示全部楼层
帮帮顶顶!!

16

主题

7181

帖子

1453

金钱

手工艺人

发表于 2018-1-11 12:01:14 | 显示全部楼层
code4app好的内容真的很多~赞

0

主题

7142

帖子

-525

金钱

限制会员

发表于 2018-1-11 12:12:18 | 显示全部楼层
我是来学习的

23

主题

7147

帖子

1048

金钱

iOS大神

Rank: 6Rank: 6

分享大神

发表于 2018-1-11 12:20:20 | 显示全部楼层
支持,感谢,祝code4app越来越好~

21

主题

7158

帖子

2664

金钱

手工艺人

发表于 2018-1-11 12:26:30 | 显示全部楼层
膜拜大神~

15

主题

7145

帖子

1700

金钱

手工艺人

发表于 2018-1-11 12:41:16 | 显示全部楼层
虽不明,但觉厉...
*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册 新浪微博登陆

本版积分规则

关闭

每日头条

通过邮件订阅最新 Code4App 信息
上一条 /4 下一条

广告投放| Github|申请友链|手机版|Code4App ( 粤ICP备15117877号-1 )

快速回复 返回顶部 返回列表