从汇编角度浅析C程序

详情请见: http://heamon7.gitbooks.io/cscw2-newly-to-assembly/content/

p1

Outline

  • 1.初探C程序的汇编级形式
  • 2.汇编语言及其相关指令简介
  • 3.用汇编分析C程序的工具介绍
  • 4.再探C程序的汇编级形式
  • 5.C程序中递归函数的汇编分析
  • 6.其他基于C程序的汇编话题

p2

1. 初探C程序的汇编级形式

  • 1.1用gcc将C程序编译成汇编代码并查看
  • 1.2C程序到可执行文件的编译链接过程
  • 1.3程序被执行时的内存分配情况概览

p3

1.1 用gcc将C程序编译成汇编代码并查看

编写代码:code/1.1/ add.c :

int add(int a, int b) {
    int result;

    result = a + b;

    return result;
}

int main(int argc, char *argv[]) {
    int a,b,result;

    a = 1;
    b = 2;
    result = add(a,b);

    return 0;
}

shell命令:

gcc -S add.c -o add.s

生成代码: code/1.1/ add.s ,关键部分:

add:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -20(%rbp)
    movl    %esi, -24(%rbp)
    movl    -24(%rbp), %eax
    movl    -20(%rbp), %edx
    addl    %edx, %eax
    movl    %eax, -4(%rbp)
    movl    -4(%rbp), %eax
    popq    %rbp
    ret
main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $32, %rsp
    movl    %edi, -20(%rbp)
    movq    %rsi, -32(%rbp)
    movl    $1, -12(%rbp)
    movl    $2, -8(%rbp)
    movl    -8(%rbp), %edx
    movl    -12(%rbp), %eax
    movl    %edx, %esi
    movl    %eax, %edi
    call    add
    movl    %eax, -4(%rbp)
    movl    $0, %eax
    leave
    ret

p4

1.2 C程序到可执行程序的编译链接过程

gcc代理的编译过程

ref:http://7905648.blog.51cto.com/7895648/1297255
http://tech.meituan.com/linker.html

1.3程序被执行时的内存分配情况概览

图解:

进程内存区域的分布

ref: http://blog.sina.com.cn/s/blog_5420e0000101a0w1.html
http://blog.csdn.net/chengyingzhilian/article/details/8045428
http://www.kerneltravel.net/journal/v/mem.htm

p5

2.汇编语言及其相关指令简介

  • 2.1.汇编语言简介
  • 2.2 8086,x86,x86-64寄存器简介
  • 2.3 AT&T风格x86-64汇编指令

p6

2.1.汇编语言简介

汇编语言采用了助记符(mnemonics)来代表特定低级机器语言的操作。特定的汇编目标指令集可能会包括特定的操作数。许多汇编程序可以识别代表地址和常量的标签(label)和符号(symbols),这样就可以用字符来代表操作数而无需采取写死的方式。普遍地说,特定的汇编语言和特定的机器语言指令集是一一对应的。

汇编指令的两大风格分别是Intel汇编与AT&T汇编,分别被Microsoft Windows/Visual C++GNU/Gas采用(Gas也可使用Intel汇编风格)

我们这里不介绍他们的使用区别,而直接介绍Linux下默认的AT&T汇编风格。
ref:http://zh.wikipedia.org/zh/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80

p7

2.2 8086,x86,x86-64简介

8086

8086是由英特爾公司於1976年开始設計,1978年年中发布的Intel第一款16位微處理器,同时也是x86架構之開端。

所有的内部寄存器、内部及外部数据总线都是16位宽,因此是完全的16位微处理器。20位外部地址总线,因此物理寻址空间为1MB (即2^20
= 1,048,576).由于内部寄存器都是16位,对1M地址空间寻址时采取了段寻址方式。8086的封装采用40引脚的双列直插(dual in-line),数据总线与地址总线复用了前16个引脚。16位的I/O地址,因此独立的I/O寻址空间为64KB (即2^16
= 65,536).由于8086内部的地址寄存器是16 位宽,因而最大线性寻址空间为64 KB.使用超过64 KB内存空间的程序设计时,需要调整段寄存器(segment registers)。直到32位的80386出现之前,8086的这种段寻址相当不便.

