從匯編角度淺析C程序

詳情請見: http://heamon7.gitbooks.io/cscw2-newly-to-assembly/content/

p1

Outline

  • 1.初探C程序的匯編級形式
  • 2.匯編語言及其相關(guān)指令簡介
  • 3.用匯編分析C程序的工具介紹
  • 4.再探C程序的匯編級形式
  • 5.C程序中遞歸函數(shù)的匯編分析
  • 6.其他基于C程序的匯編話題

p2

1. 初探C程序的匯編級形式

  • 1.1用gcc將C程序編譯成匯編代碼并查看
  • 1.2C程序到可執(zhí)行文件的編譯鏈接過程
  • 1.3程序被執(zhí)行時的內(nèi)存分配情況概覽

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 ,關(guān)鍵部分:

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程序到可執(zhí)行程序的編譯鏈接過程

gcc代理的編譯過程

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

1.3程序被執(zhí)行時的內(nèi)存分配情況概覽

圖解:

進程內(nèi)存區(qū)域的分布

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.匯編語言及其相關(guān)指令簡介

  • 2.1.匯編語言簡介
  • 2.2 8086,x86,x86-64寄存器簡介
  • 2.3 AT&T風(fēng)格x86-64匯編指令

p6

2.1.匯編語言簡介

匯編語言采用了助記符(mnemonics)來代表特定低級機器語言的操作。特定的匯編目標(biāo)指令集可能會包括特定的操作數(shù)。許多匯編程序可以識別代表地址和常量的標(biāo)簽(label)和符號(symbols),這樣就可以用字符來代表操作數(shù)而無需采取寫死的方式。普遍地說,特定的匯編語言和特定的機器語言指令集是一一對應(yīng)的。

匯編指令的兩大風(fēng)格分別是Intel匯編與AT&T匯編,分別被Microsoft Windows/Visual C++GNU/Gas采用(Gas也可使用Intel匯編風(fēng)格)

我們這里不介紹他們的使用區(qū)別,而直接介紹Linux下默認(rèn)的AT&T匯編風(fēng)格。
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年開始設(shè)計,1978年年中發(fā)布的Intel第一款16位微處理器,同時也是x86架構(gòu)之開端。

所有的內(nèi)部寄存器、內(nèi)部及外部數(shù)據(jù)總線都是16位寬,因此是完全的16位微處理器。20位外部地址總線,因此物理尋址空間為1MB (即2^20
= 1,048,576).由于內(nèi)部寄存器都是16位,對1M地址空間尋址時采取了段尋址方式。8086的封裝采用40引腳的雙列直插(dual in-line),數(shù)據(jù)總線與地址總線復(fù)用了前16個引腳。16位的I/O地址,因此獨立的I/O尋址空間為64KB (即2^16
= 65,536).由于8086內(nèi)部的地址寄存器是16 位寬,因而最大線性尋址空間為64 KB.使用超過64 KB內(nèi)存空間的程序設(shè)計時,需要調(diào)整段寄存器(segment registers)。直到32位的80386出現(xiàn)之前,8086的這種段尋址相當(dāng)不便.

寄存器

