聲明:原創作品轉載請註明出處
參考文檔:《Linux內核分析》MOOC課程 http://mooc.study.163.com/course/USTC-1000029000
本文將以一個C代碼片段來刨析x86函數調用棧變化過程,進而理解高級語言是如何在計算機上運行的。
C代碼片段如下:
int g(int x)
{
return x+30;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(28) + 100;
}
衆所周知,計算機不能直接執行高級語言,高級語言必須通過編譯器翻譯成彙編代碼,再通過彙編器翻譯成二進制代碼,最後鏈接爲可執行程序,方可在計算機上執行,目前使用最多的編譯器是gcc,故使用gcc -S -o main.s main.c -m32命令,將C代碼翻譯成相應的彙編代碼。
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $30, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $28, (%esp)
call f
addl $100, %eax
leave
ret
好了,準備工作做完了,下面就讓我們從熟悉的main()開始一步步分析函數調用棧的變化吧?
我們假設調用main()之前,函數調用棧如下:
當執行下面語句後,
main:
pushl %ebp
函數調用棧如下:
當執行下面語句後,
main:
....
movl %esp, %ebp
函數調用棧如下:
當執行下面語句後,
main:
....
subl $4, %esp
函數調用棧如下:
當執行下面語句後,
main:
....
movl $28, (%esp)
函數調用棧如下:
由於call f相當於pushl %eip,movl f %eip,所以當執行下面語句後,
main:
.....
call f
函數調用棧如下:
當執行下面語句後,
f:
pushl %ebp
函數調用棧如下:
當執行下面語句後,
f:
.....
pushl %esp, %ebp
函數調用棧如下:
當執行下面語句後,
f:
.....
subl $4, %esp
函數調用棧如下:
當執行下面語句後,
f:
.....
movl 8(%ebp), %eax
函數調用棧如下:
當執行下面語句後,
f:
.....
movl %eax, (%esp)
函數調用棧如下:
當執行下面語句後,
f:
.....
call g
函數調用棧如下:
當執行下面語句後,
g:
pushl %ebp
函數調用棧如下:
當執行下面語句後,
g:
....
movl %esp, %ebp
函數調用棧如下:
當執行下面語句後,
g:
....
movl 8(%ebp), %eax
函數調用棧如下:
當執行下面語句後,
g:
....
addl $30, %eax
函數調用棧如下:
當執行下面語句後,
g:
....
popl %ebp
函數調用棧如下:
由於ret語句相當於popl %eip,所以當執行下面語句後,
g:
....
ret
函數調用棧如下:
由於leave語句相當於movl %ebp %esp,popl %ebp,所以當執行下面語句後,
f:
....
leave
函數調用棧如下:
由於ret語句相當於popl %eip,所以當執行下面語句後,
f:
....
ret
函數調用棧如下:
當執行下面語句後,
main:
....
addl $100, %eax
函數調用棧如下:
由於leave語句相當於movl %ebp %esp,popl %ebp,所以當執行下面語句後,
main:
....
leave
函數調用棧如下:
main()中ret語句將eip返回至操作系統,至此,函數調用棧變化過程分析完成!