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

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

本系列文章共三篇。本文是第二篇,讲 ELF 文件如何存放和使用 arm ehabi。关键词:.arm.exidx,.arm.extab。

ELF .ARM.exidx 和 .ARM.extab 的位置

Section角度:很久以前,readelf -S 时候一直不理解这两个 section 是做什么的,占空间,放的不是汇编,IDA 打开,里面也是一团意义不明的 data,总觉得没什么用。

1
2
3
4
5
6
7
8
9
10
11
  ~ readelf -S libnative-lib.so
There are 25 section headers, starting at offset 0x1a130:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .note.android.ide NOTE            00000134 000134 000098 00   A  0   0  4
  [ 2] .note.gnu.build-i NOTE            000001cc 0001cc 000024 00   A  0   0  4
  .......
  [14] .ARM.exidx        ARM_EXIDX       00013eb8 013eb8 000e80 08  AL 13   0  4
  [15] .ARM.extab        PROGBITS        00014d38 014d38 001014 00   A  0   0  4

Segment角度:readelf -l 时候,EXIDX 就是 .ARM.exidx,感觉还是有点用的,但仍然意义不明;不能直接确认 .ARM.extab 的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
readelf -l libnative-lib.so

Elf file type is DYN (Shared object file)
Entry point 0x0
There are 8 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00000034 0x00000034 0x00100 0x00100 R   0x4
  LOAD           0x000000 0x00000000 0x00000000 0x1802e 0x1802e R E 0x1000
  LOAD           0x018570 0x00019570 0x00019570 0x01aa0 0x01cb9 RW  0x1000
  DYNAMIC        0x019c94 0x0001ac94 0x0001ac94 0x00110 0x00110 RW  0x4
  NOTE           0x000134 0x00000134 0x00000134 0x000bc 0x000bc R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  EXIDX          0x013eb8 0x00013eb8 0x00013eb8 0x00e80 0x00e80 R   0x4
  GNU_RELRO      0x018570 0x00019570 0x00019570 0x01a90 0x01a90 RW  0x4

好了不废话了,本文只针对 shared_library 和 executable 的 ehabi 解析,不支持 relocatable;因为 relocatable 拥有大量的 .ARM.exidx section 和重定位,有点复杂。

本文参考:

.ARM.exidx 结构

这个 section 连续存放Entry,视为一个Entry数组。先要处理大小端问题,处理好后,每个 Entry 由两个 uint16 组成,相当于如下 struct:

1
2
3
4
struct EHEntry {
  uint32_t Offset;
  uint32_t World1;
};

EHEntry.Offset 意义是函数起始偏移。最高 bit 一定是 0,结合当前偏移(当前 pc )进行使用 prel31 解码,得到 uint64_t。

格式为:

1
2
| 31----24 | 23----16 | 15-----8 | 7------0 |
| 0XXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX |

EHEntry.Word1 有三种情况:

  • EHEntry.Word1 == 1 ,表示 CannotUnwind

    1
    
    | 00000000 | 00000000 | 00000000 | 00000001 |
    
  • 最高 bit 为 1,则[31:24]必须为 0b10000000(其实是 personality 为 0,属于 inline compact model),余下 X、Y、Z, 3 个 byte 表示字节码。

    1
    2
    
    | 31----24 | 23----16 | 15-----8 | 7------0 |
    | 10000000 | XXXXXXXX | YYYYYYYY | ZZZZZZZZ |
    
  • 最高 bit 为 0,则整个 Word1 使用 prel31 解码,得到 uint64_t,指向真正的数据。必然会落在 .ARM.extab 里。

    1
    2
    
    | 31----24 | 23----16 | 15-----8 | 7------0 |
    | 0XXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX |
    

prel31 解码

这个东西就是个计算方式,根据当前的绝对偏移(uint32),与当前的内容(uint32)进行运算,求出绝对偏移(uint64)。

对于 ELF 文件,我们可以假想它基址为 0,从而实现解析;

对于内存中的 ELF 片段,可以通过这个计算,根据当前位置寻找到附近的另一个位置,从而避免重定位;

下图代码中,Address 表示当前内容,Place 表示绝对偏移。

1
2
3
4
5
6
static uint64_t PREL31(uint32_t Address, uint32_t Place) {
    uint64_t Location = Address & 0x7fffffff;
    if (Location & 0x04000000)
        Location |= (uint64_t) ~0x7fffffff;
    return Location + Place;
}

.ARM.extab 结构

.ARM.extab 作为 .ARM.exidx 的附属存在,存放数据,但无法直接找到每段数据的入口。入口需要借助上文 Entry.Word1,当 Entry.Word1 的最高 bit 为 0 时,prel31解码后一定会指向 .ARM.extab 的内容,这就是入口。

名词解释:personality ——特性,可能没有中文概念。

先读出第一个 uint32_t,进行初步解析,再根据情况进行进一步解析,有以下的情况:

  • 最高 bit 为 0,表示 generic personality,使用 prel31 解码,使用指向的函数进行 unwind。

    1
    2
    
    | 31----24 | 23----16 | 15-----8 | 7------0 |
    | 0XXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX |
    
  • 最高 bit 为 1,表示 arm compact personality[31:28]必须为 0b1000[27:24] 有且仅有有0、1、2三种情况。

    • 0: inline compact model,X、Y、Z, 3 个 byte 表示字节码。

      1
      2
      
      | 31----24 | 23----16 | 15-----8 | 7------0 |
      | 10000000 | XXXXXXXX | YYYYYYYY | ZZZZZZZZ |
      
    • 1或者2: [23:16] 表示 more_word(uint_8),表示剩余字节码个数,后面的都是字节码

      1
      2
      3
      
      | 31----24 | 23----16 | 15-----8 | 7------0 |
      | 10000001 | MOREWORD | ........ | ........ |
      | 10000010 | MOREWORD | ........ | ........ |
      