8086有8個16比特的寄存器,包括棧寄存器SP與BP,但不包括指令寄存器IP、控制寄存器FLAGS以及四個段寄存器。AX, BX, CX, DX,這四個寄存器可以按照字節(jié)訪問;但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,最初發(fā)布于1985年10月17日。
80386處理器被廣泛應(yīng)用在1980年代中期到1990年代中期的IBM PC兼容機中。這些PC被稱為“80386電腦”或“386電腦”,有時也簡稱“80386”或“386”。
80386的重要特點是:

  • 首次在x86處理器中實現(xiàn)了32位元系統(tǒng)(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架構(gòu)。x64於1999年由AMD設(shè)計,AMD首次公開64位元集以擴充給x86,稱為「AMD64」。其後也為英特爾所採用,現(xiàn)時英特爾稱之為「Intel 64」,在之前曾使用過「Clackamas Technology」 (CT)、「IA-32e」及「EM64T」。
Applerpm 以「x86-64」或「x86_64」稱呼此64位架構(gòu)。太陽電腦(已被甲骨文公司收購)及 Microsoft 稱之為「x64」。BSD 家族及其他 Linux發(fā)行版則使用「amd64」,32位元版本則稱為「i386」(或 i486/586/686)。
在x86-64出現(xiàn)以前,英特爾與惠普聯(lián)合設(shè)計出IA-64架構(gòu);惟IA-64並不與x86兼容,且市場反應(yīng)較冷淡,同時受制於多個專利權(quán),使其他廠商不能模仿。與x86兼容的AMD64架構(gòu)便應(yīng)運而生,其主要特點如名稱所述,既有支援64位通用暫存器、64位整數(shù)及邏輯運算、以及64位虛擬位址,設(shè)計人員又為架構(gòu)作出不少改進,部份重大改變?nèi)缦拢?/p>

  • 新增暫存器

寄存器

x86-64寄存器

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

2.3 AT&T風(fēng)格x86-64匯編指令

  1. 操作數(shù)指示符
操作數(shù)格式
操作數(shù)格式

尋址模式

ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)

它所表示的地址可以這樣計算出來:

FINAL ADDRESS = ADDRESS_OR_OFFSET + BASE_OR_OFFSET + MULTIPLIER * INDEX

其中ADDRESS_OR_OFFSET和MULTIPLIER必須是常數(shù),BASE_OR_OFFSET和INDEX必須是寄存器。在有些尋址方式中會省略這4項中的某些項,相當(dāng)于這些項是0。

  • 直接尋址(Direct Addressing Mode)。只使用ADDRESS_OR_OFFSET尋址,例如movl ADDRESS, %eax
    把ADDRESS地址處的32位數(shù)傳送到eax
    寄存器。
  • 變址尋址(Indexed Addressing Mode) 。上一節(jié)的movl data_items(,%edi,4), %eax
    就屬于這種尋址方式,用于訪問數(shù)組元素比較方便。
  • 間接尋址(Indirect Addressing Mode)。只使用BASE_OR_OFFSET尋址,例如movl (%eax), %ebx,把eax寄存器的值看作地址,把這個地址處的32位數(shù)傳送到ebx寄存器。注意和movl %eax, %ebx區(qū)分開。
  • 基址尋址(Base Pointer Addressing Mode)。只使用ADDRESS_OR_OFFSET和BASE_OR_OFFSET尋址,例如movl 4(%eax), %ebx ,用于訪問結(jié)構(gòu)體成員比較方便,例如一個結(jié)構(gòu)體的基地址保存在eax 寄存器中,其中一個成員在結(jié)構(gòu)體內(nèi)的偏移量是4字節(jié),要把這個成員讀上來就可以用這條指令。
  • 立即數(shù)尋址(Immediate Mode)。就是指令中有一個操作數(shù)是立即數(shù),例如movl $12, %eax 中的$12 ,這其實跟尋址沒什么關(guān)系,但也算作一種尋址方式。
  • 寄存器尋址(Register Addressing Mode)。就是指令中有一個操作數(shù)是寄存器,例如movl $12, %eax 中的%eax ,這跟內(nèi)存尋址沒什么關(guān)系,但也算作一種尋址方式。在匯編程序中寄存器用助記符來表示,在機器指令中則要用幾個Bit表示寄存器的編號,這幾個Bit也可以看作寄存器的地址,但是和內(nèi)存地址不在一個地址空間。

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

2.數(shù)據(jù)傳送指令


數(shù)據(jù)傳送指令
數(shù)據(jù)傳送指令
mov S,D
push S
pop D

3.算術(shù)和邏輯運算指令


算術(shù)和邏輯運算指令
算術(shù)和邏輯運算指令

