函数调用与汇编指令的关系

写一段简单的C代码分析其背后与汇编指令的关系

最近在看hotspot的代码,hotspot解释器会将字节码翻译成汇编指令,所以要先复习下这个基础

C代码

#include <stdio.h>

int  main(int args, char** argv){
    printf("%d", add1(100, 200, 500, 600));
}

int add1(int i, int j, int k, int m){
    return i + j + k + m;
}

gcc编译验证执行结果:

gcc -g2 FunctionInvokedAssembly.c -o FunctionInvokedAssembly
./FunctionInvokedAssembly
#1400

gcc编译成汇编代码

gcc -S -o FunctionInvokedAssembly.s FunctionInvokedAssembly.c

汇编代码如下:

    .file   "FunctionInvokedAssembly.c"
    .section    .rodata
.LC0:
    .string "%d"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movl    $600, %ecx
    movl    $500, %edx
    movl    $200, %esi
    movl    $100, %edi
    movl    $0, %eax
    call    add1
    movl    %eax, %esi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .globl  add1
    .type   add1, @function
add1:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    %edx, -12(%rbp)
    movl    %ecx, -16(%rbp)
    movl    -8(%rbp), %eax
    movl    -4(%rbp), %edx
    addl    %eax, %edx
    movl    -12(%rbp), %eax
    addl    %eax, %edx
    movl    -16(%rbp), %eax
    addl    %edx, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   add1, .-add1
    .ident  "GCC: (Ubuntu 4.8.5-4ubuntu8) 4.8.5"
    .section    .note.GNU-stack,"",@progbits

汇编用到的一些寄存器及一些指令

  • eax, ebx, ecx, edx, esi, edi, ebp(rbp), esp(rbp)等都是X86 汇编语言中CPU上的通用寄存器的名称。
  • rbp 调用函数的栈帧栈底地址
  • rsp 被调函数的栈帧栈底地址
  • eip:寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行
  • 减少esp(rsp)寄存器的值表示扩展栈帧
  • X86-64中,所有寄存器都是64位,相对32位的x86来说,标识符发生了变化,比如:从原来的%ebp变成了%rbp。为了向后兼容性,%ebp依然可以使用,不过指向了%rbp的低32位。
  • X86-64有16个64位寄存器,分别是:%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp,%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。%rax 作为函数返回值使用。%rsp 栈指针寄存器,指向栈顶。%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数...%rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改。%r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值

一条call指令,完成了两个任务:

  • 将调用函数(main)中的下一条指令入栈,被调函数返回后将取这条指令继续执行,64位rsp寄存器的值减8
  • 修改指令指针寄存器rip的值,使其指向被调函数的执行位置

寄存器图示

63              31             0
+------------------------------+
|%rax           |%eax          | 返回值
+------------------------------+
|%rbx           |%ebx          | 被调用保护者
+------------------------------+
|%rcx           |%ecx          | 第四个参数
+------------------------------+
|%rdx           |%edx          | 第三个参数
+------------------------------+
|%rsi           |%esi          | 第二个参数
+------------------------------+
|%rdi           |%edi          | 第一个参数
+------------------------------+
|%rbp           |%ebp          | 被调用者保护
+------------------------------+
|%rsp           |%esp          | 堆栈指针
+------------------------------+
|%r8            |%r8d          | 第五个参数
+------------------------------+
|%r9            |%r9d          | 第六个参数
+------------------------------+
|%r10           |%r10d         | 调用者保护
+------------------------------+
|%r11           |%r11d         | 调用者保护
+------------------------------+
|%r12           |%r12d         | 被调用者保护
+------------------------------+
|%r13           |%r13d         | 被调用者保护
+------------------------------+
|%r14           |%r14d         | 被调用者保护
+------------------------------+
|%r15           |%r15d         | 被调用者保护
+------------------------------+

栈帧

           +-------------------+
           |                   |
           |                   |
           | other frames      |
           |                   |
           |                   |
           +-------------------+
           |                   |
           |                   |
           | last frame        |
           |                   |
           |                   |
           +-------------------+
           | argument 1        |
           +-------------------+
           | argument 2        |
           +-------------------+
           | return address    |
           +-------------------+
%ebp->     | last frame %ebp   |
           +-------------------+
           |                   |
           |                   |
           | current frame     |
           |                   |
           |                   |
           +-------------------+
