Lab 1 Exercise 10
為了能夠更好的了解在x86上的C程序調用過程的細節,我們首先找到在obj/kern/kern.asm中test_backtrace子程序的地址, 設置斷點,並且探討一下在內核啟動后,這個程序被調用時發生了什么。對於這個循環嵌套調用的程序test_backtrace,它一共壓入了多少信息到堆棧之中。並且它們都代表什么含義?
答:
先找到這個子程序的地址,打開obj/kern/kern.asm。在這個文件中我們查到調用test_backtrace子程序指令的地址為0xf0100040.
所以我們在這里設置斷點,並且開始調試。
首先看一下test_backtrace的C語言形式:
void test_backtrace(int x) { cprintf("entering test_backtrace %d\n", x); if (x > 0) test_backtrace(x-1); else mon_backtrace(0, 0, 0); cprintf("leaving test_backtrace %d\n", x); }
可見這個子程序是一個循環調用,在每一層循環中先打印信息 "entering test_backtrace x",然后對test_backtrace進行循環調用。當被循環調用完成,再打印信息 "leaving test_backtrace x"。所以最先進入的程序會最后退出。由於在init中對這個函數調用的次數為設置為5,所以屏幕上會打印出下列信息。
下面具體看一些每次調用所執行的匯編程序,當每一次進入test_backtrace后,剛剛開始時,它要完成之前介紹過的過程調用的通用操作,如下:
1 push %ebp 2 mov %esp, %ebp 3 push %ebx 4 sub $0x14, %esp
這四個操作將被用於存放調用這個子程序的父程序的棧幀信息,以及為當前子程序分配新的棧幀。在Exercise 1.9中,我們已經討論過,entry.S文件中為整個內核設置了堆棧空間的地址范圍,從0xf0108000-0xf0110000。由於堆棧是向下增長的,所以在運行init函數之前,esp寄存器的值就是0xf0110000,代表堆棧尚未使用。進入i386_init函數后,如果要調用某個子程序,就會把 i386_init 程序的棧幀信息壓入到這個堆棧空間中。
在 i386_init 函數中,運行了子程序test_backtrace(5)。
當運行test_backtrace(5)之前,esp寄存器ebp寄存器的值分別為如下:
esp : 0xf010ffe0 ebp : 0xf010fff8
0xf010ffe0~0xf010fff8就是當前i386_init子程序的棧幀,當計算機要調用test_backtrace(5)程序時,
首先call指令把i386_init的返回地址壓入堆棧中,所以esp變為0xf010ffdc,然后進入test_backtrace(5)子程序。
子程序中第一句push %ebp,把i386_init的ebp寄存器的值壓入堆棧中,即地址0xf010ffd8處,此時esp的值變為0xf010ffd8。
然后 mov %esp, %ebp 把ebp的值更新為esp的值,0xf010ffd8。這個就是當前test子程序的ebp寄存器的值。即它的棧幀的高地址邊界。
然后 push %ebx 把ebx寄存器的值壓入堆棧,此時esp變為0xf010ffd4。因為%ebx寄存器可能被這個子程序所使用,所以必須把它之前的值保留。
然后 sub $0x14, %esp 把esp中的值減去0x14=20,esp的值變為0xf010ffc0。這就是給test子程序分配一個大小為20個存儲單元的額外的棧幀空間,供它存儲一些臨時變量的值。
所以上述4條匯編指令,執行完成后,esp,ebp寄存器的值變化為
esp : 0xf010ffc0 ebp:0xf010ffd8
這就是test_backtrace(5)子程序運行時的棧幀地址范圍。而輸入參數'5'的值存放在0xf010ffe0單元處。
緊接着就是調用test_backtrace(4),由於它和test_backtrace(5)是一樣的,只不過現在是在test_backtrace(5)中調用test_backtrace(4),所以調用后內存中會同時存在二者的棧幀,test_backtrace(4)的棧幀就在test_backtrace(5)的棧幀之后。test_backtrace(4)中 esp, ebp的值也可以像我們分析test_backtrace(5)一樣被分析出來,如下:
esp:0xf010ffa0 ebp:0xf010ffb8
同理test_backtrace(3)中:
esp:0xf010ff80 ebp:0xf010ff98
test_backtrace(2)中:
esp:0xf010ff60 ebp:0xf010ff78
test_backtrace(1)中:
esp:0xf010ff40 ebp:0xf010ff58
test_backtrace(0)中:
esp:0xf010ff20 ebp:0xf010ff38
對於任意一層調用,比如test_backtrace(i),它的esp和ebp的值假設分別為esp(i)和ebp(i)。那么在這個棧幀范圍內主要存在這么幾個重要的值:
首先ebp(i)所指向的內存單元處存放着上一層程序的ebp寄存器的值,即ebp(i-1)。
另外在esp(i)所指向的內存單元處存放着對下一層子程序調用時傳入的參數,即i+1