寄存器

8086有8个16比特的寄存器,包括栈寄存器SP与BP,但不包括指令寄存器IP、控制寄存器FLAGS以及四个段寄存器。AX, BX, CX, DX,这四个寄存器可以按照字节访问;但BP, SI, DI, SP,这四个地址寄存器只能按照16位宽访问。

Block diagram of Intel 8086:

Block diagram of Intel 8086

![The 8086 registers](http://picture-repository-of-heamon7.qiniudn.com/The 8086 registers.png)

ref: http://zh.wikipedia.org/zh/Intel_8086
http://www.cnblogs.com/zhaoyl/archive/2012/05/15/2501972.html

80386

Intel 80386,是英特尔(Intel)公司的一款x86系列CPU,最初发布于1985年10月17日。
80386处理器被广泛应用在1980年代中期到1990年代中期的IBM PC兼容机中。这些PC被称为“80386电脑”或“386电脑”,有时也简称“80386”或“386”。
80386的重要特点是:

  • 首次在x86处理器中实现了32位元系统(IA-32)。

寄存器

x86通用寄存器

ref:
http://blog.chinaunix.net/uid-23069658-id-3756930.html
http://zh.wikibooks.org/wiki/X86%E7%B5%84%E5%90%88%E8%AA%9E%E8%A8%80/X86%E6%9E%B6%E6%9E%84%E5%8F%8A%E5%AF%84%E5%AD%98%E5%99%A8%E8%A7%A3%E9%87%8A

x86-64

x86-64(簡稱x64)是64位版本的x86指令集,向前相容於16位32位的x86架構。x64於1999年由AMD設計,AMD首次公開64位元集以擴充給x86,稱為「AMD64」。其後也為英特爾所採用,現時英特爾稱之為「Intel 64」,在之前曾使用過「Clackamas Technology」 (CT)、「IA-32e」及「EM64T」。
Applerpm 以「x86-64」或「x86_64」稱呼此64位架構。太陽電腦(已被甲骨文公司收購)及 Microsoft 稱之為「x64」。BSD 家族及其他 Linux發行版則使用「amd64」,32位元版本則稱為「i386」(或 i486/586/686)。
在x86-64出現以前,英特爾與惠普聯合設計出IA-64架構;惟IA-64並不與x86兼容,且市場反應較冷淡,同時受制於多個專利權,使其他廠商不能模仿。與x86兼容的AMD64架構便應運而生,其主要特點如名稱所述,既有支援64位通用暫存器、64位整數及邏輯運算、以及64位虛擬位址,設計人員又為架構作出不少改進,部份重大改變如下:

  • 新增暫存器

寄存器

x86-64寄存器

(CPU中程序员唯一能够控制的就是寄存器)

2.3 AT&T风格x86-64汇编指令

  1. 操作数指示符
操作数格式
操作数格式

寻址模式

ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)

它所表示的地址可以这样计算出来:

FINAL ADDRESS = ADDRESS_OR_OFFSET + BASE_OR_OFFSET + MULTIPLIER * INDEX

