ARM汇编学习笔记(栈回溯-1)

https://www.leadroyal.cn/p/1125/

本系列文章共三篇。本文是第一篇,讲一些栈回溯的背景,不涉及技术细节。关键词:arm unwind,ehabi,arm stacktrace。

起因

故事要从几个月前的一个 arm crash 说起,把 crash 交给新来的小朋友看,他说 IDA 里显示的栈回溯和logcat里显示的栈回溯是不一致的,问我为什么。

我说一直是logcat里看的,是正确的;那么 ida 里只有一层栈回溯肯定是错的,但我却解释不来原因,于是有了本文和一系列研究。

平时讨论的函数调用栈结构

从小老师就教育我们,函数开头一般是这三句话,用于保存堆栈,开辟新的栈空间:

1
2
3
push ebp
mov ebp, esp
sub esp, 0x100

在这种设定下,栈回溯变得非常简单,ebp 就是栈帧,ebp 附近是上一个栈帧,再附近是返回地址。网上相关的文章一搜一大把,这里就不多讲了,找一张网图凑合一下。

arm 的栈结构

我们随便找个 /system/lib/libc.so,再随便编译一个 so,随便找几个函数看一下,发现和x86的不大一样。

1
2
3
4
5
6
7
8
.text:000233C0                 PUSH.W          {R4-R8,LR}
.text:000233C4                 SUB             SP, SP, #8
.text:000233C6                 LDR             R4, [SP,#0x20+arg_8]
.text:000233C8                 MOV             R5, R1
...
.text:00023460                 MOV             R0, R4
.text:00023462                 ADD             SP, SP, #8
.text:00023464                 POP.W           {R4-R8,PC}
1
2
3
4
5
6
7
.text:00023A94                 PUSH.W          {R4-R9,LR}
.text:00023A98                 SUB             SP, SP, #4
.text:00023A9A                 MOV             R8, R1
.text:00023A9C                 MOV             R5, R0
...
.text:00023B12                 ADD             SP, SP, #4
.text:00023B14                 POP.W           {R4-R9,PC}
1
2
3
4
5
6
7
8
.text:0003477C                 PUSH            {R7,LR}
.text:0003477E                 MOV             R7, SP
.text:00034780                 SUB             SP, SP, #0x28
.text:00034782                 LDR             R2, =(__stack_chk_guard_ptr - 0x34788)
...
.text:000347CE                 MOVS            R0, #0
.text:000347D0                 ADD             SP, SP, #0x28
.text:000347D2                 POP             {R7,PC}
1
2
3
4
5
6
7
.text:000138C4                 PUSH            {R4,R5,R7,LR}
.text:000138C6                 ADD             R7, SP, #8
.text:000138C8                 SUB             SP, SP, #0x20
.text:000138CA                 LDR             R4, =(__stack_chk_guard_ptr - 0x138D0)
...
.text:000138F4                 ADDEQ           SP, SP, #0x20
.text:000138F6                 POPEQ           {R4,R5,R7,PC}

观察这几组汇编,前两段 sp 的内容并没有被保存到任意一个寄存器里,但它可以被正确栈回溯,暗示栈回溯信息不在这段汇编里;后两段,把 sp 放到 r7 里,把 sp+8 放到 r7 里,有点像栈帧的感觉,并且函数内也没有覆盖掉 r7 的内容,有点 x86 的感觉。

查阅资料,随着时代发展,arm 有两种 unwind 方式:

  1. 一种是古老的,和 x86 类似的(目前没有找到样例,可能在某种编译选项下存在),使用专用的 fp 寄存器保存原先的 spfp 在函数内禁止被改写,thumb 模式下使用 r7 作为 fp,arm 模式下使用 r11 作为 fp
  2. 另一种是流行的, arm 特有的(目前绝大部分都使用这种方式),遵从 eabi 里的 ehabi 标准,即 exception handler abi,定义了一套专属的标准。简而言之就是对每个函数分配自己专用的字节码,解释执行,从而实现栈回溯。

使用readelf -u可以查看,字节码长这样:

1
2
3
4
5
6
7
0x9a8c <__cxa_end_cleanup_impl>: @0x14f28
  Compact model index: 1
  0x97      vsp = r7
  0x41      vsp = vsp - 8
  0x84 0x0b pop {r4, r5, r7, r14}
  0xb0      finish
  0xb0      finish

arm ehabi

讲了这么多,终于引出本系列的重点:arm ehabi。

官方文档,复杂但权威:https://developer.arm.com/documentation/ihi0038/b/
看雪有篇不错的文档:原创andorid native栈回溯原理分析与思考

  • 使用 readelf -u 可以打印相关信息,也可以使用pyelftools里的 readelf.py -au 打印出来(而且这个功能是我写的)。
  • 千万不要使用 llvm-readelf -u,因为它有 bug,只支持 .o 文件。

名词解释

名词解释
stack unwind意思就是栈回溯。
abi(application binary interface)二进制应用接口,相当于标准和规范
arm eabiarm 很多规范的合集,包括 AADWARF、AAELF等,也包括 CLIBABI、CPPABI、【EHABI】
arm ehabiarm 的 exception handler abi
exception handler异常处理,既包括 crash 时的栈回溯,也包括 c++ 里的异常处理。
arm exception handler index table指 .ARM.exidx,存放函数offset、简单的handler 的数据、复杂handler的索引。
arm exception handler table指 .ARM.extab,存放复杂 handler 的数据。

和平时逆向相关的,有两部分内容,有个大致认知就行:

  1. 数据存放。在 ELF 文件里肯定存放了信息,指导如何进行栈回溯,.ARM.exidx 和 .ARM.extab 就是做这件事情的;
  2. 数据使用。每个函数 unwind 时需要解析 exception handler table,需要解释执行字节码,这个功能有时会由操作系统完成(例如 crash 的时候),有时会由应用程序自己完成(例如使用写代码主动进行栈回溯)。

总结

本文讲的是背景,没什么技术细节,第二篇讲文件格式。

第一篇指路:https://www.leadroyal.cn/p/1125
第二篇指路:https://www.leadroyal.cn/p/1131
第三篇指路:https://www.leadroyal.cn/p/1135

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值