<!-- @page { margin: 0.79in } P { margin-bottom: 0.08in } -->
先寫一段程序,用的編輯環境爲 Netbeans 6.9, gcc, gdb:
int main(int argc, char** argv) {
int i=1;
i++;
return (EXIT_SUCCESS);
}
編譯爲彙編指令爲
!int main(int argc, char** argv) {
main+1: mov %esp,%ebp
! int i=1;
main()
main+6: movl $0x1,-0x4(%ebp)
! i++;
main+13: addl $0x1,-0x4(%ebp)
! return (EXIT_SUCCESS);
main+17: mov $0x0,%eax
!}
main+23: ret
!應該爲註釋,標記正在進行到代碼的哪一個步驟
看 main+0: push %ebp。
ebp是基址指針寄存器。這裏應該是說將原來的 ebp壓入棧,這裏的 ebp應該是作爲一個棧的基地址而從棧裏面取地址就要用 ebp做基準,所以這裏存入上一級棧的 ebp。
看: main+1: mov %esp,%ebp
這裏把 esp的值傳給 ebp,也就是說 ebp現在可以當作目前這一級棧的基地址,而因爲 esp經常改變,所以要用 ebp來做基址。
看 main+3: sub $0x10,%esp
這裏因爲有一個局部變量 i,所以要把 esp向上調,不知道 esp是不是就是指棧頂的意思?這裏貌似還要 RoundUp變到 16 的倍數,所以總是 0xn0。如果有 5個局部變量,就會移動 x020。(這裏還是有問題,見下一個例子)
看 main()
看 main+6: movl $0x1,-0x4(%ebp)
這裏可以看到將 1付給 i。這裏用到了偏址 4,得到 i的指,這裏很奇怪爲什麼 i在那個位置。注意這裏只有當運行到這一個語句的時候纔會將 i的值壓棧,而 esp會預留全部的局部變量的空間。
看 main+13: addl $0x1,-0x4(%ebp)
這裏將 i的值加上 1。
看 main+17: mov $0x0,%eax
這裏將 0付給 eax寄存器,可見這裏 eax是存儲返回值的寄存器。
看: main+22: leave
main+23: ret
這裏應該是函數返回。
如果有一個語句爲 j=i+j
則對應於彙編爲
main+24: mov -0x4(%ebp),%eax//將 i的值付給 eax。
main+27: add %eax,-0x8(%ebp) //將 eax付給 j。可見這裏 eax也是可以作爲臨時寄存器。
繼續編一段程序:
int add(int i,int p){
int j=i+p;
return j;
}
int main(int argc, char** argv) {
int i=1;
i=add(i,8);
return (i);
}
main+0: push %ebp
main+1: mov %esp,%ebp
main+3: sub $0x18,%esp
! int i=1;
main+6: movl $0x1,-0x4(%ebp)
!
! i=add(i,8);
main+13: movl $0x8,0x4(%esp)
main+21: mov -0x4(%ebp),%eax
main+24: mov %eax,(%esp)
main+27: call 0x80483b4 <add>
main()
main+32: mov %eax,-0x4(%ebp)
!
! return (i);
main+35: mov -0x4(%ebp),%eax
!}
main+38: leave
main+39: ret
!int add(int i,int p){
add()
add+0: push %ebp
add+1: mov %esp,%ebp
add+3: sub $0x10,%esp
! int j=i+p;
add+9: mov 0x8(%ebp),%edx
add+12: lea (%edx,%eax,1),%eax
add+15: mov %eax,-0x4(%ebp)
! return j;
add+18: mov -0x4(%ebp),%eax
!}
add+21: leave
add+22: ret
看 main+13: movl $0x8,0x4(%esp)
main+21: mov -0x4(%ebp),%eax
main+24: mov %eax,(%esp)
這裏其實就是把參數壓棧,把 8和 i壓進去。
看 main+27: call 0x80483b4 <add>
這裏調用 add函數。
add+0: push %ebp
add+1: mov %esp,%ebp
add+3: sub $0x10,%esp
還是和 main函數開頭一樣的操作。存儲舊的 ebp值壓棧。這裏剛開頭的時候其實 esp的值已經變掉了從 0x560變到了 0x558,這裏應該是 call指令完成的。
看 add+6: mov 0xc(%ebp),%eax
add+9: mov 0x8(%ebp),%edx
add+12: lea (%edx,%eax,1),%eax
add+15: mov %eax,-0x4(%ebp)
這裏計算那個表達式的值。
最後返回。
這裏我把棧的圖畫一下。
esp 0x548
0xbf899554 j
ebp 0xbf899558 0xbf899578(上一級的 ebp值 )
0xbf89955c 0x8048431(這裏己有可能是調用着執行 call時的下一條語句)
esp 0xbf899560 i
0xbf899564 8
0xbf899574 i
esp 0xbf899578 上一級的 ebp。
但是如果我把 add函數寫成這樣
int add(int i,int p){
int j=1;
printf("i:%p\n",&i);
printf("p:%p\n",&p);
printf("j:%p\n",&j);
return j;
}
結果是:
i:0xbffff550
p:0xbffff554
j:0xbffff53c
發現局部變量和函數參數之間有四個空即 16個字節。發現其實 j並不是壓在 ebp上一格的,而是 (ebp+12),這個很詭異。所以會出現在這種情況,我原來還以爲是局部變量與參數間空 16個字節放各種 pointer以及返回值這些東西呢,這個實在是太詭異了。
宗其所述:正常情況下,局部變量與參數間空了 8個字節(linux下)。