zoukankan      html  css  js  c++  java
  • 函数调用与汇编指令的关系

    写一段简单的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 <printf@plt>
    
    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汇编入门

  • 相关阅读:
    MVC异常过滤器
    文件分块传输
    UDP广播
    React 还是 Vue: 你应该选择哪一个Web前端框架?
    一个很好的XLSX的操作
    报表神器
    pycharm快敏捷键
    xlwt
    常用的列表和元祖
    HTML,css
  • 原文地址:https://www.cnblogs.com/simoncook/p/11141240.html
Copyright © 2011-2022 走看看