其中ADDRESS_OR_OFFSET和MULTIPLIER必须是常数,BASE_OR_OFFSET和INDEX必须是寄存器。在有些寻址方式中会省略这4项中的某些项,相当于这些项是0。

  • 直接寻址(Direct Addressing Mode)。只使用ADDRESS_OR_OFFSET寻址,例如movl ADDRESS, %eax
    把ADDRESS地址处的32位数传送到eax
    寄存器。
  • 变址寻址(Indexed Addressing Mode) 。上一节的movl data_items(,%edi,4), %eax
    就属于这种寻址方式,用于访问数组元素比较方便。
  • 间接寻址(Indirect Addressing Mode)。只使用BASE_OR_OFFSET寻址,例如movl (%eax), %ebx,把eax寄存器的值看作地址,把这个地址处的32位数传送到ebx寄存器。注意和movl %eax, %ebx区分开。
  • 基址寻址(Base Pointer Addressing Mode)。只使用ADDRESS_OR_OFFSET和BASE_OR_OFFSET寻址,例如movl 4(%eax), %ebx ,用于访问结构体成员比较方便,例如一个结构体的基地址保存在eax 寄存器中,其中一个成员在结构体内的偏移量是4字节,要把这个成员读上来就可以用这条指令。
  • 立即数寻址(Immediate Mode)。就是指令中有一个操作数是立即数,例如movl $12, %eax 中的$12 ,这其实跟寻址没什么关系,但也算作一种寻址方式。
  • 寄存器寻址(Register Addressing Mode)。就是指令中有一个操作数是寄存器,例如movl $12, %eax 中的%eax ,这跟内存寻址没什么关系,但也算作一种寻址方式。在汇编程序中寄存器用助记符来表示,在机器指令中则要用几个Bit表示寄存器的编号,这几个Bit也可以看作寄存器的地址,但是和内存地址不在一个地址空间。

ref:
http://docs.linuxtone.org/ebooks/C&CPP/c/ch18s04.html

2.数据传送指令


数据传送指令
数据传送指令
mov S,D
push S
pop D

3.算术和逻辑运算指令


算术和逻辑运算指令
算术和逻辑运算指令

4.过程调用指令


过程调用指令
过程调用指令

上面这三条指令每一条都相当于几条指令

3.用汇编分析C程序的工具介绍

  • 3.1 gcc
  • 3.2 gdb&&ddd

gcc

C 编程中相关文件后缀

.a 静态库 (archive)
.c C源代码(需要编译预处理)
.h C源代码头文件
.i C源代码(不需编译预处理)
.o 对象文件
.s 汇编语言代码
.so 动态库

常用命令:

ref:
http://man.linuxde.net/gcc
http://wiki.ubuntu.org.cn/Gcchowto
http://wiki.ubuntu.org.cn/Compiling_C

3.2 gdb&&ddd

常用命令:

ref:
http://wiki.ubuntu.org.cn/%E7%94%A8GDB%E8%B0%83%E8%AF%95%E7%A8%8B%E5%BA%8F
http://wiki.ubuntu.org.cn/index.php?title=Insight%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95%EF%BC%88gdb%E7%9A%84%E6%9C%80%E4%BC%98%E5%89%8D%E7%AB%AF%EF%BC%89&variant=zh-hans

(objdump)

4.再探C程序的汇编级形式:

gcc编译链接得到目标文件:

gcc -g -Wall add.c -o add

利用gdb查看汇编代码:

gdb -q add

得到:

heamon7@ubuntu:~/Project/test$ gdb -q add
Reading symbols from add...done.
(gdb) l 1,20
1   int add(int a, int b) {
2       int result;
3
4       result = a + b;
5
6       return result;
7   }
8
9   int main(int argc, char *argv[]) {
10      int a,b,result;
11
12      a = 1;
13      b = 2;
14      result = add(a,b);
15
16      return 0;
17  }
(gdb) disass add
Dump of assembler code for function add:
   0x00000000004004ed <+0>: push   %rbp
   0x00000000004004ee <+1>: mov    %rsp,%rbp
   0x00000000004004f1 <+4>: mov    %edi,-0x14(%rbp)
   0x00000000004004f4 <+7>: mov    %esi,-0x18(%rbp)
   0x00000000004004f7 <+10>:    mov    -0x18(%rbp),%eax
   0x00000000004004fa <+13>:    mov    -0x14(%rbp),%edx
   0x00000000004004fd <+16>:    add    %edx,%eax
   0x00000000004004ff <+18>:    mov    %eax,-0x4(%rbp)
   0x0000000000400502 <+21>:    mov    -0x4(%rbp),%eax
   0x0000000000400505 <+24>:    pop    %rbp
   0x0000000000400506 <+25>:    retq