%esp->     |                   |
           +-------------------+

入口函数是main,然后调用各个子函数。在对应机器语言中,GCC把过程转化成栈帧(frame),简单的说,每个栈帧对应一个过程。X86-32典型栈帧结构中,由%ebp指向栈帧开始,%esp指向栈顶。

gcc边调试边反编译汇编代码

gdb FunctionInvokedAssembly
> b main
> r
>  disassemble /rm
Breakpoint 1, main (args=1, argv=0x7fffffffdf48) at FunctionInvokedAssembly.c:11
11      printf("%d", add1(100, 200, 500, 600));
(gdb) disassemble /rm
Dump of assembler code for function main:
9   int  main(int args, char** argv){
   0x00000000004004fd <+0>: 55  push   %rbp
   0x00000000004004fe <+1>: 48 89 e5    mov    %rsp,%rbp
   0x0000000000400501 <+4>: 48 83 ec 10 sub    $0x10,%rsp
   0x0000000000400505 <+8>: 89 7d fc    mov    %edi,-0x4(%rbp)
   0x0000000000400508 <+11>:    48 89 75 f0 mov    %rsi,-0x10(%rbp)

10  //  printf("%d", add1(100, 200, 500, 600, 700, 800, 900, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13));
11      printf("%d", add1(100, 200, 500, 600));
=> 0x000000000040050c <+15>:    b9 58 02 00 00  mov    $0x258,%ecx
   0x0000000000400511 <+20>:    ba f4 01 00 00  mov    $0x1f4,%edx
   0x0000000000400516 <+25>:    be c8 00 00 00  mov    $0xc8,%esi
   0x000000000040051b <+30>:    bf 64 00 00 00  mov    $0x64,%edi
   0x0000000000400520 <+35>:    b8 00 00 00 00  mov    $0x0,%eax
   0x0000000000400525 <+40>:    e8 13 00 00 00  callq  0x40053d <add1>
   0x000000000040052a <+45>:    89 c6   mov    %eax,%esi
   0x000000000040052c <+47>:    bf f4 05 40 00  mov    $0x4005f4,%edi
   0x0000000000400531 <+52>:    b8 00 00 00 00  mov    $0x0,%eax
   0x0000000000400536 <+57>:    e8 b5 fe ff ff  callq  0x4003f0 <[email protected]>

12  }
   0x000000000040053b <+62>:    c9  leaveq
   0x000000000040053c <+63>:    c3  retq   

End of assembler dump.

