通过帧指针 FP进行方法回溯

986 阅读2分钟

背景

在之前进行app启动优化,使用静态插桩时需要在插入方法中拿到父方法,在看完冬瓜为什么使用汇编可以 Hook objc_msgSend(上)- 汇编基础这篇文章后想使用帧指针FP来验证下方法回溯。

插桩方法

在插桩时会用到下面这个方法:

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    // If initialization has not occurred yet (meaning that guard is uninitialized), that means that initial functions like +load are being run. These functions will only be run once anyways, so we should always allow them to be recorded and ignore guard
    if (sStopCollecting || (!(*guard) && sInitDidOccur)) {
        return;
    }
    *guard = 0;
    void *pointer = __builtin_return_address(0);
    PointerNode *node = malloc(sizeof(PointerNode));
    *node = (PointerNode){pointer, NULL};
    OSAtomicEnqueue(sQueue, node, offsetof(PointerNode, next));
}

其中方法

void *pointer = __builtin_return_address(0);

根据传参可以得到各祖先方法,传入0可以得到父方法。

创建一个结构体

struct Dog {
    func eat() {
    }
}

当调用eat()时,因为已被插桩所以会先调用__sanitizer_cov_trace_pc_guard,然后查看PC中的方法正是eat():

(lldb) po PC
0x0000000100a27c3c

(lldb) dis -s 0x0000000100a27c3c
swiftDemo`Dog.eat():
    0x100a27c3c <+20>: ldp    x29, x30, [sp], #0x10
    0x100a27c40 <+24>: ret    
  

使用FP实现

首先拿到x29(FP)寄存器所存地址,即当前方法的栈底:

(lldb) register read x29
      fp = 0x000000016f3dd620

然后加8个字节得到父方法的x30(LR)(链接寄存器)之前所存的地址:

(lldb) memory read 0x000000016f3dd620+0x8
0x16f3dd628: 3c 7c a2 00 01 00 00 00 70 d7 3d 6f 01 00 00 00  <|......p.=o....

读取0x16f3dd628内容,可得父方法LR所存地址为0x0000000100a27c3c

(lldb) dis -s 0x0000000100a27c3c
swiftDemo`Dog.eat():
    0x100a27c3c <+20>: ldp    x29, x30, [sp], #0x10
    0x100a27c40 <+24>: ret    
  

即对应eat()方法。

以上原理参考: 图片引自:@xi_lin

获取任意线程调用栈的那些事

Improving App Performance with Order Files桩

为什么使用汇编可以 Hook objc_msgSend(上)- 汇编基础

SanitizerCoverage