End of assembler dump.
(gdb) disass main
Dump of assembler code for function main:
   0x0000000000400507 <+0>: push   %rbp
   0x0000000000400508 <+1>: mov    %rsp,%rbp
   0x000000000040050b <+4>: sub    $0x20,%rsp
   0x000000000040050f <+8>: mov    %edi,-0x14(%rbp)
   0x0000000000400512 <+11>:    mov    %rsi,-0x20(%rbp)
   0x0000000000400516 <+15>:    movl   $0x1,-0xc(%rbp)
   0x000000000040051d <+22>:    movl   $0x2,-0x8(%rbp)
   0x0000000000400524 <+29>:    mov    -0x8(%rbp),%edx
   0x0000000000400527 <+32>:    mov    -0xc(%rbp),%eax
   0x000000000040052a <+35>:    mov    %edx,%esi
   0x000000000040052c <+37>:    mov    %eax,%edi
   0x000000000040052e <+39>:    callq  0x4004ed <add>
   0x0000000000400533 <+44>:    mov    %eax,-0x4(%rbp)
   0x0000000000400536 <+47>:    mov    $0x0,%eax
   0x000000000040053b <+52>:    leaveq
   0x000000000040053c <+53>:    retq
End of assembler dump.
(gdb)

接着打断点,进行常规调试,查看程序的运行的情况:

(gdb) b 14
Breakpoint 1 at 0x400524: file add.c, line 14.
(gdb) r
Starting program: /home/heamon7/Project/test/add

Breakpoint 1, main (argc=1, argv=0x7fffffffe658) at add.c:14
14      result = add(a,b);
(gdb) p a
$1 = 1
(gdb) p b
$2 = 2
(gdb) p result
$3 = 0
(gdb) s
add (a=1, b=2) at add.c:4
4       result = a + b;
(gdb) s
6       return result;
(gdb) s
7   }
(gdb) p result
$4 = 3
(gdb) c
Continuing.
[Inferior 1 (process 23833) exited normally]
(gdb)

接着我们看一下从机器级层面对代码进行调试,查看程序的运行情况:

(gdb) b 12
Breakpoint 1 at 0x400516: file add.c, line 12.
(gdb) r
Starting program: /home/heamon7/Project/test/add

Breakpoint 1, main (argc=1, argv=0x7fffffffe658) at add.c:12
12      a = 1;
(gdb) si
13      b = 2;
(gdb)
14      result = add(a,b);
(gdb)
0x0000000000400527  14      result = add(a,b);
(gdb)
0x000000000040052a  14      result = add(a,b);
(gdb)
0x000000000040052c  14      result = add(a,b);
(gdb)
0x000000000040052e  14      result = add(a,b);
(gdb)
add (a=0, b=0) at add.c:1
1   int add(int a, int b) {
(gdb)
0x00000000004004ee  1   int add(int a, int b) {
(gdb)
0x00000000004004f1  1   int add(int a, int b) {
(gdb)
0x00000000004004f4  1   int add(int a, int b) {
(gdb)
4       result = a + b;
(gdb)
0x00000000004004fa  4       result = a + b;
(gdb)
0x00000000004004fd  4       result = a + b;
(gdb)
0x00000000004004ff  4       result = a + b;
(gdb)
6       return result;
(gdb)
7   }
(gdb)
0x0000000000400506  7   }
(gdb)
0x0000000000400533 in main (argc=1, argv=0x7fffffffe658) at add.c:14
14      result = add(a,b);
(gdb)
16      return 0;
(gdb)
17  }
(gdb)
0x000000000040053c  17  }
(gdb)
__libc_start_main (main=0x400507 <main>, argc=1, argv=0x7fffffffe658,
    init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe648) at libc-start.c:321