> info register
rax            0x4004fd 4195581
rbx            0x0  0
rcx            0x400570 4195696
rdx            0x7fffffffdf58   140737488346968
rsi            0x7fffffffdf48   140737488346952
rdi            0x1  1
rbp            0x7fffffffde60   0x7fffffffde60
rsp            0x7fffffffde50   0x7fffffffde50
r8             0x7ffff7dd0d80   140737351847296
r9             0x7ffff7dd0d80   140737351847296
r10            0x0  0
r11            0x0  0
r12            0x400400 4195328
r13            0x7fffffffdf40   140737488346944
r14            0x0  0
r15            0x0  0
rip            0x40050c 0x40050c <main+15>
eflags         0x206    [ PF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0

参考

X86-64寄存器和栈帧
函数调用过程探究
X86 Opcode and Instruction Reference
你会swap吗,按值传递还是按引用?
寄存器理解 及 X86汇编入门

原文地址:https://www.cnblogs.com/simoncook/p/11141240.html

时间: 2024-07-31 16:38:25

函数调用与汇编指令的关系的相关文章

汇编指令大全

blt   小于跳转 tst r0,#02 bne sleep ldr  r1,#0 解释:位比较,先进行and运算,如果r0第2位不为1,则与的结果为0,设置标志位zero=1,继续下面的ldr指令.反之,zero=0,跳转到sleep执行. bne指令: 非零则跳转 个人总结:tst 和bne连用: 先是用tst进行位与运算,然后将位与的结果与0比较,如果不为0,则跳到bne紧跟着的标记(如bne sleep,则跳到sleep处). tst 和beq连用: 先是用tst进行位与运算,然后将位

ARM汇编指令汇总

1.ARM汇编的格式:    在ARM汇编里,有些字符是用来标记行号的,这些字符要求顶格写:有些伪码是需要成对出现的,例如ENTRY和END,就需要对齐出现,也就是说他们要么都顶格,要么都空相等的空,否则编译器将报错.常量定义需要顶格书写,不然,编译器同样会报错.    2.字符串变量的值是一系列的字符,并且使用双引号作为分界符,如果要在字符串中使用双引号,则必须连续使用两个双引号.    3.在使用LDR时,当格式是LDR r0,=0x022248,则第二个参数表示地址,即0x022248,同

C51指针与A51汇编接口之间关系研究

最近在研究单片机C51对汇编的接口问题.char和int等都比较简单,使用寄存器或固定地地址传值都是可以的,具体可以参考keil的C51 user's guide.本篇短文主要重点讨论一下A51下如何遵循C51的接口标准来实现C51的指针.主要原因是,现在用C51的人越来越多,大家都图省事和方便.网上面有关A51的资料少得可怜,知道用汇编来实现代码优化的少之又少.本人是一直坚持用汇编写东西的.在嵌入式领域,很多东西都与硬件有关,多知道点底层东西还是有好处. 使用工具主要为keil,在window

汇编指令总结

GAS中每个操作都是有一个字符的后缀,表明操作数的大小. C声明 GAS后缀 大小(字节) char b 1 short w 2 (unsigned) int / long / char* l 4 float s 4 double l 8 long double t 10/12 注意:GAL使用后缀"l"同时表示4字节整数和8字节双精度浮点数,这不会产生歧义因为浮点数使用的是完全不同的指令和寄存器. 操作数格式: 格式 操作数值 名称 样例(GAS = C语言) $Imm Imm 立即

学习linux内核时常碰到的汇编指令(1)

 转载:http://blog.sina.com.cn/s/blog_4be6adec01007xvg.html 80X86 汇编指令符号大全 +.-.*./∶算术运算符. &∶宏处理操作符.宏扩展时不识别符号和字符串中的形式参数,如果在形式参数前面加上一个& 记号,宏汇编程序就能够用实在参数代替这个形式参数了. $∶地址计数器的值——记录正在被汇编程序翻译的语句地址.每个段均分配一个计数器,段内定义的所有标号和变量的偏移地址就是当前汇编地址计数器的值. ?∶操作数.在数据定义语句中,操作

用机器指令和汇编指令编程(修改版)

实验名称 用机器指令和汇编指令编程(1) 实验日期   2018.10.22 学院:计软院 专业:计算机科学与技术 年级:2017级 班次:5班 姓名:陈奕明 学号 20171308194 一.实验目的 1. 掌握使用debug工具编写和调试汇编命令的方法 2. 掌握第1-2章所学的关于CPU.寄存器.内存的基础知识 3. 掌握第1-2章涉及的几条指令mov, add, sub, jmp的用法 二.实验准备 1. 复习教材第1~2章内容,完成教材内相关检测点 2. 结合教材实验1 (P35)及公

ARM体系结构和汇编指令

第一节 可编程器件的编程原理 1. 可编程器件的特点 1 . CPU在固定频率的时钟控制下节奏运行 2 . CPU可以通过总线读取外部存储设备中的二进制指令集,然后解码执行 3 . 这些可以被CPU解码执行的二进制指令集是CPU设计的时候确定的,是CPU的设计者(ARM公司)定义的,本质上是一串由1和0组成的数字.这就是CPU的汇编指令集 2. 从源代码到cpu执行过程 第二节 指令集对cpu的意义 1. 汇编语言与C等高级语言的差异 汇编无移植性,c语言有一定可移植性,jave等更高级的语言移

汇编指令初步

汇编指令初步 指令和数据的存储方式 数据在SRAM存储器中的存储方法 AVR iATtiny88处理器从SRAM起始地址0x100开始存储数据,0x100之前的空间预留给系统使用 存储空间 | 功能 | 先后顺序 -|-|- data区 | 存储已初始化的变量 | 先 bss区 | 存储未初始化的变量 | 后 指令机器码在FLASH存储器中的存储方法 启动 函数 退出 汇编程序分析 PC指针:程序计数器 非转移指令 2个字节为一个指令单元,根据指令占多少字节长度,决定PC加多少 转移指令 将PC

汇编指令解析

X86架构 [原创]X86汇编之指令格式解析 [原创]汇编指令之OpCode快速入门 [原创]X64汇编之指令格式解析 ARM架构: