Capture and parsing of thread Call Stack in iOS (1)

Source: Internet
Author: User

Capture and parsing of thread Call Stack in iOS (1)

 

1. Obtain the Call Stack of any thread

To obtain the call stack of the current thread, you can directly use the existing API:[NSThread callStackSymbols].

However, there is no relevant API to support obtaining the call stack of any thread, so you can only implement it by yourself.

1. Infrastructure

What is the call stack of a thread?

My understanding is that it should include the execution address of the current thread, and the address can be traced back to the entry address of the thread at a level, which forms a chain in reverse order: the thread entry executes a method, then, the call is nested to the current site step by step.

(Image Source: Wikipedia)

Each level of method call corresponds to an activity record, also known as an activity frame. That is to say, the call stack is composed of a frame structure, which can be called a stack frame.

We can see that a stack frame structure contains the Return Address, which is the Address to be returned after the execution of the current activity record ends (expand ). <喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> Authorization/ydLUzai5/be1u9i12Na3wLS9 + ndqu9jl3jwam8l3a + DQo8aDIgaWQ9 "2-command pointer and base address Pointer"> 2. Command pointer and base address pointer

We have defined two objectives: (1) the currently executed command, and (2) the current stack frame structure.

Taking x86 as an example, the register usage is as follows:

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.

We can see that we can get the current instruction address through the instruction pointer and get the current stack frame address through the stack base address pointer.

So the question is, how can we get the relevant registers?

3. Thread execution status

When a thread is suspended, it needs to be restored for subsequent execution. Therefore, when the thread is suspended, the relevant field needs to be saved, for example, the instruction to be executed.

There must be a relevant struct to save the running state of the thread. After some reading, the following information is obtained:

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

Therefore, we can use this API with relevant parameters to obtain the desired register information:

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);}

Here we introduce a struct called_STRUCT_MCONTEXT.

4. registers on different platforms

_STRUCT_MCONTEXTThe structures on different platforms are different:

X86_64, such as iPhone 6 simulator:

_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, such as iPhone 4s simulator:

_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, such as 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, such as iPhone 4 s:

_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 */};

Refer to the iOS ABI Function Call Guide, which is described in ARM64:

The frame pointer register (x29) must always address a valid frame record, although some functions-such as leaf functions or tail CILS-may elect not to create an entry in this list. as a result, stack traces will always be meaningful, even without debug information

In ARMv7/v6, the following is described:

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 CILS.
Large data types (larger than 4 bytes) are 4-byte aligned.
Register R7 is used as a frame pointer
Register R9 has special usage .*

Therefore, by understanding the register structure of different platforms above, we can compile general backtracking functions.

5. Algorithm Implementation
/*** Stack frame layout can be referred to: * 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 l Imit) {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) {/* Because lr stores the returned address, duplicate address items should be generated when lr is valid */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 ;}

As shown above.

2. coding implements symbolic resolution of an address

Capture and parse the thread Call Stack in iOS (2 ).

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

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.