了解ARM ASM中的函数调用

Understanding function calls in ARM ASM

本文关键字:函数调用 ASM ARM 了解      更新时间:2023-10-16

我正在拆解本机Android库(armeabi-v7a),这些库很可能是从C++或C代码创建的,并被剥离。目标是创建一个函数调用树,以便以后进行分析。

我在理解分解后的输出时遇到问题。

arm-linux-androideabi-objdump -d libledger.so > output.txt 创建的以下汇编程序片段为例

00014988 <_ZSt10__pop_heapIN9__gnu_cxx17__normal_iteratorIPSsSt6vectorISsSaISsEEEENS0_5__ops15_Iter_less_iterEEvT_S9_S9_T0_>: 
   ...
   149e0:   4620        mov r0, r4
   149e2:   f030 fadd   bl  44fa0 <_ZNK10__cxxabiv120__si_class_type_info11__do_upcastEPKNS_17__class_type_infoEPKvRNS1_15__upcast_resultE+0x2a7ec>
   149e6:   e7ff        b.n 149e8 <_ZSt10__pop_heapIN9__gnu_cxx17__normal_iteratorIPSsSt6vectorISsSaISsEEEENS0_5__ops15_Iter_less_iterEEvT_S9_S9_T0_+0x60>
   149e8:   4628        mov r0, r5
   149ea:   f030 fad9   bl  44fa0 <_ZNK10__cxxabiv120__si_class_type_info11__do_upcastEPKNS_17__class_type_infoEPKvRNS1_15__upcast_resultE+0x2a7ec>
   149ee:   f004 fe55   bl  1969c <__cxa_end_cleanup>
   149f2:   bf00        nop
   149f4:   b154        cbz r4, 14a0c <_ZSt16__introsort_loopIN9__gnu_cxx17__normal_iteratorIPSsSt6vectorISsSaISsEEEEiNS0_5__ops15_Iter_less_iterEEvT_S9_T0_T1_+0x14>
   149f6:   0005        movs    r5, r0
000149f8 <_ZSt16__introsort_loopIN9__gnu_cxx17__normal_iteratorIPSsSt6vectorISsSaISsEEEEiNS0_5__ops15_Iter_less_iterEEvT_S9_T0_T1_>:
   149f8:   e92d 4ff0   stmdb   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
   149fc:   1d07        adds    r7, r0, #4
   149fe:   b085        sub sp, #20
   14a00:   4604        mov r4, r0
   14a02:   4690        mov r8, r2
   14a04:   460e        mov r6, r1
   14a06:   1b35        subs    r5, r6, r4
   14a08:   2d43        cmp r5, #67 ; 0x43
   14a0a:   f340 8095   ble.w   14b38 <_ZSt16__introsort_loopIN9__gnu_cxx17__normal_iteratorIPSsSt6vectorISsSaISsEEEEiNS0_5__ops15_Iter_less_iterEEvT_S9_T0_T1_+0x140>
   14a0e:   f1b8 0f00   cmp.w   r8, #0
   14a12:   d13f        bne.n   14a94 <_ZSt16__introsort_loopIN9__gnu_cxx17__normal_iteratorIPSsSt6vectorISsSaISsEEEEiNS0_5__ops15_Iter_less_iterEEvT_S9_T0_T1_+0x9c>
   14a14:   10ad        asrs    r5, r5, #2
   14a16:   4b4a        ldr r3, [pc, #296]  ; (14b40 <_ZSt16__introsort_loopIN9__gnu_cxx17__normal_iteratorIPSsSt6vectorISsSaISsEEEEiNS0_5__ops15_Iter_less_iterEEvT_S9_T0_T1_+0x148>)
   14a18:   1eaf        subs    r7, r5, #2
   14a1a:   f10d 090c   add.w   r9, sp, #12

在这里描述的分支指令中,BL家族的指令最类似于函数调用,5.3子程序调用中也暗示了这一点。然后还有通过SWI的系统调用。然而,我想到了一些问题:

  1. 我能确定对于BL指令,要跳转到的标签总是指向C函数的开头吗?这意味着它们的行为就像调用一个C函数
  2. C函数调用还使用了哪些指令
  3. 在上面的例子中,这意味着在149e2:有一个函数调用。不过我不理解<XY+0x2a7ec>部分。这是否意味着。。。
    • 被调用的函数从addressoff(XY) + 0x2a7ec开始,但不在.dynsym表中,因此没有可供反汇编程序参考的友好名称
    • 这是否意味着XY在调用时会有巨大的偏移量
    • 或者两者都有可能
  4. 如何识别函数的开始?在我看来,像00014988 <...>这样地址完整的部分是函数。尽管我担心这只适用于动态符号表中的函数,这将解释上面的巨大偏移
  5. 149f4也有一个分支,这次是通过CBZ。这看起来也像是对另一个函数的函数调用,同样带有偏移量。当00014988000149f8都是函数时,此CBZ调用将直接跳转到函数中,而不是一开始。这是什么意思

对于BL指令,我能确定要跳转到的标签总是指向C函数的开头吗?这意味着它们的行为就像调用一个C函数。

是的,大多数时候分支都会到函数的开头。

C函数调用还使用了哪些指令?

任何可以更改程序计数器的内容
我看到了BX, LDR PC,POP的说明。

在上面的例子中,这意味着在149e2:有一个函数调用。不过我不理解这个部分。这是否意味着
被调用的函数从addressoff(XY)+0x2a7ec开始,但不在.dynsym表中,因此没有可供反汇编程序参考的友好名称?

并非每个地址都与来源行号对齐。偏移量是与映射文件中的已知符号(通常是函数名)的距离。

这是否意味着XY被称为具有巨大的偏移量?

没有。分支的目的地位于公共符号的偏移处。在许多情况下,许多函数都不是公共的,因此它们被引用到地图文件中最近的公共符号。

或者两者都有可能?

请参见上文。

如何识别函数的开始?

没有一般规则
这是一个过程:

  1. 打开列表文件(包含汇编语言)
  2. 在汇编语言列表中查找偏移量
  3. 或者找到资金,然后添加偏移量并查找该地址

在149f4也有一个分支,这次是通过CBZ。这看起来也像是对另一个函数的函数调用,同样带有偏移量。当00014988和000149f8都是函数时,此CBZ调用将直接跳转到函数中,而不是一开始。这是什么意思?

CBZ不是子程序函数调用。它是另一个地址的分支。预计不会返回。

分支与链接(BL)和分支与交换(BX)指令将LR寄存器设置为返回地址(通常是BLBX之后的下一条指令)。为了返回,LR寄存器中的值被复制到程序计数器(PC)寄存器中,导致执行转移到LR中的地址。这是函数或子程序调用的返回。