這個(gè)文集是用來記錄 庖丁解牛Linux內(nèi)核 這個(gè)課程的筆記和作業(yè).錯(cuò)誤之處,懇請指出.
實(shí)驗(yàn)?zāi)康?/h2>
通過這個(gè)實(shí)驗(yàn),主要了解到C函數(shù)的調(diào)用在匯編層面的邏輯.
實(shí)驗(yàn)內(nèi)容
C程序
首先我們準(zhǔn)備了一個(gè)簡單的函數(shù)調(diào)用的c程序
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
通過gcc -S -o main.S main,c -m32,可以獲得main.S也就是該段代碼的反匯編代碼.去掉其中以"."開頭的代碼,因?yàn)檫@是匯編語言中的偽指令,是寫給編譯器看的,并不是真正的機(jī)器會執(zhí)行的代碼.然后我們得到下面的匯編代碼:
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax # 這里是要跳過call中入棧的eip,跳過前面看入棧的ebp,也就是4*2=8bytes,內(nèi)存單位是1byte,那么8(%ebp)就是在ebp的基礎(chǔ)上往前移8bytes,指向main里的立即數(shù)8.
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp #從這一句開始執(zhí)行, 把main函數(shù)的棧底地址入棧.
movl %esp, %ebp # 更新棧底地址
subl $4, %esp
movl $8, (%esp) #相當(dāng)于push $8
call f # push 下條指令eip到棧中,然后修改eip為f的地址,跳到f處開始執(zhí)行
addl $1, %eax
leave
ret
基本邏輯直接看我上面的注釋.下面是幾個(gè)注意的點(diǎn)
- 要注意的是除了最開始的初始化,esp指向的棧頂是當(dāng)前有效數(shù)據(jù)的最低地址,而不是指向null的地址.那么
subl $4, %esp
movl $8, (%esp)
這兩句其實(shí)就是push $8,為什么要分成兩句我還不知道.
總結(jié)
- 計(jì)算機(jī)的工作原理其實(shí)就是講一些代碼放在連續(xù)的內(nèi)存里,然后同時(shí)開辟一段堆和棧. 堆用來存儲全局.
- 我們在調(diào)用函數(shù)時(shí),在匯編層面的處理是:
- 先把CS:IP壓入棧,以便函數(shù)結(jié)束后再接著當(dāng)前流程執(zhí)行
- 再把當(dāng)前函數(shù)的棧頂壓入
- 再從右到左壓入函數(shù)變量