iOS中線程Call Stack的捕獲和解析(一)

來源:互聯網
上載者:User

iOS中線程Call Stack的捕獲和解析(一)

 

一、擷取任意一個線程的Call Stack

如果要擷取當前線程的調用棧,可以直接使用現有API:[NSThread callStackSymbols]

但是並沒有相關API支援擷取任意線程的調用棧,所以只能自己編碼實現。

1. 基礎結構

一個線程的調用棧是什麼樣的呢?

我的理解是應該包含當前線程的執行地址,並且從這個地址可以一級一級回溯到線程的入口地址,這樣就反向構成了一條鏈:線程入口執行某個方法,然後逐級嵌套調用到當前現場。

(圖片來源於維基百科)

,每一級的方法調用,都對應了一張活動記錄,也稱為活動幀。也就是說,調用棧是由一張張幀結構組成的,可以稱之為棧幀。

我們可以看到,一張棧幀結構中包含著Return Address,也就是當前活動記錄執行結束後要返回的地址(展開)。<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPsTHw7SjrNTaztLDx7vxyKG1vdW71qG686Osvs2/ydLUzai5/be1u9i12Na3wLS9+NDQu9jL3cHLoaM8L3A+DQo8aDIgaWQ9"2-指令指標和基址指標">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.

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

所以我們可以通過這個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);}

這裡引入了一個結構體叫_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 */};

可以對照《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;}

如上。

二、編碼實現對一個地址進行符號化解析

後續iOS中線程Call Stack的捕獲和解析(二)。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.