在c/c++中函数是通过压栈的方式来实现函数参数的传递过程,调用者函数会把被调函数的参数从右到左依次压入到栈中,
在printf中第一个被找到的就是那个字符串指针,就是双引号括起来的那部分,函数通过字符串的传输个数以及类型来计算数据的栈的指针的偏移地址
参考:C语言在ARM中函数调用时,栈是如何变化的?
1. 代码
#include<stdio.h>
int main(int argc , char ** argv)
{
int a = 1;
int b = 2 ;
printf("%d,%d",a,b) ;
return 0 ;
}
编译成汇编代码
arm-linux-gnueabihf-gcc -S test.c -o test.s
2. 查看生成的汇编代码(注释过),主要分析main 函数出的汇编代码
2.1 函数的前序操作
arm中的通用寄存器
r0-r3用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。被调用函数在返回之前不必恢复 r0-r3。—如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。r4-r11被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。r11 是栈帧指针 fp。r12是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。- 寄存器
r13是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。 - 寄存器
r14是链接寄存器 lr。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复 - 寄存器
r15是程序计数器 pc。它不能用于任何其它用途
main:
@ 函数前序操作
push {r7, lr} @ 保存帧指针和链接寄存器(返回地址)
sub sp, sp, #16 @ 为局部变量分配 16 字节的栈空间
add r7, sp, #0 @ 设置新的帧指针
str r0, [r7, #4] @ 保存第一个参数 argc 到栈中
str r1, [r7, #0] @ 保存第二个参数 argv 到栈中
- push {r7, lr} 保存当前的帧指针以及 返回地址到lr中
在调用函数的时候 栈的布局是
| 地址 | 内容 |
|---------|--------------|
| SP | r7 (旧帧指针) |
| SP+4 | lr (返回地址) |
- sub sp, sp, #16 为局部变量分配16个栈空间
更新之后的栈的布局
| 地址 | 内容 |
|---------|------------------|
| SP | 局部变量(16字节)|
| SP+16 | r7 (旧帧指针) |
| SP+20 | lr (返回地址) |
- add r7, sp, #0 设置新的帧指针设置新的帧指针 使其指向栈顶
2.2 初始化局部变量
movs r3, #1 @ 将常量 1 加载到 r3 寄存器
str r3, [r7, #12] @ 将 r3 的值存储到局部变量 a
movs r3, #2 @ 将常量 2 加载到 r3 寄存器
str r3, [r7, #8] @ 将 r3 的值存储到局部变量 b
此时栈的布局
| 地址 | 内容 |
|-----------|----------------|
| SP | argv |
| SP+4 | argc |
| SP+8 | 2 (b) |
| SP+12 | 1 (a) |
| SP+16 | r7 (旧帧指针) |
| SP+20 | lr (返回地址) |
2.3 准备printf函数
ldr r2, [r7, #8] @ 从栈中找到局部变量b的值,加载到寄存器 r2中
ldr r1, [r7, #12] @ 从栈中找到局部变量b的值,加载到寄存器 r2中
movw r0, #:lower16:.LC0 @ 将格式字符串的低 16 位地址加载到 r0
movt r0, #:upper16:.LC0 @ 将格式字符串的高 16 位地址加载到 r0
2.4 调用printf
bl printf @ 调用 printf 函数
2.5 函数的后续操作
movs r3, #0 @ 将返回值 0 加载到 r3 寄存器
mov r0, r3 @ 将 r3 的值移动到 r0
adds r7, r7, #16 @ 恢复栈指针
mov sp, r7 @ 恢复栈指针
pop {r7, pc} @ 恢复帧指针和程序计数器(返回地址)
3. 完整的汇编代码
@ 文件头部
.syntax unified @ 使用统一语法
.arch armv7-a @ 目标架构
.eabi_attribute 27, 3
.eabi_attribute 28, 1
.fpu vfpv3-d16 @ 使用 VFPv3-D16 浮点单元
.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
.thumb @ 使用 Thumb 指令集
.file "test.c"
@ 只读数据段
.section .rodata @ 定义只读数据段
.align 2 @ 按照四字节对齐
.LC0: @ 标签 指向字符串地址
.ascii "%d,%d\000" @ 定义字符串
@ 这里也就是主程序段
.text @ 定义代码段
.align 2
.global main @ 定义全局符号 mian
.thumb @ 指定使用thumb 指令集
.thumb_func @ 指定使用thumb 指令集
.type main, %function @ 声明main 是一个函数
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
push {r7, lr} @ 将帧指针和链接寄存器(返回地址)保存到栈
sub sp, sp, #16 @ 为局部变量分配16字节的空间
add r7, sp, #0 @设置帧指针
str r0, [r7, #4] @保存函数参数到栈中
str r1, [r7] @保存函数参数到栈中
@初始化局部变量
movs r3, #1 @将 1 加载到 寄存器r3 中 a = 1
str r3, [r7, #12] @将 a压入到栈中 , 也就是将r3 的值存储到局部变量a 中
movs r3, #2 @将 2加载到 寄存器r3 中 b = 2
str r3, [r7, #8] @ 先将 b 压入到 栈中
@准备printf的参数
ldr r2, [r7, #8] @将 b 的值加载到 r2
ldr r1, [r7, #12] @将 a 的值加载到 r1
movw r0, #:lower16:.LC0 @将格式字符串的地址加载到 r0
movt r0, #:upper16:.LC0
bl printf @调用 printf 函数,参数在 r0(格式字符串)、r1(a 的值)、r2(b 的值)中。
函数后序操作
movs r3, #0
mov r0, r3 @将返回值0加载到 r0。
adds r7, r7, #16 @恢复栈指针
mov sp, r7
@ sp needed
pop {r7, pc} 恢复帧指针和程序计数器(返回地址
.size main, .-main
.ident "GCC: (Linaro GCC 4.9-2017.01) 4.9.4"
.section .note.GNU-stack,"",%progbits
894

被折叠的 条评论
为什么被折叠?



