美文网首页
干货 | ARMv8架构下程序运行时栈帧布局

干货 | ARMv8架构下程序运行时栈帧布局

作者: 王中狗 | 来源:发表于2017-12-08 14:28 被阅读0次

结合ARM相关文档和在飞腾机器上使用gdb调试实际程序来研究ARM的指令和运行时栈帧布局。主要参考了三篇文档。

1. Procedure Call Standard for the ARM 64-bit Architecture。参考其中的过程调用标准和运行时栈帧布局。

2. ARMv8 Instruction Set Overview。参考其中的指令概述。

3. ARM Compiler Migration and Compatibility Guide。参考其中ARM汇编与GNU汇编格式的比较。

在文章1中,对ARM架构下运行时栈帧布局如图1所示。

图1 ARM运行时栈帧布局

其中,FP(x29)寄存器保存栈帧地址,LR(x30)保存当前过程的返回地址。栈是从高地址向低地址生长。为验证图中的布局形式,在飞腾机器上安装gdb,通过调试一个示例程序,来研究ARM的指令特点和栈帧结构。示例程序如图2所示,函数TestParam定义了两个局部变量,分别为数组和标量类型。

图2 示例程序

使用gdb调试图2中代码所产生的程序,然后再反汇编函数TestParam,可以得到如下结果。