321 libc-start.c: No such file or directory.
(gdb) c
Continuing.
[Inferior 1 (process 23945) exited normally]
(gdb)

这里的运行情况和我们常规调试看到的并不一样,我们回到之前的汇编代码,分析一下每一步在干什么。
为了方便查看源C代码和汇编代码的对应关系,我们利用反汇编工具objdump得到的反汇编代码,来分析程序执行过程。
从目标文件反汇编得到的汇编代码:

gcc -g -Wall add.c -o add
objdump -S add

得到:

00000000004004ed <add>:
int add(int a, int b) {
  4004ed:   55                      push   %rbp
  4004ee:   48 89 e5                mov    %rsp,%rbp
  4004f1:   89 7d ec                mov    %edi,-0x14(%rbp)
  4004f4:   89 75 e8                mov    %esi,-0x18(%rbp)
    int result;

    result = a + b;
  4004f7:   8b 45 e8                mov    -0x18(%rbp),%eax
  4004fa:   8b 55 ec                mov    -0x14(%rbp),%edx
  4004fd:   01 d0                   add    %edx,%eax
  4004ff:   89 45 fc                mov    %eax,-0x4(%rbp)

    return result;
  400502:   8b 45 fc                mov    -0x4(%rbp),%eax
}
  400505:   5d                      pop    %rbp
  400506:   c3                      retq

0000000000400507 <main>:

int main(int argc, char *argv[]) {
  400507:   55                      push   %rbp   //建立main函数的栈帧
  400508:   48 89 e5                mov    %rsp,%rbp   //建立main函数的栈帧
  40050b:   48 83 ec 20             sub    $0x20,%rsp   //为main函数分配栈帧空间,此处为32个字节
  40050f:   89 7d ec                mov    %edi,-0x14(%rbp)   //将系统传给main函数的第一个参数复制到自己的栈帧(可以查看值吗?)
  400512:   48 89 75 e0             mov    %rsi,-0x20(%rbp)   //将系统传给main函数的第二个参数复制到自己的栈帧,,并存放在栈帧的最顶部(可以查看值吗?)      

    int a,b,result;

    a = 1;
  400516:   c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)   //此处为给main函数的局部变量a赋值过程,将该值复制到main函数的栈帧中;使用movl表明这个变量是4个字节的,打在C代码第12行的断点,在汇编代码里实际断点在这里
    b = 2;
  40051d:   c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%rbp)   //此处为给main函数的局部变量b赋值过程,将该值复制到main函数的栈帧中;可以看出局部变量的地址是从低向高分配的
    result = add(a,b);
  400524:   8b 55 f8                mov    -0x8(%rbp),%edx
  400527:   8b 45 f4                mov    -0xc(%rbp),%eax
  40052a:   89 d6                   mov    %edx,%esi
  40052c:   89 c7                   mov    %eax,%edi
  40052e:   e8 ba ff ff ff          callq  4004ed <add>
  400533:   89 45 fc                mov    %eax,-0x4(%rbp)

    return 0;
  400536:   b8 00 00 00 00          mov    $0x0,%eax
}
  40053b:   c9                      leaveq
  40053c:   c3                      retq
  40053d:   0f 1f 00                nopl   (%rax)

接下来,我们对以上代码逐段分析:

