標籤:
SJTUBEAR 原創作品轉載請註明出處 /《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
1. 彙編
在修習LINUX核心這門課的初始階段,首先需要掌握的就是彙編以及組譯工具對於堆棧的操作。
下面我們就來分析一下一個簡單地C程式是如何被組譯工具所表達的!
2. 得到彙編代碼
首先,我們寫一個簡單地C程式,命名為exp1.c:
1 #include <stdio.h> 2 3 int g(int x) 4 { 5 return x+3; 6 } 7 8 int f(x) 9 {10 return g(x);11 }12 13 int main()14 {15 return f(8)+1; 16 }
程式非常的簡單,我們此時再通過編譯指令將其編譯為組譯工具:
1 gcc –S –o main.s main.c -m32
這樣我們就得到了這個簡單C程式的彙編代碼:
1 .file "exp1.c" 2 .text 3 .globl g 4 .type g, @function 5 g: 6 .LFB0: 7 .cfi_startproc 8 pushl %ebp 9 .cfi_def_cfa_offset 810 .cfi_offset 5, -811 movl %esp, %ebp12 .cfi_def_cfa_register 513 movl 8(%ebp), %eax14 addl $3, %eax15 popl %ebp16 .cfi_def_cfa 4, 417 .cfi_restore 518 ret19 .cfi_endproc20 .LFE0:21 .size g, .-g22 .globl f23 .type f, @function24 f:25 .LFB1:26 .cfi_startproc27 pushl %ebp28 .cfi_def_cfa_offset 829 .cfi_offset 5, -830 movl %esp, %ebp31 .cfi_def_cfa_register 532 subl $4, %esp33 movl 8(%ebp), %eax34 movl %eax, (%esp)35 call g36 leave37 .cfi_restore 538 .cfi_def_cfa 4, 439 ret40 .cfi_endproc41 .LFE1:42 .size f, .-f43 .globl main44 .type main, @function45 main:46 .LFB2:47 .cfi_startproc48 pushl %ebp49 .cfi_def_cfa_offset 850 .cfi_offset 5, -851 movl %esp, %ebp52 .cfi_def_cfa_register 553 subl $4, %esp54 movl $8, (%esp)55 call f56 addl $1, %eax57 leave58 .cfi_restore 559 .cfi_def_cfa 4, 460 ret61 .cfi_endproc62 .LFE2:63 .size main, .-main64 .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"65 .section .note.GNU-stack,"",@progbits
3.彙編程式碼分析
彙編出的代碼,多了很多輔助資訊,為了能夠更好地看清主幹,我們刪減一下:
1 g: 2 pushl %ebp //儲存現場,將父函數的棧底寄存器存入當前程式棧中 3 movl %esp, %ebp //構建當前函數堆棧 4 movl 8(%ebp), %eax //從父函數堆棧中取得參數,存入ax寄存器 5 addl $3, %eax //完成+3操作 6 popl %ebp //恢複原父函數堆棧 7 ret //pop出原EIP地址,恢複執行 8 f: 9 pushl %ebp //儲存現場,將父函數的棧底寄存器存入當前程式棧中10 movl %esp, %ebp //構建當前函數堆棧11 subl $4, %esp //棧頂加一,用以儲存變數傳遞給g函數12 movl 8(%ebp), %eax //取得參數13 movl %eax, (%esp) //將參數傳入變數位置14 call g //調用g15 leave //清楚局部變數空間16 ret //返回17 main:18 pushl %ebp19 movl %esp, %ebp20 subl $4, %esp //空出局部變數空間21 movl $8, (%esp) //為變數賦值22 call f //調用f23 addl $1, %eax //完成+1操作24 leave //清理局部變數25 ret //返回
我們對f函數進行詳細的分析:
1. 首先進行enter指令:
此時,ebp當前所指向的位置存入棧頂,並且將ebp重新導向指向esp:
2.棧頂加一併存入變數值:
3.調用g
4.從g返回後,傳回值儲存在AX寄存器中,不用操作,調用leave,清理變數
5.最後ret,同時EIP被讀出恢複到原位置繼續執行,傳回值在AX中傳遞給調用函數
3.個人的一點感悟:
程式的調用就是這樣嵌套的執行下去,每個函數都有自己的堆棧用以儲存當前變數以及環境值,並通過將父函數的EBP放入棧底用以恢複環境。
同時EIP存入父棧棧頂,便於恢複到原節點處繼續執行。
這樣,就可以有規律的一直嵌套下去。
如果使用遞迴函式,就是一個碼堆棧的過程,知道最頂部的堆棧返回,函數就像多米諾骨牌一樣收回所有的堆棧。
這也是遞迴函式佔用空間比較多的原因之一。如果沒有很好地退出機制,有可能記憶體溢出。
C程式彙編運行模式簡析