字节码的反汇编

根据上文,我们可以得到每一处 Entry 及其 unwind方式。我们关心的是使用字节码进行 unwind (即 personality 为0、1、2)的情况,经过解析可以得到 uint8_t[N] 的字节码 ,解析方式在 “Table 4, ARM-defined frame-unwinding instructions” 文件章节。

参考 llvm-readelf 的实现,它的可读性最好,代码在 https://github.com/llvm/llvm-project/blob/master/llvm/tools/llvm-readobj/ARMEHABIPrinter.h ,其中有大量OpcodeDecoder::Decode_XXXXX 函数可以抄。

纯体力活,没什么好说的,官方表格如下:

InstructionExplanation
00xxxxxxvsp = vsp + (xxxxxx << 2) + 4. Covers range 0x04-0x100 inclusive
01xxxxxxvsp = vsp – (xxxxxx << 2) - 4. Covers range 0x04-0x100 inclusive
10000000 00000000Refuse to unwind (for example, out of a cleanup) (see remark a)
1000iiii iiiiiiii (i not a ll 0)Pop up to 12 integer registers under masks {r15-r12}, {r11-r4} (see remark b)
1001nnnn ( nnnn != 13,15)Set vsp = r[nnnn]
10011101Reserved as prefix for ARM register to register moves
10011111Reserved as prefix for Intel Wireless MMX register to register moves
10100nnnPop r4-r[4+nnn]
10101nnnPop r4-r[4+nnn], r14
10110000Finish (see remark c)
10110001 00000000Spare (see remark f)
10110001 0000iiii ( i not all 0)Pop integer registers under mask {r3, r2, r1, r0}
10110001 xxxxyyyySpare (xxxx != 0000)
10110010 uleb128vsp = vsp + 0x204+ (uleb128 << 2) (for vsp increments of 0x104-0x200, use 00xxxxxx twice)
10110011 ssssccccPop VFP double-precision registers D[ssss]-D[ssss+cccc] saved (as if) by FSTMFDX (see remark d)
101101nnSpare (was Pop FPA)
10111nnnPop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by FSTMFDX (seeremark d)
11000nnn (nnn != 6,7)Intel Wireless MMX pop wR[10]-wR[10+nnn]
11000110 ssssccccIntel Wireless MMX pop wR[ssss]-wR[ssss+cccc] (see remark e)
11000111 00000000Spare
11000111 0000iiiiIntel Wireless MMX pop wCGR registers under mask {wCGR3,2,1,0}
11000111 xxxxyyyySpare (xxxx != 0000)
11001000 ssssccccPop VFP double precision registers D[16+ssss]-D[16+ssss+cccc] saved (as if) by VPUSH (see remarks d,e)
11001001 ssssccccPop VFP double precision registers D[ssss]-D[ssss+cccc] saved (as if) by VPUSH (see remark d)
11001yyySpare (yyy != 000, 001)
11010nnnPop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by VPUSH (seeremark d)
11xxxyyySpare (xxx != 000, 001, 010)

实战:用 python 写一个 ehabi 的 parser

很遗憾,pyelftools 并未实现解析 arm ehabi 的功能,要有这个功能,我也懒得写本文了。。。

我为什么要写解析的功能?一方面因为 pyelftools 平时经常用,想为它做一些贡献;另一方面,我计划写一个 ida-arm-unwind 的插件,缺乏一个 python 库帮我完成解析,在 pyelftools 上补充功能是最合适的。

pull reqeust:https://github.com/eliben/pyelftools/pull/328

merge commit:https://github.com/eliben/pyelftools/commit/ee0facee32ae5fc91709c93f9a57a9a7683a3315

花了不少时间,写了将近 1000 行代码,实现得也比较优雅,大概有如下的功能:

  • 加了 has_ehabi_infos 和 get_ehabi_infos 两个 API,返回 List[EHABIInfo]
  • 添加 class EHABIInfo,提供 num_entry 和 get_entry(i) 两个 API,返回 EHABIEntry
  • 添加 class EHABIEntry 及其子类,描述每个 unwind 条目,描述函数偏移和字节码,也可以反汇编

看一下效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  pyelftools git:(master)  scripts/readelf.py -au test/testfiles_for_unittests/arm_exidx_test.so | head -n 20

Unwind section '.ARM.exidx' at offset 0x639d8 contains 1933 entries

Entry 0:
    Function offset 0x34610: @0x69544
    Compact model index: 1
    0x97 ; vsp = r7
    0x41 ; vsp = vsp - 8
    0x84 0x0d ; pop {r4, r6, r7, lr}
    0xb0 ; finish
    0xb0 ; finish

Entry 1:
    Function offset 0x34640: @0x6bef4
    Compact model index: 1
    0x97 ; vsp = r7
    0x41 ; vsp = vsp - 8
    0x84 0x0b ; pop {r4, r5, r7, lr}
    0xb0 ; finish
    0xb0 ; finish

总结

本文讲了 ELF 里 arm ehabi 的存放和使用。

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值