int main(int argc, char *argv[]) {
  400507:   55                      push   %rbp   //建立main函数的栈帧
  400508:   48 89 e5                mov    %rsp,%rbp   //建立main函数的栈帧
  40050b:   48 83 ec 20             sub    $0x20,%rsp   //为main函数分配栈帧空间,此处为32个字节
  40050f:   89 7d ec                mov    %edi,-0x14(%rbp)   //将系统传给main函数的第一个参数复制到自己的栈帧(可以查看值吗?)
  400512:   48 89 75 e0             mov    %rsi,-0x20(%rbp)   //将系统传给main函数的第二个参数复制到自己的栈帧,,并存放在栈帧的最顶部(可以查看值吗?)      

此时通过:

(gdb) x/1xg $rbp-0x20
0x7fffffffe550: 0x00007fffffffe658
(gdb) x/1xw $rbp-0x14
0x7fffffffe55c: 0x00000001

我们发现传递给main函数的第一个参数的值是1,而第二个参数的值貌似是一个内存地址(值的引用/指针)。并且可以看出函数的实参列表(Arglist)在栈帧中的地址是从高向低分配的。
同时我们认为main函数的栈帧是从此时的$rbp+0x10开始的,也就是在main函数栈帧的底部里还保存了rbp和rip。
(很奇怪的是两个参数之间存的值竟然是_start函数的的4字节地址,这里是考虑了8个字节的对齐吗?)
接着看:

    int a,b,result;

    a = 1;
  400516:   c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)   //此处为给main函数的局部变量a赋值过程,将该值复制到main函数的栈帧中;使用movl表明这个变量是4个字节的
    b = 2;
  40051d:   c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%rbp)   //此处为给main函数的局部变量b赋值过程,将该值复制到main函数的栈帧中    

我们发现,打在C程序代码第12行的断点,在汇编代码实际断点在这里,也可以看出函数局部变量(Locals)的地址是从低向高分配的。而且一个有趣的现象是Arglist和Locals的起始地址都是-0x10(%rbp)。

接着看:

    result = add(a,b);
  400524:   8b 55 f8                mov    -0x8(%rbp),%edx   //这里把传递给add函数的第二个参数的值复制到寄存器edx
  400527:   8b 45 f4                mov    -0xc(%rbp),%eax   //这里把传递给add函数的第一个参数的值复制到寄存器eax
  40052a:   89 d6                   mov    %edx,%esi   //接着把传递给add函数的第二个参数值从edx寄存器复制到esi寄存器
  40052c:   89 c7                   mov    %eax,%edi   //接着把传递给add函数的第一个参数值从eax寄存器复制到edi寄存器
  40052e:   e8 ba ff ff ff          callq  4004ed <add>   //这里call命令让整个程序的执行流跳转到地址(地址长度是一个字节)0x4004ed(也就是add函数的地址)处,
  400533:   89 45 fc                mov    %eax,-0x4(%rbp)   //(暂停main函数的分析,进而分析add函数;注意这条指令的地址在该函数的上一条指令被执行前,存入eip寄存器了)

这里实际的情况印证了之前讲到的,rdi,rsi,rdx,rcx,r8d,r9d这6个寄存器会依次暂存主调函数传给被调函数的参数。而之所以中间还要转存到edx和eax是因为,cpu从寄存器中读取数据的速度远远大于从内存中读取数据的速度;为了提高性能,一旦内存中一个参数被使用,那么先会被暂存到一个空余的寄存器中,以后再使用时,就不用从内存中读取了。
我们也可以看出在存取传递给函数的参数时,是从右向左读取的。
eip的工作原理是,cpu读取当前eip指向的指令,存入指令缓冲器(指令队列)中,然后eip根据被读取指令的长度,增加相应的字节数,指向下一条指令,然后cpu执行指令队列中刚刚读取的指令。
call这个跳转指令在执行时,实际分为两步,一个是先pop该指令执行时eip的值(即主调函数的调用发生时的下一条指令地址)到当前函数的栈帧中(当前栈帧增长,esp的值会减小8个字节),然后程序的执行流跳转到相应的地址处,即eip的值等于相应的地址(此处即为add函数的地址处0x00000000004004ed)。

接着看add函数的内部:

