printf的压栈过程

在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中的通用寄存器

  1. r0-r3用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。被调用函数在返回之前不必恢复 r0-r3。—如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。
  2. r4-r11 被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。r11 是栈帧指针 fp。
  3. r12 是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。
  4. 寄存器 r13 是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。
  5. 寄存器r14是链接寄存器 lr。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复
  6. 寄存器 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 到栈中
  1. push {r7, lr} 保存当前的帧指针以及 返回地址到lr中
    在调用函数的时候 栈的布局是
| 地址    | 内容         |
|---------|--------------|
| SP      | r7 (旧帧指针) |
| SP+4    | lr (返回地址)  |
  1. sub sp, sp, #16 为局部变量分配16个栈空间
    更新之后的栈的布局
| 地址    | 内容             |
|---------|------------------|
| SP      | 局部变量(16字节)|
| SP+16   | r7 (旧帧指针)    |
| SP+20   | lr (返回地址)    |
  1. 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值