问题描述
我目前正在尝试使用 C 程序在 ARM64 设备上基于堆栈指针和链接寄存器获取回溯。
下面是objdump的例子
bar() 使用 240444 调用 foo():ebfffd68 bl 23f9ec
我可以获取链接寄存器 (lr),然后从中获取 23f9ec,将其保存到回溯列表作为最后一个例程。
我的问题:从下面与当前 lr 0023f9ec
这是我的实现,但之前的 lr 出错
int bt(void** backtrace,int max_size) {
unsigned long* sp = __get_SP();
unsigned long* ra = __get_LR();
int* funcbase = (int*)(int)&bt;
int spofft = (short)((*funcbase));
sp = (char*)sp-spofft;
unsigned long* wra = (unsigned long*)ra;
int spofft;
int depth = 0;
while(ra) {
wra = ra;
while((*wra >> 16) != 0xe92d) {
wra--;
}
if(wra == 0)
return 0;
spofft = (short)(*wra & 0xffff);
if(depth < max_size)
backtrace[depth] = ra;
else
break;
ra =(unsigned long *)((unsigned long)ra + spofft);
sp =(unsigned long *)((unsigned long)sp + spofft);
depth++;
}
return 1;
}
0023f9ec <foo@@Base>:
23f9ec: e92d42f3 push {r0,r1,r4,r5,r6,r7,r9,lr}
23f9f0: e1a09001 mov r9,r1
23f9f4: e1a07000 mov r7,r0
23f9f8: ebfffff9 bl 23f9e4 <__get_SP@@Base>
23f9fc: e59f4060 ldr r4,[pc,#96] ; 23fa64 <foo@@Base+0x78>
23fa00: e08f4004 add r4,pc,r4
23fa04: e1a05000 mov r5,r0
23fa08: ebfffff3 bl 23f9dc <__get_LR@@Base>
23fa0c: e59f3054 ldr r3,#84] ; 23fa68 <foo@@Base+0x7c>
23fa10: e3002256 movw r2,#598 ; 0x256
23fa14: e59f1050 ldr r1,#80] ; 23fa6c <foo@@Base+0x80>
23fa18: e7943003 ldr r3,[r4,r3]
23fa1c: e08f1001 add r1,r1
23fa20: e5934000 ldr r4,[r3]
23fa24: e1a03005 mov r3,r5
23fa28: e6bf4074 sxth r4,r4
23fa2c: e58d4004 str r4,[sp,#4]
23fa30: e1a06000 mov r6,r0
23fa34: e58d0000 str r0,[sp]
23fa38: e59f0030 ldr r0,#48] ; 23fa70 <foo@@Base+0x84>
23fa3c: e08f0000 add r0,r0
23fa40: ebfd456d bl 190ffc <printf@plt>
23fa44: e1a03009 mov r3,r9
23fa48: e1a02007 mov r2,r7
23fa4c: e1a01006 mov r1,r6
23fa50: e0640005 rsb r0,r5
23fa54: ebffff70 bl 23f81c <get_prev_sp_ra2@@Base>
23fa58: e3a00000 mov r0,#0
23fa5c: e28dd008 add sp,sp,#8
23fa60: e8bd82f0 pop {r4,pc}
23fa64: 003d5be0 eorseq r5,r0,ror #23
23fa68: 000026c8 andeq r2,r8,asr #13
23fa6c: 002b7ba6 eoreq r7,fp,lsr #23
23fa70: 002b73e5 eoreq r7,ror #7
0023fe14 <bar@@Base>:
23fe14: e92d4ef0 push {r4,sl,lr}
23fe18: e24dde16 sub sp,#352 ; 0x160
23fe1c: e59f76a8 ldr r7,#1704] ; 2404cc <bar@@Base+0x6b8>
23fe20: e1a04000 mov r4,r0
23fe24: e59f66a4 ldr r6,#1700] ; 2404d0 <bar@@Base+0x6bc>
23fe28: e1a03000 mov r3,r0
23fe2c: e59f26a0 ldr r2,#1696] ; 2404d4 <bar@@Base+0x6c0>
23fe30: e08f7007 add r7,r7
23fe34: e08f6006 add r6,r6
23fe38: e3a00000 mov r0,#0
23fe3c: e08f2002 add r2,r2
23fe40: e1a05001 mov r5,r1
23fe44: e3a01003 mov r1,#3
23fe48: e59f9688 ldr r9,#1672] ; 2404d8 <bar@@Base+0x6c4>
.....................................................................
24043c: e3a0100f mov r1,#15
240440: e1a0000a mov r0,sl
240444: ebfffd68 bl 23f9ec <foo@@Base>
240448: e59f2108 ldr r2,#264] ; 240558 <bar@@Base+0x744>
24044c: e3a01003 mov r1,#3
240450: e08f2002 add r2,r2
240454: e1a05000 mov r5,r0
240458: e1a03000 mov r3,r0
24045c: e3a00000 mov r0,#0
解决方法
我认为没有什么简单的方法可以做到这一点。
通常任何操作系统的寄存器 ABI 都包含一个“帧指针”寄存器。例如,在 Apple 的 armv7 ABI 上,这是 r7
:
0x10006fc0 b0b5 push {r4,r5,r7,lr}
0x10006fc2 02af add r7,sp,8
0x10006fc4 0448 ldr r0,[0x10006fd8]
0x10006fc6 d0e90c45 ldrd r4,[r0,0x30]
0x10006fca 0020 movs r0,0
0x10006fcc fff7a6ff bl 0x10006f1c
0x10006fd0 0019 adds r0,r0,r4
0x10006fd2 6941 adcs r1,r5
0x10006fd4 b0bd pop {r4,pc}
如果在那里取消引用 r7
,则会得到一对指针,其中第二个是 lr
,第一个是调用函数的 r7
,允许你重复这个过程,直到你到达堆栈的底部。
根据您发布的程序集来看,您正在查看的代码库没有那个。这意味着获取返回地址的唯一方法与代码本身的方法相同:向前执行每条指令并解析/解释它们,直到到达加载到 pc
中的内容。这当然是不完美的,因为您的调用堆栈中可能有些函数永远不会返回,但您对此无能为力。
向后搜索可能很诱人,虽然您可以使用启发式方法并可能用它获得相当合理的结果,但这比向前搜索更不可靠,因为您完全无法判断是否到达通过从前一条指令向前或从其他地方显式跳转到 X 来寻址。