4.過程調(diào)用指令


過程調(diào)用指令
過程調(diào)用指令

上面這三條指令每一條都相當(dāng)于幾條指令

3.用匯編分析C程序的工具介紹

  • 3.1 gcc
  • 3.2 gdb&&ddd

gcc

C 編程中相關(guān)文件后綴

.a 靜態(tài)庫 (archive)
.c C源代碼(需要編譯預(yù)處理)
.h C源代碼頭文件
.i C源代碼(不需編譯預(yù)處理)
.o 對象文件
.s 匯編語言代碼
.so 動態(tài)庫

常用命令:

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編譯鏈接得到目標(biāo)文件:

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)

接著打斷點,進行常規(guī)調(diào)試,查看程序的運行的情況:

(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)

接著我們看一下從機器級層面對代碼進行調(diào)試,查看程序的運行情況:

(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)

這里的運行情況和我們常規(guī)調(diào)試看到的并不一樣,我們回到之前的匯編代碼,分析一下每一步在干什么。
為了方便查看源C代碼和匯編代碼的對應(yīng)關(guān)系,我們利用反匯編工具objdump得到的反匯編代碼,來分析程序執(zhí)行過程。
從目標(biāo)文件反匯編得到的匯編代碼:

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函數(shù)的棧幀
  400508:   48 89 e5                mov    %rsp,%rbp   //建立main函數(shù)的棧幀
  40050b:   48 83 ec 20             sub    $0x20,%rsp   //為main函數(shù)分配棧幀空間,此處為32個字節(jié)
  40050f:   89 7d ec                mov    %edi,-0x14(%rbp)   //將系統(tǒng)傳給main函數(shù)的第一個參數(shù)復(fù)制到自己的棧幀(可以查看值嗎?)
  400512:   48 89 75 e0             mov    %rsi,-0x20(%rbp)   //將系統(tǒng)傳給main函數(shù)的第二個參數(shù)復(fù)制到自己的棧幀,,并存放在棧幀的最頂部(可以查看值嗎?)      

    int a,b,result;

    a = 1;
  400516:   c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)   //此處為給main函數(shù)的局部變量a賦值過程,將該值復(fù)制到main函數(shù)的棧幀中;使用movl表明這個變量是4個字節(jié)的,打在C代碼第12行的斷點,在匯編代碼里實際斷點在這里
    b = 2;
  40051d:   c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%rbp)   //此處為給main函數(shù)的局部變量b賦值過程,將該值復(fù)制到main函數(shù)的棧幀中;可以看出局部變量的地址是從低向高分配的
    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函數(shù)的棧幀
  400508:   48 89 e5                mov    %rsp,%rbp   //建立main函數(shù)的棧幀
  40050b:   48 83 ec 20             sub    $0x20,%rsp   //為main函數(shù)分配棧幀空間,此處為32個字節(jié)
  40050f:   89 7d ec                mov    %edi,-0x14(%rbp)   //將系統(tǒng)傳給main函數(shù)的第一個參數(shù)復(fù)制到自己的棧幀(可以查看值嗎?)
  400512:   48 89 75 e0             mov    %rsi,-0x20(%rbp)   //將系統(tǒng)傳給main函數(shù)的第二個參數(shù)復(fù)制到自己的棧幀,,并存放在棧幀的最頂部(可以查看值嗎?)      

此時通過:

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

