首页 > 编程知识 正文

arm调用栈回溯

时间:2023-05-06 19:46:36 阅读:177631 作者:2060

arm平台的调用堆栈回溯(backtrace )。

title: arm平台的调用堆栈回溯(backtrace )。

date :2018-09-1916336007336047

tags:

我来介绍一下

arm平台的调用栈与x86平台的调用栈基本相同,但略有不同。 主要是堆栈帧的堆栈内容和传输方法不同这一点。 不同的arm平台程序采用不同的编译选项,程序运行时的堆栈帧也不同。 某些工具在追溯arm的调用堆栈时可能无法后退。 例如,当gdb使用bt查看core dump文件调用栈时,它可能会变为Backtrace stoped。 这可能是因为堆栈区域的堆栈顺序。 如果工具无法回溯,则必须通过人工组合汇编代码来回溯堆栈,或者使用unwind进行回溯。

arm堆栈帧结构

通常,arm的调用栈的大致结构与x86相同,都是从高地址扩展到低地址。 上图是内存的分布之一。

pc、lr、sp、fp是处理器的寄存器,其含义如下。

pc,程序计数器,程序计数器。 当前执行中的指令保存在pc寄存器中

fp,即帧指针,帧指针。 通常指向表示函数堆栈开始位置的函数堆栈帧的底部。

sp,stack pointer,堆栈指针。 指向当前堆栈区域的最上面的位置,推送和pop会一起移动。

lr,link register。 函数调用会将函数返回后要执行的下一个指令放入lr中,并与x86体系结构中的返回地址相对应。

调用堆栈从上级地址向下级地址增加,函数调用时,将pc、lr、ip、fp寄存器分别推入堆栈,移动sp指针,为当前程序打开堆栈空间。

arm官方手册的说明如下。

根据当前的处理器模式,一个arm程序中随时会有15个通用寄存器。 分别是r0-r12、sp和lr。

sp (或r13 )是堆栈指针。 C编译器和c编译器始终使用sp作为堆栈指针。 在Thumb-2中,sp被严格定义为堆栈指针,因此对堆栈操作没有帮助,许多使用sp的指令会产生不可预测的结果。 建议不要将sp用作通用寄存器。

在用户模式下,lr (或r14 )用作链接寄存器(lr ),用于存储调用子程序时的返回地址。 如果返回地址存储在堆栈中,也可以将r14用作通用寄存器。

在异常处理模式中,lr存储异常的返回地址; 如果在异常中执行子程序调用,lr将存储子程序的返回地址。 如果返回地址存储在堆栈中,则可以将lr用作通用寄存器。

除了官方手册中记载的sp、lr寄存器外,通常r12还作为fp寄存器发挥作用。 fp寄存器对程序的执行没有帮助,主要用于堆栈帧的回溯。 因为sp始终指向的堆栈顶部在fp上知道上一个堆栈帧的开头位置。

与上图中的调用堆栈对应的汇编代码如下:

第8514行将当前sp保存在ip中。 ip是用于在函数间分析和调用时临时存储数据的通用寄存器,通常为r12。

8518行从右到左依次堆叠4个寄存器。

851c行从保存的ip中减去4,得到当前被调用函数的fp地址,即堆栈中pc的位置。

在820行中减少8个sp,在堆栈区域制作8字节的大小,存储局部的航班命令。

00008514 :

8514: e1a0c00d mov ip,sp

8518: e92dd800 push {fp,ip,lr,pc}

851c: e24cb004 sub fp,ip,#4

820:e24DD008子sp、sp、#8

8524: e3a03000 mov r3,#0

8528: e50b3010 str r3,[fp,#-16]

852c: e30805dc movw r0,#34268;0x85dc

8530: e3400000 movt r0,#0

8534: EB fff F9 d bl 83b0puts @ PLT

8538: e51b3010 ldr r3,[fp,#-16]

853c: e12fff33 blx r3

8540: e3a03000 mov r3,#0

8544: e1a00003 mov r0,r3

848:e24BD00c子sp、fp、#12

854c: e89da800 ldm sp,{fp,sp,pc}

-mapcs-frame编译选项

在第一节中,程序堆栈的寄存器为{fp,ip,lr,PC}个,这是在gcc的带-mapcs-frame的编译选项下编译的。 gcc的默认参数是mno-apcs-frame。 有关此选项的信息,请参阅,

generateastackframethatiscompliantwiththearmprocedurecallstandardforallfunct

ions, even if this is not strictly necessary for correct execution of the code. Specifying -fomit-frame-pointer with this option causes the stack frames not to be generated for leaf functions. The default is -mno-apcs-frame. This option is deprecated.

也就是说,该编译选项会产生(push {fp, ip, lr, pc}),保证栈帧的格式。如果没有-mapcs-frame,则不保证帧格式和当前帧格式,GCC生成的指令可能会发生各种变化。在AAPCS发布之后[附录1],1993年的APCS就已经太旧了,所以

在gcc5.0之后,该选项已经被废弃。gcc5.0的更新记录写到:

The options -mapcs, -mapcs-frame, -mtpcs-frame and -mtpcs-leaf-frame which are only applicable to the old ABI have been deprecated.

至于该参数在将来是否会被gcc移除,那就不知道了。

将第一节中的程序重新使用默认编译选项,用4.7版本的gcc编译,结果如下。这时,fp还在,调用栈push了fp和lr到栈空间,新的fp指向了lr在栈中的位置。

00008514 :

8514: e92d4800 push {fp, lr}

8518: e28db004 add fp, sp, #4

851c: e24dd008 sub sp, sp, #8

8520: e3a03000 mov r3, #0

8524: e50b3008 str r3, [fp, #-8]

8528: e30805d4 movw r0, #34260 ; 0x85d4

852c: e3400000 movt r0, #0

8530: ebffff9e bl 83b0 puts@plt

8534: e51b3008 ldr r3, [fp, #-8]

8538: e12fff33 blx r3

853c: e3a03000 mov r3, #0

8540: e1a00003 mov r0, r3

8544: e24bd004 sub sp, fp, #4

8548: e8bd8800 pop {fp, pc}

0000854c:

854c: e92d4800 push {fp, lr}

8550: e28db004 add fp, sp, #4

8554: ebffffee bl 8514

8558: e1a00003 mov r0, r3

855c: e8bd8800 pop {fp, pc}

使用gcc-7.3默认选项编译结果如下,fp已经不在了,虽然这里仍然可能通过r7得知上个栈帧的位置,但是已经没法使用fp获取栈帧了。此时是不保证栈帧保存在栈中的。所以依赖栈帧内容进行恢复已经非常不可靠。那么既然无法依赖fp,那该怎么进行栈帧回溯呢,gnu说使用unwind方法回溯,这节暂时不会介绍unwind方法。

000103c8 :

103c8: b580 push {r7, lr}

103ca: b082 sub sp, #8

103cc: af00 add r7, sp, #0

103ce: 2300 movs r3, #0

103d0: 607b str r3, [r7, #4]

103d2: f240 4048 movw r0, #1096 ; 0x448

103d6: f2c0 0001 movt r0, #1

103da: f7ff ef7e blx 102d8 puts@plt

103de: 687b ldr r3, [r7, #4]

103e0: 4798 blx r3

103e2: 2300 movs r3, #0

103e4: 4618 mov r0, r3

103e6: 3708 adds r7, #8

103e8: 46bd mov sp, r7

103ea: bd80 pop {r7, pc}

000103ec:

103ec: b580 push {r7, lr}

103ee: af00 add r7, sp, #0

103f0: f7ff ffea bl 103c8

103f4: 2300 movs r3, #0

103f6: 4618 mov r0, r3

103f8: bd80 pop {r7, pc}

使用栈帧进行回溯

这一节使用gcc4.7版本,默认编译选项编译出来的程序,演示调用栈回溯。该编译选项下,压栈的寄存器为{fp, lr}。

下边的内容是一段core dump中的寄存器和调用栈,本节将对这段内容进行回溯。

Reg: r9, Val = 0xf7578000; Reg: r10, Val = 0x00000001;

Reg: fp, Val = 0x827d3104; Reg: ip, Val = 0xf7578ae0;

Reg: sp, Val = 0x827d30e0; Reg: lr, Val = 0xf7549990;

Reg: pc, Val = 0xf7548c20; Reg: cpsr, Val = 0x60000210;

0x827d30e0: 0x00000031 0x827d31a0 0x00000001 0xd5dff060

0x827d30f0: 0xd5e0e6b1 0xd5dec134 0xf7578000 0xf7577c40

0x827d3100: 0x827d313c 0xf7549990

0x827d3140: 0x00000000 0xd5dec104 0xf7568514 0x00000002

0x827d3150: 0xd5dec104 0xf7577c40 0xf7577c38 0xd5de9224

0x827d3160: 0x827d31a0 0xf757a084 0xf7577c40 0xd5df6dd4

0x827d3170: 0x827d3194 0x00000001 0xd5e0e678 0xd5dec104

0x827d3180: 0xd5de9224 0xf7568548 0x00000000 0xf7568550

当前sp地址为0x827d30e0,fp地址为0x827d3104,从而得知当前函数frame0的栈帧。fp指向的地址0x827d3104为frame1的lr,0x827d3100为上一个栈帧的fp。

0x827d30e0: 0x00000031 0x827d31a0 0x00000001 0xd5dff060

0x827d30f0: 0xd5e0e6b1 0xd5dec134 0xf7578000 0xf7577c40

0x827d3100: 0x827d313c(fp) 0xf7549990(lr)

从frame0的fp地址0x827d313c可知,frame1的调用栈起始地址,去掉frame0的内容,得到frame1的栈帧。

0x827d312c 0xf7530c14

0x827d3110: 0xd5dff060 0x0000002c 0xd5e0e6b1 0xd5e0e6b1

0x827d3120: 0x00000001 0xd5e0e6b1 0xd5dff060 0xd5dec134

0x827d3130: 0xf7578000 0xf7577c40 0x827d3194(fp) 0xf754ad0c(lr)

依次类推,依次得到frame2、frame3...的栈帧。

当汇编代码的函数调用使用push {fp, ip, lr, pc}时,则上一个栈帧的fp2在当前栈帧的(fp - #4)位置。栈帧的回溯要结合程序的汇编代码具体分析,有可能程序并不使用fp指针,也有可能栈中根本没有保存fp。

unwind方法回溯

TODO

附录1-函数调用标准缩略语

PCS Procedure Call Standard.

AAPCS Procedure Call Standard for the ARM Architecture (this standard).

APCS ARM Procedure Call Standard (obsolete).

TPCS Thumb Procedure Call Standard (obsolete).

ATPCS ARM-Thumb Procedure Call Standard (precursor to this standar

参考资料

ARM 体系结构概述

Procedure Call Standard for the ARM® Architecture

GCC 5 Release Series

转载于:https://www.cnblogs.com/your2b/p/9698216.html

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。

  •  标签:  
  • arm