00000000004004ed <add>:
int add(int a, int b) {
  4004ed:   55                      push   %rbp   //首先把主调函数的栈基址入栈(栈增长,esp的值减小8个字节)
  4004ee:   48 89 e5                mov    %rsp,%rbp   // 让当前基址指针指向主调函数的栈顶
  4004f1:   89 7d ec                mov    %edi,-0x14(%rbp)   //将主调函数传给add函数的第一个实参复制到add函数的栈帧
  4004f4:   89 75 e8                mov    %esi,-0x18(%rbp)   //将主调函数传给add函数的第二个实参复制到add函数的栈帧

系统并没有像在main函数里的那样,显式地给add函数分配栈帧空间,原因是add函数内并不调用其他函数,因此没有必要让esp的值再发生变化。所以实际上add函数的栈帧的顶部和其主调函数的栈顶重合。
同时我们认为add函数的栈帧开始于此时的$rbp+0x10,
(这里为什么要保留16个字节的空间没有使用呢?)

接着:

    int result;

    result = a + b;
  4004f7:   8b 45 e8                mov    -0x18(%rbp),%eax   //将加法运算的第二个操作数的值从栈中复制到eax寄存器
  4004fa:   8b 55 ec                mov    -0x14(%rbp),%edx   //将加法运算的第一个操作数的值从栈中复制到edx寄存器
  4004fd:   01 d0                   add    %edx,%eax   //执行加法运算,并将值保存在eax寄存器中
  4004ff:   89 45 fc                mov    %eax,-0x4(%rbp)   //将eax寄存器中的值(得到的和)复制到add函数的栈帧中(这个地址就是add函数的局部变量result的地址)

这里验证了之前讲到的,在使用内存中定义的一个值时,会先把它复制到一个寄存器中暂存起来。

接下来:

    return result;
  400502:   8b 45 fc                mov    -0x4(%rbp),%eax   //将返回值result复制到寄存器eax中
}
  400505:   5d                      pop    %rbp   //将add函数栈帧的栈顶值(上一个函数的栈基址)弹出到rbp寄存器中
  400506:   c3                      retq  //ret指令从栈中弹出地址,并跳转到这个地址,这里相当于把值弹出到eip寄存器中。

这里验证了前面讲的eax寄存器经常存储被调函数的返回值。执行到这里后,由于之前栈中压入的eip,跳转到 # callq 400ed <add> #指令的下一条指令。
然后:

  400533:   89 45 fc                mov    %eax,-0x4(%rbp)   //将被调函数的返回值复制到main函数栈帧中(局部变量result)

    return 0;
  400536:   b8 00 00 00 00          mov    $0x0,%eax   //main函数向它的主调函数返回值0
}
  40053b:   c9                      leaveq   //leave指令可以使栈做好返回准备,
  40053c:   c3                      retq  //同之前介绍的一样
  40053d:   0f 1f 00                nopl   (%rax)

leave指令相当于以下两条指令:

mov %rbp,%rsp
pop %rbp

函数调用栈分析

程序启动时的Linux堆栈
程序启动时的Linux堆栈

ref:

5.C程序中递归函数的汇编分析

以斐波那契为例

6.其他关于C程序的汇编话题

ref: http://www.zhihu.com/question/20849824

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 4,505评论 1 19
  • 原文作者 Sandeep.S英文原文 [https://www.ibiblio.org/gferg/ldp/GCC...
    JeffreyLi阅读 39,262评论 8 41
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,669评论 0 27
  • 第一话 末日 一个小小的公司职员,有一个小小的名字“豆丁”他像往常一样看着三手桑塔纳去上班,他心情格外的好,...
    草原牧歌_0b55阅读 263评论 1 1
  • 1、 女友特别爱逛街,经常混迹于各色各样的店里。 别人都是有目标的逛,她却是漫无目的,为了逛而逛。跟她逛街,简直要...
    老苹果2阅读 251评论 0 0