=> 0x0000000000400680 <+0>:stpx29, x30, [sp,#-64]!

0x0000000000400684 <+4>:movx29, sp

0x0000000000400688 <+8>:strw0, [x29,#28]

0x000000000040068c <+12>:strw1, [x29,#24]

0x0000000000400690 <+16>:strw2, [x29,#20]

0x0000000000400694 <+20>:strw3, [x29,#16]

0x0000000000400698 <+24>:adrpx0, 0x411000 <__libc_start_main@got.plt>

0x000000000040069c <+28>:addx0, x0, #0x38

0x00000000004006a0 <+32>:ldrx1, [x0]

0x00000000004006a4 <+36>:strx1, [x29,#56]

0x00000000004006a8 <+40>:movx1, #0x0                   // #0

0x00000000004006ac <+44>:ldrw1, [x29,#28]

0x00000000004006b0 <+48>:ldrw0, [x29,#24]

0x00000000004006b4 <+52>:addw0, w1, w0

0x00000000004006b8 <+56>:strw0, [x29,#48]

0x00000000004006bc <+60>:ldrw1, [x29,#20]

0x00000000004006c0 <+64>:ldrw0, [x29,#16]

0x00000000004006c4 <+68>:subw0, w1, w0

0x00000000004006c8 <+72>:strw0, [x29,#52]

0x00000000004006cc <+76>:ldrw1, [x29,#20]

0x00000000004006d0 <+80>:ldrw0, [x29,#16]

0x00000000004006d4 <+84>:addw0, w1, w0

0x00000000004006d8 <+88>:strw0, [x29,#44]

0x00000000004006dc <+92>:ldrw1, [x29,#48]

0x00000000004006e0 <+96>:ldrw2, [x29,#52]

0x00000000004006e4 <+100>:adrpx0, 0x400000

0x00000000004006e8 <+104>:addx0, x0, #0x808

0x00000000004006ec <+108>:ldrw3, [x29,#44]

0x00000000004006f0 <+112>:bl0x400530 

0x00000000004006f4 <+116>:adrpx0, 0x411000 <__libc_start_main@got.plt>

0x00000000004006f8 <+120>:addx0, x0, #0x38

0x00000000004006fc <+124>:ldrx1, [x29,#56]

0x0000000000400700 <+128>:ldrx0, [x0]

0x0000000000400704 <+132>:eorx0, x1, x0

0x0000000000400708 <+136>:cmpx0, xzr

0x000000000040070c <+140>:b.eq0x400714 

0x0000000000400710 <+144>:bl0x400500 <__stack_chk_fail@plt>

0x0000000000400714 <+148>:ldpx29, x30, [sp],#64

0x0000000000400718 <+152>:ret

该程序在运行时的栈帧如图3所示。

以下是反汇编指令的解释以及其对栈中内容的影响。

=> 0x0000000000400680 <+0>:stpx29, x30, [sp,#-64]!

0x0000000000400684 <+4>:movx29, sp

指令stp把一对值x29和x30放到SP-64的地址(7ffffff370)中去。此时的SP是旧SP,其值为7ffffff3b0。值得注意的是,这条语句同时完成了SP的自减运算,也就是执行之后,SP的值也变成了7ffffff370。第二条指令把FP的值设置为与SP的值相同。

0x0000000000400688 <+8>:strw0, [x29,#28]

0x000000000040068c <+12>:strw1, [x29,#24]

0x0000000000400690 <+16>:strw2, [x29,#20]

0x0000000000400694 <+20>:strw3, [x29,#16]

这4条指令把保存在参数传递寄存器中的4个参数保存到栈中。如图2中的w0, w1, w2, w3所示。

0x0000000000400698 <+24>:adrpx0, 0x411000 <__libc_start_main@got.plt>

0x000000000040069c <+28>:addx0, x0, #0x38

0x00000000004006a0 <+32>:ldrx1, [x0]

0x00000000004006a4 <+36>:strx1, [x29,#56]

0x00000000004006a8 <+40>:movx1, #0x0                   // #0

这5条指令是用于安全保障的。因为函数TestParam中声明了一个数组,因此有受到缓冲区溢出攻击的危险。在其他平台下或者之前版本中,需要在编译时显式使用-fstack-protector选项,才会增加这样的安全保障指令。而在飞腾ARM配置的编译器中,默认就增加了。

其主要思路是在编译时生成一个随机化的值,如图中的_stack_guard保存在bss段中。在开始执行函数体时,把它从bss段中取出,放在栈的底部。然后执行函数。若有针对数组e的缓冲区溢出攻击,则_stack_guard就会被改写。在函数执行结束时,再把栈底部的值和bss段中的原始值相比较,若两者不同,就说明有攻击行为发生。

这5条指令的功能就是从bss段中把_stack_guard的值放到栈的底部。需要注意的是,在查找时使用了相对寻址指令adrp。

0x00000000004006ac <+44>:ldrw1, [x29,#28]

0x00000000004006b0 <+48>:ldrw0, [x29,#24]

0x00000000004006b4 <+52>:addw0, w1, w0

0x00000000004006b8 <+56>:strw0, [x29,#48]

0x00000000004006bc <+60>:ldrw1, [x29,#20]

0x00000000004006c0 <+64>:ldrw0, [x29,#16]

0x00000000004006c4 <+68>:subw0, w1, w0

0x00000000004006c8 <+72>:strw0, [x29,#52]

这8条指令是使用形式参数进行运算,并把结果保存在数组中。数组中只有两个元素,被放置在靠近栈底的位置。

0x00000000004006cc <+76>:ldrw1, [x29,#20]

0x00000000004006d0 <+80>:ldrw0, [x29,#16]

0x00000000004006d4 <+84>:addw0, w1, w0

0x00000000004006d8 <+88>:strw0, [x29,#44]

这4条指令的作用是计算出f的值,并把它保存到栈中。

0x00000000004006dc <+92>:ldrw1, [x29,#48]

0x00000000004006e0 <+96>:ldrw2, [x29,#52]

0x00000000004006e4 <+100>:adrpx0, 0x400000

0x00000000004006e8 <+104>:addx0, x0, #0x808

0x00000000004006ec <+108>:ldrw3, [x29,#44]

0x00000000004006f0 <+112>:bl0x400530 

这6条指令是负责准备参数寄存器x0、w1、w2和w3的值,并调用printf。x0中存放的是指向格式字符串的指针。

0x00000000004006f4 <+116>:adrpx0, 0x411000 <__libc_start_main@got.plt>

0x00000000004006f8 <+120>:addx0, x0, #0x38

0x00000000004006fc <+124>:ldrx1, [x29,#56]

0x0000000000400700 <+128>:ldrx0, [x0]

0x0000000000400704 <+132>:eorx0, x1, x0

0x0000000000400708 <+136>:cmpx0, xzr

0x000000000040070c <+140>:b.eq0x400714 

0x0000000000400710 <+144>:bl0x400500 <__stack_chk_fail@plt>

这8条指令是比较_stack_guard的值与存放在bss段中的值是否相等,若相等,在跳到400714,继续执行TestParam函数,否则跳到_stack_chk_fail函数,处理缓冲区溢出发生的情况。

0x0000000000400714 <+148>:ldpx29, x30, [sp],#64

0x0000000000400718 <+152>:ret

这2条指令从栈中恢复x29和x30的值,并返回。需要注意的是ldp指令执行之后,sp的值自动增加了64。

(更多内容欢迎关注微信公众号“PerfXLab卧谈会)

相关文章

  • 干货 | ARMv8架构下程序运行时栈帧布局

    结合ARM相关文档和在飞腾机器上使用gdb调试实际程序来研究ARM的指令和运行时栈帧布局。主要参考了三篇文档。 1...

  • 虚拟机的方法调用和字节码执行

    目录 一、运行时栈帧结构二、方法调用三、方法执行 一、运行时栈帧结构 栈帧是用于支持虚拟机进行 方法调用 和 方法...

  • 字节码执行引擎

    运行时栈帧结构 栈帧是虚拟机栈中的元素,每一个方法的调用对应着一个栈帧的入栈出栈。栈帧包括局部变量表、操作数栈、动...

  • 虚拟机字节码执行引擎

    运行时栈帧结构。 每个方法都有个栈帧,用来存储局部变量,操作数栈,方法入口,动态连接等。方法执行的过程就是栈帧入栈...

  • 虚拟机字节码执行引擎

    运行时栈帧结构 方法的调用和结束对应着栈帧在虚拟机栈的入栈 局部变量表 存放方...

  • 《深入理解Java虚拟机-JVM高级特性与最佳实践》学习总结(第

    第八章 虚拟机字节码执行引擎 8.1 运行时栈帧结构 1.什么是栈帧?栈帧(Stack Frame)是用于支持虚拟...

  • [c/c++]3.如何查看和更改程序运行栈的大小

    进程内存布局 我们写程序运行栈的栈帧保存在栈区,函数调用深度太多将导致爆栈栈的大小有上限,每个进程可以指定软上限,...

  • 虚拟机字节码执行引擎

    运行时栈帧结构 栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。...

  • 字节码执行机制

    运行时帧栈结构 帧栈 是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素...

  • 虚拟机字节码执行

    一、运行时栈帧结构 栈帧是用于支持虚拟机方法调用和方法执行的结构,栈帧存储了方法的局部变量表,操作数栈,动态链接,...

网友评论

      本文标题:干货 | ARMv8架构下程序运行时栈帧布局

      本文链接:https://www.haomeiwen.com/subject/liyeixtx.html