当前位置: 首页 > news >正文

从汇编指令看函数调用堆栈的详细过程

1、C++代码

这个C++源码实现了一个简单的加法函数,并在主函数中调用该函数来计算两个整数的和。

int sum(int a,int b)
{int temp=0;temp=a+b;return temp;
}int main()
{int a=10;int b=20;int ret=sum(a,b);return 0;
}

2、汇编代码

在 ARM Cortex-A9 平台上,编译后的 C++ 源代码的汇编代码如下:

	.cpu cortex-a9.eabi_attribute 28, 1.eabi_attribute 20, 1.eabi_attribute 21, 1.eabi_attribute 23, 3.eabi_attribute 24, 1.eabi_attribute 25, 1.eabi_attribute 26, 2.eabi_attribute 30, 6.eabi_attribute 34, 1.eabi_attribute 18, 4.file	"main.cpp".text.align	2.global	_Z3sumii.syntax unified.arm.fpu neon.type	_Z3sumii, %function
_Z3sumii:.fnstart
.LFB0:@ args = 0, pretend = 0, frame = 16@ frame_needed = 1, uses_anonymous_args = 0@ link register save eliminated.str	fp, [sp, #-4]!add	fp, sp, #0sub	sp, sp, #20str	r0, [fp, #-16]str	r1, [fp, #-20]mov	r3, #0str	r3, [fp, #-8]ldr	r2, [fp, #-16]ldr	r3, [fp, #-20]add	r3, r2, r3str	r3, [fp, #-8]ldr	r3, [fp, #-8]mov	r0, r3add	sp, fp, #0@ sp neededldr	fp, [sp], #4bx	lr.cantunwind.fnend.size	_Z3sumii, .-_Z3sumii.align	2.global	main.syntax unified.arm.fpu neon.type	main, %function
main:.fnstart
.LFB1:@ args = 0, pretend = 0, frame = 16@ frame_needed = 1, uses_anonymous_args = 0push	{fp, lr}add	fp, sp, #4sub	sp, sp, #16mov	r3, #10str	r3, [fp, #-8]mov	r3, #20str	r3, [fp, #-12]ldr	r1, [fp, #-12]ldr	r0, [fp, #-8]bl	_Z3sumiistr	r0, [fp, #-16]mov	r3, #0mov	r0, r3sub	sp, fp, #4@ sp neededpop	{fp, pc}.cantunwind.fnend.size	main, .-main.ident	"GCC: (GNU) 7.3.0".section	.note.GNU-stack,"",%progbits

3、汇编代码分析
汇编代码的开头

.cpu cortex-a9
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file	"main.cpp"

这些行指定了目标 CPU 和一些 EABI(嵌入式应用程序二进制接口)属性。它们用于设置编译器和链接器的配置。

具体来说:

  • .eabi_attribute 是 ARM 汇编中的一个伪指令,用于设置 EABI 属性。
  • 28 是属性编号,表示该属性的类型。
  • 1 是该属性的值,表示特定的设置或选项。
    属性编号 28 代表 TAG_CPU_unaligned_access,用于指示代码是否允许非对齐的内存访问。这个属性的值 1 表示允许非对齐的内存访问。

属性编号 20 代表 TAG_ABI_FP_rounding,用于指示浮点运算的舍入模式支持。这个属性的值 1 表示支持 IEEE 754 标准规定的最近偶数舍入模式(也称为“最接近舍入”或“银行家舍入”)。

属性编号 21 代表 TAG_ABI_FP_denormal,描述浮点运算如何处理非正规化(denormal)数。值 1 表示支持 IEEE 754 标准的非正规化数。

属性编号 23 代表 TAG_ABI_FP_number_model,描述浮点数模型。值 3 表示 IEEE 754 标准的浮点数模型。

属性编号 24 代表 TAG_ABI_align_needed,描述是否需要特定的对齐。值 1 表示需要特定的对齐要求。

属性编号 25 代表 TAG_ABI_align_preserved,描述是否保留特定的对齐。值 1 表示保留特定的对齐要求。

属性编号 26 代表 TAG_ABI_enum_size,描述枚举类型的大小。值 2 表示枚举类型的大小是 32 位。

属性编号 30 代表 TAG_ABI_HardFP_use,描述硬件浮点运算的使用。值 6 表示硬件浮点运算使用的是 VFPv3-D16(Vector Floating Point v3 with 16 double-precision registers)。

属性编号 34 代表 TAG_CPU_unaligned_access,描述是否允许非对齐的内存访问。值 1 表示允许非对齐的内存访问。

属性编号 18 代表 TAG_ABI_PCS_wchar_t,描述 wchar_t 类型的大小。值 4 表示 wchar_t 类型的大小是 4 字节。

sum函数.rodata 段

	.text.align	2.global	_Z3sumii.syntax unified.arm.fpu neon.type	_Z3sumii, %function

这部分代码定义了一些只读数据段 (.rodata),并声明了一些全局符号,包括 _Z3sumii(即 sum 函数)。

这里解释一下sum函数生成符号_Z3sumii的规则,这在gdb调试中很重要:

  • _Z 是编译器生成的符号名称的前缀,用于标识这是一个函数名。
  • 3 表示函数名的长度,即后续有三个字符构成函数名。
  • sum 是函数的实际名称。
  • ii 表示函数 sum 接受两个整型参数(int a, int b)。
    _Z3sumii也称为函数签名(signature),由上可知函数签名由函数的名称、参数类型及其顺序组成。函数的返回类型不包括在签名中。

给定以下两个函数声明:

int sum(int a, int b);
void sum(int a, int b);

这两个函数的签名都是相同的,因为它们具有相同的函数名称和参数列表(即两个整型参数 int a 和 int b)。在函数签名中,返回类型(int 和 void)并不影响其唯一性。

所以,从函数签名的角度来看,这两个函数的签名是相同的,因此可以说它们的"符号"相同。

sum 函数实现

_Z3sumii:.fnstart
.LFB0:@ args = 0, pretend = 0, frame = 16@ frame_needed = 1, uses_anonymous_args = 0@ link register save eliminated.str	fp, [sp, #-4]!add	fp, sp, #0sub	sp, sp, #20str	r0, [fp, #-16]str	r1, [fp, #-20]mov	r3, #0str	r3, [fp, #-8]ldr	r2, [fp, #-16]ldr	r3, [fp, #-20]add	r3, r2, r3str	r3, [fp, #-8]ldr	r3, [fp, #-8]mov	r0, r3add	sp, fp, #0@ sp neededldr	fp, [sp], #4bx	lr.cantunwind.fnend.size	_Z3sumii, .-_Z3sumii

3. ARM汇编代码中的堆栈操作过程分析

这段代码展示了函数调用时ARM架构下的堆栈管理机制,包括函数调用时的参数传递、局部变量存储以及返回地址处理等。

1. 主函数(main)的堆栈操作

main:
.LFB1:@ 函数序言(prologue)push    {fp, lr}        @ 将帧指针(fp)和链接寄存器(lr)压栈保存add     fp, sp, #4      @ 设置新的帧指针(fp = sp + 4)sub     sp, sp, #16     @ 在栈上分配16字节空间用于局部变量

操作分析

  1. 保存调用现场push {fp, lr}将当前帧指针(fp)和链接寄存器(lr)压入堆栈。这是ARM架构中典型的函数开场操作,用于保存调用者的帧指针和返回地址[citation:1][citation:5]。

    • 在满递减堆栈(FD)模式下,push等同于STMFD指令,寄存器按编号升序压入从高到低的地址[citation:4][citation:6]。
    • 压入顺序是fp(即r11)先入栈,lr(r14)后入栈,sp会递减8字节(假设每个寄存器4字节)。
  2. 建立新栈帧add fp, sp, #4将帧指针设置为当前sp+4的位置,指向保存的fp位置,用于后续访问局部变量和参数[citation:1]。

  3. 分配局部变量空间sub sp, sp, #16将栈指针下移16字节,为局部变量a、b和ret分配空间[citation:5]。

    @ 局部变量初始化mov     r3, #10         @ 将立即数10存入r3str     r3, [fp, #-8]   @ 将r3的值存入fp-8的位置(变量a)mov     r3, #20         @ 将立即数20存入r3str     r3, [fp, #-12]  @ 将r3的值存入fp-12的位置(变量b)

操作分析

  • 通过fp相对寻址方式([fp, #-8]和[fp, #-12])将局部变量a和b存储在栈上[citation:2]。
    @ 调用sum函数前的参数准备ldr     r1, [fp, #-12]  @ 将变量b的值加载到r1(第二个参数)ldr     r0, [fp, #-8]   @ 将变量a的值加载到r0(第一个参数)bl      _Z3sumii        @ 调用sum函数(会修改lr)

操作分析

  • 按照ARM调用约定,前4个参数通过r0-r3传递[citation:1][citation:5]。
  • bl指令会跳转到sum函数,同时将返回地址(下一条指令地址)存入lr。
    @ 函数收尾(epilogue)str     r0, [fp, #-16]  @ 将sum返回值存入fp-16的位置(变量ret)mov     r3, #0          @ 准备返回值0mov     r0, r3          @ 将返回值存入r0sub     sp, fp, #4      @ 恢复栈指针(sp = fp - 4)@ sp neededpop     {fp, pc}        @ 恢复fp并从栈中弹出返回地址到pc

操作分析

  1. 恢复栈指针sub sp, fp, #4将sp恢复到压入fp前的状态。
  2. 恢复调用现场pop {fp, pc}恢复保存的fp,并将保存的返回地址(lr)直接弹出到pc,实现函数返回[citation:5]。
    • 这里利用了pop指令可以指定pc的特性,相当于同时执行了pop {fp, lr}bx lr[citation:5]。

2. sum函数的堆栈操作

_Z3sumii:
.LFB0:@ 函数序言(prologue)str     fp, [sp, #-4]!  @ 将fp压栈并更新sp(pre-indexed存储)add     fp, sp, #0      @ 设置新的帧指针(fp = sp)sub     sp, sp, #20     @ 分配20字节栈空间(局部变量和参数)

操作分析

  1. 保存帧指针str fp, [sp, #-4]!使用预索引(pre-indexed)方式将fp压栈,同时sp减4[citation:6]。

    • !表示写回,即sp = sp - 4,这是满递减堆栈的典型操作[citation:1][citation:4]。
  2. 建立新栈帧add fp, sp, #0将fp设置为当前sp值,用于访问局部变量和参数。

  3. 分配局部变量空间sub sp, sp, #20分配20字节栈空间(可能由于对齐要求)[citation:5]。

    @ 存储参数和局部变量str     r0, [fp, #-16]  @ 存储第一个参数a到fp-16str     r1, [fp, #-20]  @ 存储第二个参数b到fp-20mov     r3, #0          @ 初始化temp为0str     r3, [fp, #-8]   @ 存储temp到fp-8

操作分析

  • 函数参数r0和r1被保存到栈上,这是非优化编译的典型特征(保存参数到栈帧)[citation:1]。
    @ 计算a + bldr     r2, [fp, #-16]  @ 加载a到r2ldr     r3, [fp, #-20]  @ 加载b到r3add     r3, r2, r3      @ 计算a + bstr     r3, [fp, #-8]   @ 存储结果到temp
    @ 函数收尾(epilogue)ldr     r3, [fp, #-8]   @ 加载temp到r3(返回值)mov     r0, r3          @ 将返回值存入r0add     sp, fp, #0      @ 恢复栈指针(sp = fp)@ sp neededldr     fp, [sp], #4    @ 从栈中恢复fp并更新sp(post-indexed加载)bx      lr              @ 返回调用者

操作分析

  1. 准备返回值:将计算结果通过r0返回(ARM调用约定)[citation:5]。
  2. 恢复栈指针add sp, fp, #0将sp恢复到fp的位置。
  3. 恢复帧指针ldr fp, [sp], #4使用后索引(post-indexed)方式从栈中恢复fp,同时sp加4[citation:6]。
  4. 函数返回bx lr跳转到lr保存的返回地址。

3. 堆栈布局分析

在main函数调用sum函数时,堆栈的典型布局如下(地址从高到低):

高地址
...
main调用者的栈帧
保存的fp        <-- main函数的fp初始指向这里
保存的lr
局部变量a       [fp-8]
局部变量b       [fp-12]
局部变量ret     [fp-16]
...
低地址

sum函数被调用时,堆栈布局变为:

高地址
...
main调用者的栈帧
保存的fp        <-- main函数的fp指向这里
保存的lr
局部变量a       
局部变量b       
局部变量ret     
保存的fp        <-- sum函数的fp初始指向这里
sum的参数和局部变量
...
低地址

相关文章:

  • 机器学习9——决策树
  • 【Visual Studio Code上传文件到服务器】
  • 生物实验室安全、化学品安全
  • Wpf布局之Canvas面板!
  • libevent(2)之使用教程(1)介绍
  • C++11 异步编程(3)--- packaged_task
  • nginx反向代理的bug
  • 用Flink打造实时数仓:生产环境中的“坑”与“解药”
  • 备战全国青少年信息素养大赛图形化编程复赛/省赛——绘制图形
  • [数论](a % MOD + b % MOD) % MOD = (a + b) % MOD
  • 《P1637 三元上升子序列》
  • #华为昇腾#华为计算#昇腾开发者计划2025#
  • Redis学习笔记——黑马点评 附近商铺到UV统计 完结
  • 中州养老:学会设计数据库表
  • 银行账户管理系统01
  • 图解Git中Rebase与Merge的区别
  • Linux中《动/静态库原理》
  • WireShark网络取证分析第一集到第五集和dvwa靶场环境分析漏洞
  • C++并发编程-5.C++ 线程安全的单例模式演变
  • 暑假复习篇之五子棋①