這是網易雲課堂linux內核分析課程的實驗報告linux
實驗的內容是經過編寫一段簡單的c程序,並分析其彙編代碼,以瞭解計算機是如何運行程序。vim
程序hello.c源代碼:bash
1 #include <stdio.h> 2 3 int bar(int a) 4 { 5 return a; 6 } 7 8 int foo(a) 9 { 10 return bar(a) + 1; 11 } 12 13 int main() 14 { 15 return foo(10) + 1; 16 }
在bash下輸入以下指令,得到源代碼的彙編hello.s:函數
gcc -S -o hello.s hello.c -m32
因爲本次試驗只須要用到源代碼對應的彙編指令,因此能夠把hello.s中以.
開頭的行刪除。在vim下可使用:spa
:%s/\s*\..*//g #將以"."開頭的行換爲空行 :%g/^$/d #刪除空行
操做後獲得如下代碼:code
1 bar: 2 pushl %ebp 3 movl %esp, %ebp 4 movl 8(%ebp), %eax 5 popl %ebp 6 ret 7 foo: 8 pushl %ebp 9 movl %esp, %ebp 10 subl $4, %esp 11 movl 8(%ebp), %eax 12 movl %eax, (%esp) 13 call bar 14 addl $1, %eax 15 leave 16 ret 17 main: 18 pushl %ebp 19 movl %esp, %ebp 20 subl $4, %esp 21 movl $10, (%esp) 22 call foo 23 addl $1, %eax 24 leave 25 ret
下面對彙編代碼進行分析,從main函數開始:
1八、19行的pushl
和movl
指令至關於enter
指令,用於保存前一個棧的信息,同時爲main函數開闢一個空棧。pushl
將前一個棧的基地址保存,movl
將ebp
賦值爲前一個棧的棧頂,同時也是main棧的基地址。esp
做爲main棧的棧頂。完成了上面兩部,main棧就算建成了。接下來開始執行源代碼裏的東西了。ip
20、21行。subl
爲函數的棧開了空間,用來存放foo須要的參數。movl
將參數放在該空間。而後執行call
指令,跳到foo函數中,也就是eip
要變成foo的地址。注意call
指令要把當前eip
壓棧(pushl %eip
)。而後把目光轉到foo函數中,也就是第7行。和main函數開頭同樣,須要保存上一個棧的信息,同時爲本身開創一個棧。pushl
和movl
指令作了這件事。接着放置bar須要的參數在本身的棧中(就是剛剛在mian函數時放的那個參數)。完成後執行call
指令跳到bar函數。內存
bar函數終於再也不調用別的函數,而是獲取foo給的參數(第4行,這時候ebp就是foo棧的棧頂)。將參數賦值給eax
後進行結束函數的工做。第5行的popl
指令將esp
加4,變爲前一個棧(foo)的棧頂;ebp
得到出棧的內容,也就是前一個棧的基址。foo的棧完成恢復。最後ret
指令跳回foo函數中(至關於popl %eip
),bar函數就正式結束了。回到foo函數後,根據源代碼要給返回值加1(14行),緊接着要結束foo。和結束bar函數過程同樣,結束foo函數就是執行leave
而後回跳到main中。main函數爲返回值加1(23行),以一樣的流程退出main函數。程序結束。get
注意1:函數的棧在內存中是從高地址向低地址增加的,因此pushl
後esp
要減4,而popl
後esp
要加4
注意2:leave
指令至關於movl %esp %ebp
,popl %ebp
。
因爲bar的代碼沒有使用到變量,因此在建立好函數的棧後,esp
和ebp
是相等的,不須要leave指令。函數foo和main中建立了變量,並存放在棧中,修改了esp,所以退出函數時要還原esp的。因此在退出函數的時候要使用leave
。
注意3:call
指令壓棧的內容在調用函數的棧中。畢竟call
指令執行在開闢新的函數棧指令以前。it
理解:
程序就是由一條條彙編代碼組成。這些彙編代碼執行了運算,使用了內存空間。c語言中的函數,就是彙編中將當前寄存器保存到內存中,而後轉跳到另一處執行,執行完成後跳回原來地方,並恢復寄存器內容。而c語言屏蔽了這一過程,提供了抽象,咱們只須要專一於函數要實現什麼功能,不須要關注如何實現函數。
過程截圖
xxtsmooc
原創做品轉載請註明出處
《Linux內核分析》MOOC課程
http://mooc.study.163.com/course/USTC-1000029000