標籤:語言 機器 賦值 回退 cal rem 刪除 抽象 alt
孟寧《Linux核心分析》第一周實驗
Zou Le
原創作品轉載請註明出處。
課程資訊:
《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
---------------------------實驗本文---------------------------
本實驗在實驗樓64位LIinux虛擬機器下進行。
C代碼如下:
int increment5(int x) { return x + 5;}int solve(int x) { return increment5(x) - 2;}int main(void) { return solve(2017);}
C程式的執行邏輯為:以main函數為入口,調用solve函數,字面值2017作為solve函數的參數,在solve函數中執行調用increment5函數,increment5函數接受2017,並加上5,返回solve函數再減去2,最後回到main函數返回該值。
通過同目錄下-S和-m32參數得到的彙編代碼main.s,將彙編代碼中以’.’開頭的行刪除後得到的代碼如下:
increment5: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax addl $5, %eax popl %ebp retsolve: pushl %ebp movl %esp, %ebp pushl 8(%ebp) call increment5 addl $4, %esp subl $2, %eax leave retmain: pushl %ebp movl %esp, %ebp pushl $2017 call solve addl $4, %esp leave ret
實驗如下:
由教材CSAPP第三章可知,記憶體為單個過程(函數)分配的那部分棧稱為棧幀。最頂端的棧幀由兩個指標界定,分別為%ebp指向棧底,%esp指向棧頂,每次push命令都會讓%esp減去4(記憶體中棧向下增長),然後在新的地址寫入值。pop則先讀出當前%esp位置的值,再將棧減去4。
----------------程式運行時記憶體棧的變化描述---------------
首先給出帶行號的代碼作為基準。
本彙編代碼執行的過程為:
可以觀察到每次調用函數,首先執行的命令為
pushl %ebp
movl %esp, %ebp
若記憶體中main函數入口時esp和ebp都指向第0格。則這兩行代碼所做的事情就是將第0格的資訊儲存到第1格,然後將棧的原點ebp挪到第1格,以第1格作為本過程的棧底。
第20行,所做的事情為將字面值2017寫入第2格,此時ebp指向第1格,esp指向第2格。
第21行,call命令將eip=22的命令寫入第3格,然後令eip=9。
第9行和第10行,進入solve函數,先建立棧幀:將main函數的棧底資訊(棧底=1)儲存到第4格,然後以第4格作為本過程的棧底。注意到,此時8(%ebp)指向的是第2格,即solve函數的參數。對於單參數的函數,往往進入新函數後,其參數所在的位置就是8(%ebp),%ebp本身儲存了上一個(要返回的函數)的棧底資訊,再往前一格儲存了eip指標要返回的位置。
第11行,esp再次增加四,在第5格寫入字面值2017。
第12行,call命令將eip=13儲存到第6格,然後令eip=2。
第2行和第3行,建立棧幀,將slove函數的棧底資訊(棧底=4)儲存到第7格,然後以第7格作為本函數的棧底。8(%ebp)隨即指向第5格的值,即2017。
第4行,將第5格的值2017寫入eax寄存器中。
第5行,將eax中的值增加5。
第6行,將第7格(棧底=4)的資訊賦值給ebp,即又讓ebp指向第4格,同時esp回退1,指向第6格。
第7行,ret的語意為 popl %eip,即此時esp指向第6格,內容為eip指向13。該命令修改eip指向第13行,並使得esp回退到第5格。
第13行,將esp再回退1格,指向第4格。(此時棧頂和棧低都是第4格)。注意到,每次call命令後都會跟一個addl $4, %esp。在本例子中,均為回退掉為call函數所準備的單個函數參數。
第14行,%eax中減去2。
第15行,leave指令。leave指令的語意為先movl %ebp, %esp,然後popl %ebp。本行中%ebp儲存的資訊為main函數的棧底(第1格),合起來的作用為讓%ebp重新指向main函數的棧底1,並且esp指向第3格(eip=22)。
第16行,ret。此行令eip重新回到main函數,程式的第22行,同時esp回退到第2格。
第22行,將esp回退一格,回到第1格。
第23行,令棧底等於0,棧頂也等於0。
第24行程式結束,%eax中儲存最後的結果。
具體記憶體資訊和ebp、esp變化如下所示:
由上述過程可知,相比於進階語言如C語言,組合語言有很大一部分繁瑣的底層的函數調用,傳值的資訊。如我們所說的函數調用棧,是通過儲存上級函數繼續執行的地址和上級函數的原來的棧地址後,建立新的棧底來完成的。
通過本次實驗,我對32位彙編中常出現的8(%ebp)和leave+ret有了“感覺”,感受到了C語言相對於彙編所做的抽象工作的重要性,也對程式的機器級表示有了初步的認識。
[Linux核心分析第一周課程] 由C語言程式的彙編表示觀察CPU寄存器與記憶體的互動