我們發(fā)現(xiàn)傳遞給main函數(shù)的第一個參數(shù)的值是1,而第二個參數(shù)的值貌似是一個內(nèi)存地址(值的引用/指針)。并且可以看出函數(shù)的實參列表(Arglist)在棧幀中的地址是從高向低分配的。
同時我們認(rèn)為main函數(shù)的棧幀是從此時的$rbp+0x10開始的,也就是在main函數(shù)棧幀的底部里還保存了rbp和rip。
(很奇怪的是兩個參數(shù)之間存的值竟然是_start函數(shù)的的4字節(jié)地址,這里是考慮了8個字節(jié)的對齊嗎?)
接著看:

    int a,b,result;

    a = 1;
  400516:   c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)   //此處為給main函數(shù)的局部變量a賦值過程,將該值復(fù)制到main函數(shù)的棧幀中;使用movl表明這個變量是4個字節(jié)的
    b = 2;
  40051d:   c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%rbp)   //此處為給main函數(shù)的局部變量b賦值過程,將該值復(fù)制到main函數(shù)的棧幀中    

我們發(fā)現(xiàn),打在C程序代碼第12行的斷點,在匯編代碼實際斷點在這里,也可以看出函數(shù)局部變量(Locals)的地址是從低向高分配的。而且一個有趣的現(xiàn)象是Arglist和Locals的起始地址都是-0x10(%rbp)。

接著看:

    result = add(a,b);
  400524:   8b 55 f8                mov    -0x8(%rbp),%edx   //這里把傳遞給add函數(shù)的第二個參數(shù)的值復(fù)制到寄存器edx
  400527:   8b 45 f4                mov    -0xc(%rbp),%eax   //這里把傳遞給add函數(shù)的第一個參數(shù)的值復(fù)制到寄存器eax
  40052a:   89 d6                   mov    %edx,%esi   //接著把傳遞給add函數(shù)的第二個參數(shù)值從edx寄存器復(fù)制到esi寄存器
  40052c:   89 c7                   mov    %eax,%edi   //接著把傳遞給add函數(shù)的第一個參數(shù)值從eax寄存器復(fù)制到edi寄存器
  40052e:   e8 ba ff ff ff          callq  4004ed <add>   //這里call命令讓整個程序的執(zhí)行流跳轉(zhuǎn)到地址(地址長度是一個字節(jié))0x4004ed(也就是add函數(shù)的地址)處,
  400533:   89 45 fc                mov    %eax,-0x4(%rbp)   //(暫停main函數(shù)的分析,進而分析add函數(shù);注意這條指令的地址在該函數(shù)的上一條指令被執(zhí)行前,存入eip寄存器了)

這里實際的情況印證了之前講到的,rdi,rsi,rdx,rcx,r8d,r9d這6個寄存器會依次暫存主調(diào)函數(shù)傳給被調(diào)函數(shù)的參數(shù)。而之所以中間還要轉(zhuǎn)存到edx和eax是因為,cpu從寄存器中讀取數(shù)據(jù)的速度遠(yuǎn)遠(yuǎn)大于從內(nèi)存中讀取數(shù)據(jù)的速度;為了提高性能,一旦內(nèi)存中一個參數(shù)被使用,那么先會被暫存到一個空余的寄存器中,以后再使用時,就不用從內(nèi)存中讀取了。
我們也可以看出在存取傳遞給函數(shù)的參數(shù)時,是從右向左讀取的。
eip的工作原理是,cpu讀取當(dāng)前eip指向的指令,存入指令緩沖器(指令隊列)中,然后eip根據(jù)被讀取指令的長度,增加相應(yīng)的字節(jié)數(shù),指向下一條指令,然后cpu執(zhí)行指令隊列中剛剛讀取的指令。
call這個跳轉(zhuǎn)指令在執(zhí)行時,實際分為兩步,一個是先pop該指令執(zhí)行時eip的值(即主調(diào)函數(shù)的調(diào)用發(fā)生時的下一條指令地址)到當(dāng)前函數(shù)的棧幀中(當(dāng)前棧幀增長,esp的值會減小8個字節(jié)),然后程序的執(zhí)行流跳轉(zhuǎn)到相應(yīng)的地址處,即eip的值等于相應(yīng)的地址(此處即為add函數(shù)的地址處0x00000000004004ed)。

接著看add函數(shù)的內(nèi)部:

