彙編過程調用是怎樣操作棧的?

正在學csapp,在講call指令時有個例題,如下。

main函數調用sum。給出了調用時的示意圖,如下。

但是感覺跟上圖——棧幀結構圖不太一致(調用圖較早幀的長度和%esp箭頭指向的位置)。

希望各位能給解釋一下這個調用的過程,謝謝大家。


書里應該要表達的是stack frame的意思吧?

x86/x64裡面,stack frame就是根據當前的ebp能反推出整個調用棧。

ebp esp eip的幾個特性:

1. 主流編譯器在函數調用的caller里,執行call指令會讓eip入棧;

2. 被調函數(callee)裡頭兩句一定是

push ebp

mov ebp, esp

最後一句一定是

mov esp, ebp

pop ebp

3. ebp在函數內部是不會改變的,入棧出棧動作只改變esp,於是通過ebp就能反推出整個調用棧了。

反推棧幀的方法

當前的ebp就是當前函數入口時的esp;

入口時的[esp-4]就是前一個函數的ebp;

入口時的[esp-8]就是前一個函數的eip值;

拿到前一個函數的ebp值繼續反推就能獲得整個調用棧的ebp esp eip,這就是stack frame。

如果是64位:

寄存器換成rip rbp rsp,棧指針一次減8;

其它方面:

1. 基本沒有far call(系統調用、中斷除外),所以,棧上一般只有IP,沒有CS;

2. 32位多數情況下參數用棧傳輸,64位下是用寄存器的更多,具體要看編譯器;

3. enter和leave指令等效於push ebp; mov ebp, esp和mov esp, ebp; pop ebp;

---------------------------------

實例:

函數調用up_align_to -&> align_to,彙編為VS2008

size_t up_align_to(size_t val, size_t align)
{
011914B0 55 push ebp
011914B1 8B EC mov ebp,esp
011914B3 81 EC C0 00 00 00 sub esp,0C0h
011914B9 53 push ebx
011914BA 56 push esi
011914BB 57 push edi
011914BC 8D BD 40 FF FF FF lea edi,[ebp-0C0h]
011914C2 B9 30 00 00 00 mov ecx,30h
011914C7 B8 CC CC CC CC mov eax,0CCCCCCCCh
011914CC F3 AB rep stos dword ptr es:[edi]
return align_to(val, align, 1);
011914CE 6A 01 push 1 //參數3
011914D0 8B 45 0C mov eax,dword ptr [align]
011914D3 50 push eax //參數2
011914D4 8B 4D 08 mov ecx,dword ptr [val]
011914D7 51 push ecx //參數1
011914D8 E8 08 FD FF FF call align_to (11911E5h) //函數調用
011914DD 83 C4 0C add esp,0Ch
}
011914E0 5F pop edi
011914E1 5E pop esi
011914E2 5B pop ebx
011914E3 81 C4 C0 00 00 00 add esp,0C0h
011914E9 3B EC cmp ebp,esp
011914EB E8 73 FC FF FF call @ILT+350(__RTC_CheckEsp) (1191163h)
011914F0 8B E5 mov esp,ebp
011914F2 5D pop ebp
011914F3 C3 ret

size_t align_to(size_t val, size_t align, int is_up)
{
01191420 55 push ebp //保存ebp
01191421 8B EC mov ebp,esp //保存esp
01191423 81 EC C0 00 00 00 sub esp,0C0h
01191429 53 push ebx
0119142A 56 push esi
0119142B 57 push edi
0119142C 8D BD 40 FF FF FF lea edi,[ebp-0C0h]
01191432 B9 30 00 00 00 mov ecx,30h
01191437 B8 CC CC CC CC mov eax,0CCCCCCCCh
0119143C F3 AB rep stos dword ptr es:[edi]
if (val % align == 0)

執行到參數3以後

[棧上其它數據]
[參數3]
[] &<-esp,ebp為up_align_to入口時的esp值

執行到參數2以後

[棧上其它數據]
[參數3]
[參數2]
[] &<-esp,ebp為up_align_to入口時的esp值

執行到參數1以後

[棧上其它數據]
[參數3]
[參數2]
[參數1]
[] &<-esp,ebp為up_align_to入口時的esp值

執行CALL以後

[棧上其它數據]
[參數3]
[參數2]
[參數1]
[eip]
[] &<-esp,ebp為up_align_to入口時的esp值

執行到保存ebp以後

[棧上其它數據]
[參數3]
[參數2]
[參數1]
[eip]
[ebp]
[] &<-esp,ebp為up_align_to入口時的esp值

執行到保存esp以後

[棧上其它數據]
[參數3]
[參數2]
[參數1]
[eip]
[ebp]
[] &<-esp,ebp為align_to入口時的esp值

[ebp-4]就是up_align_to入口時的esp值

[ebp-8]就是call指令後面的地址(011914DD)

[[ebp-4]-4]是上上個調用函數入口時的esp值

[[ebp-4]-8]是上上個調用函數call後邊的地址

所以,求stack frame只要遞歸求[ebp-4]就行了,每個ebp-4挨著的就是eip


這個圖沒啥問題啊,這個圖的棧是向低址生長的

……指向的話

x64的話這個指向肯定不對的,比如圖裡面call之後應該是指向返回地址本身

嗯沒錯esp不應該指向空閑單元,這裡畫錯了


建議題主多看看不同架構和環境下,是如何定義調用約定的。書上介紹的應該是最基本的cdcel,實際還有很多常用的,比如System V AMD64 ABI。

函數(子程序)調用和返回過程中如何管理資源,依賴於雙方具體的調用約定。沒有一個明確統一的通行的標準可供執行。


在程序段寄存器cs的descriptor.D位為1時

call sum等價於

push dword ret_addr

jmp sum

ret_addr:

ret指令等價於

add esp, 4

jmp dword [esp - 4]

適用於x86體系legacy-mode的protected mode子模式或者long-mode的compatibility mode子模式


@北極@雲天明 我是題主,原諒我補充問題不會加圖片,只好在寫答案這裡發了。非常感謝大家的答案,讓我學到了除了這個問題之外的新的知識。

我畫了一張圖,再麻煩大家看一下call的流程是不是這樣子?


沒搞懂你問的什麼問題...,用戶程序調用系統調用時,用的棧是內核棧,當然調用一般函數就是用的進程空間的用戶棧.內核棧是從(TSS)中獲取r0內核棧的棧指針.棧的增長方向是從上往下增長(push的話esp減減 ,pop esp++).call的時候將會把返回地址(也就是調用call程序的下一個指令地址)壓入棧,esp將-4(32位操作系統).ret呢就會把壓入棧的返回地址裝載到CS:IP..


推薦閱讀:

請問popl %esp這個語句有什麼用處?
《深入理解計算機系統》配套實驗:Bomblab
彙編語言中call和ret指令必須成對出現嗎?
關於CSAPP第12章並發編程的一個例子的疑惑?
做CSAPP遇到點問題,希望能指點一番?

TAG:編程 | 彙編語言 | CSAPP |