00000000004004ed <add>:
int add(int a, int b) {
  4004ed:   55                      push   %rbp   //首先把主調(diào)函數(shù)的棧基址入棧(棧增長,esp的值減小8個字節(jié))
  4004ee:   48 89 e5                mov    %rsp,%rbp   // 讓當(dāng)前基址指針指向主調(diào)函數(shù)的棧頂
  4004f1:   89 7d ec                mov    %edi,-0x14(%rbp)   //將主調(diào)函數(shù)傳給add函數(shù)的第一個實參復(fù)制到add函數(shù)的棧幀
  4004f4:   89 75 e8                mov    %esi,-0x18(%rbp)   //將主調(diào)函數(shù)傳給add函數(shù)的第二個實參復(fù)制到add函數(shù)的棧幀

系統(tǒng)并沒有像在main函數(shù)里的那樣,顯式地給add函數(shù)分配棧幀空間,原因是add函數(shù)內(nèi)并不調(diào)用其他函數(shù),因此沒有必要讓esp的值再發(fā)生變化。所以實際上add函數(shù)的棧幀的頂部和其主調(diào)函數(shù)的棧頂重合。
同時我們認(rèn)為add函數(shù)的棧幀開始于此時的$rbp+0x10,
(這里為什么要保留16個字節(jié)的空間沒有使用呢?)

接著:

    int result;

    result = a + b;
  4004f7:   8b 45 e8                mov    -0x18(%rbp),%eax   //將加法運算的第二個操作數(shù)的值從棧中復(fù)制到eax寄存器
  4004fa:   8b 55 ec                mov    -0x14(%rbp),%edx   //將加法運算的第一個操作數(shù)的值從棧中復(fù)制到edx寄存器
  4004fd:   01 d0                   add    %edx,%eax   //執(zhí)行加法運算,并將值保存在eax寄存器中
  4004ff:   89 45 fc                mov    %eax,-0x4(%rbp)   //將eax寄存器中的值(得到的和)復(fù)制到add函數(shù)的棧幀中(這個地址就是add函數(shù)的局部變量result的地址)

這里驗證了之前講到的,在使用內(nèi)存中定義的一個值時,會先把它復(fù)制到一個寄存器中暫存起來。

接下來:

    return result;
  400502:   8b 45 fc                mov    -0x4(%rbp),%eax   //將返回值result復(fù)制到寄存器eax中
}
  400505:   5d                      pop    %rbp   //將add函數(shù)棧幀的棧頂值(上一個函數(shù)的棧基址)彈出到rbp寄存器中
  400506:   c3                      retq  //ret指令從棧中彈出地址,并跳轉(zhuǎn)到這個地址,這里相當(dāng)于把值彈出到eip寄存器中。

這里驗證了前面講的eax寄存器經(jīng)常存儲被調(diào)函數(shù)的返回值。執(zhí)行到這里后,由于之前棧中壓入的eip,跳轉(zhuǎn)到 # callq 400ed <add> #指令的下一條指令。
然后:

  400533:   89 45 fc                mov    %eax,-0x4(%rbp)   //將被調(diào)函數(shù)的返回值復(fù)制到main函數(shù)棧幀中(局部變量result)

    return 0;
  400536:   b8 00 00 00 00          mov    $0x0,%eax   //main函數(shù)向它的主調(diào)函數(shù)返回值0
}
  40053b:   c9                      leaveq   //leave指令可以使棧做好返回準(zhǔn)備,
  40053c:   c3                      retq  //同之前介紹的一樣
  40053d:   0f 1f 00                nopl   (%rax)

leave指令相當(dāng)于以下兩條指令:

mov %rbp,%rsp
pop %rbp

函數(shù)調(diào)用棧分析

程序啟動時的Linux堆棧
程序啟動時的Linux堆棧

ref:

5.C程序中遞歸函數(shù)的匯編分析

以斐波那契為例

6.其他關(guān)于C程序的匯編話題

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容