深入理解 C函式呼叫與組合語言

2021-10-24 17:52:47 字數 3746 閱讀 5560

函式在呼叫時,需要關注的有幾個點,如何傳遞引數,如何返回到原來的地方繼續執行。今天通過檢視函式對應的彙編進行分析。

函式和呼叫函式的**如下:

long long add(long long x, long long y) 

…long long x = 3, y = 7;

long long a = add(x, y);

函式add有兩個long long的引數x和y,在函式中,有乙個區域性變數sum,賦值為x+y,最後返回sum。使用gcc和gdb分別編譯和除錯**,擷取了其中的彙編**。

1004010b6:	48 c7 45 f8 03 00 00 	movq   $0x3,-0x8(%rbp)

1004010bd: 00

1004010be: 48 c7 45 f0 07 00 00 movq $0x7,-0x10(%rbp)

1004010c5: 00

1004010c6: 48 8b 55 f0 mov -0x10(%rbp),%rdx

1004010ca: 48 8b 45 f8 mov -0x8(%rbp),%rax

1004010ce: 48 89 c1 mov %rax,%rcx

1004010d1: e8 aa ff ff ff callq 100401080 1004010d6: 48 89 45 e8 mov %rax,-0x18(%rbp)

0000000100401080 :

100401080: 55 push %rbp

100401081: 48 89 e5 mov %rsp,%rbp

100401084: 48 83 ec 10 sub $0x10,%rsp

100401088: 48 89 4d 10 mov %rcx,0x10(%rbp)

10040108c: 48 89 55 18 mov %rdx,0x18(%rbp)

100401090: 48 8b 55 10 mov 0x10(%rbp),%rdx

100401094: 48 8b 45 18 mov 0x18(%rbp),%rax

100401098: 48 01 d0 add %rdx,%rax

10040109b: 48 89 45 f8 mov %rax,-0x8(%rbp)

10040109f: 48 8b 45 f8 mov -0x8(%rbp),%rax

1004010a3: 48 83 c4 10 add $0x10,%rsp

1004010a7: 5d pop %rbp

1004010a8: c3 retq

在call指令呼叫函式add之前,區域性變數x和y被賦值為3和7,區域性變數使用基址指標rbp加上偏移量進行訪問。編譯器使用rdx和rax暫存器傳遞引數,然後使用callq呼叫函式add,關於call指令,查閱intel指令手冊,可以看到下面的偽**:

temprip其實是通過計算,得到的函式目的位址。此時,rip指向call指令下一條指令的位址,由於執行完函式後需要返回,因此將rip入棧,也就是push(rip),最後一行將rip設定為temprip,即函式位址。這就是call指令做的2個動作,1)儲存下一條指令的位址到棧中,其實就是函式的返回值;2)將rip設定為函式位址。這裡的函式位址就是call指令後面的引數值。下一條指令的位址是1004010d6,如果此時列印棧頂rsp處的內容,就是該值。

0000000100401080 :

100401080: 55 push %rbp

100401081: 48 89 e5 mov %rsp,%rbp

100401084: 48 83 ec 10 sub $0x10,%rsp

100401088: 48 89 4d 10 mov %rcx,0x10(%rbp)

10040108c: 48 89 55 18 mov %rdx,0x18(%rbp)

100401090: 48 8b 55 10 mov 0x10(%rbp),%rdx

100401094: 48 8b 45 18 mov 0x18(%rbp),%rax

100401098: 48 01 d0 add %rdx,%rax

10040109b: 48 89 45 f8 mov %rax,-0x8(%rbp)

10040109f: 48 8b 45 f8 mov -0x8(%rbp),%rax

1004010a3: 48 83 c4 10 add $0x10,%rsp

1004010a7: 5d pop %rbp

1004010a8: c3 retq

這裡有乙個知識點需要說明,即棧是向下增長的,這裡的「向下」指的是從高位址向低位址。rsp暫存器指向了棧頂的位址,當執行入棧push操作時,rsp會減小,當執行出棧pop操作時,rsp會增加。因此,在函式add中,sub $0x10, %rsp,將rsp減小了0x10,這是在開闢區域性棧空間。

函式add的開始有2行**:

push %rbp

mov %rsp, %rbp

這與函式將要結束時的**對應:

pop %rbp

將rbp保持起來,最後再將rbp恢復原值。將rsp的值保持到rbp中,是為了使用rbp加上偏移量,實現對區域性變數的訪問。rsp在函式結束之前,執行了add $0x10, %rsp操作,恢復原值。這裡是函式add(被呼叫者)恢復棧平衡,在函式呼叫的前後,棧指標rsp沒有發生變化。

函式add通過rax返回結果,rax暫存器一般作為函式的返回值。最後呼叫了retq,interl指令手冊對ret指令的偽**描述如下:

最關鍵的一行是rip := pop();將棧頂當前的8個位元組(返回位址,也就是call指令的下一條指令的位址)給了rip。因此,ret指令也做了2個操作,乙個是出棧pop(),乙個是將棧頂的值賦值給rip,使得程式返回到函式呼叫的下一條位址,接著執行。

關於函式引數的傳遞,函式引數使用暫存器rdx,rcx,rdi,rsi,r8,r9,當引數個數超過6個時,多餘的引數便使用棧進行傳遞。不同平台和編譯器可能有所不同。引數傳遞的順序按照從右往左的次序。

從組合語言理解函式呼叫,可以了解全貌,關鍵點是如何利用棧,因此需要關注暫存器rsp的變化。call和ret指令對棧和cs:ip進行了操作,從其偽**中,可以很清晰地看出做了哪些操作。

深入理解程式設計使用linux組合語言(一)

在編譯執行第四章power函式遇到報錯,報錯資訊invalid sp address 0xffffdf2c,通過gdb定位到程式 0x40007e mov ebx,dword ptr rbp 0x8 不知道原因是啥,查資料和琢磨了半天,終於想到可能是32位程式編譯的問題,然後通過新增32位程式編譯引...

組合語言 拓展實驗 對中斷的深入理解

編寫兩個源程式。兩個源程式負責的工作分別是 1 1.asm,負責重灌1號中斷。2 2.asm,負責計算2的8次方,結果以十六進製制顯示在螢幕 這兩個源程式執行中,配合實現以下功能 執行1.exe,重灌的1號中斷進入記憶體,相應的中斷向量指向新的中斷處理程式,此中斷處理程式在2.exe執行過程中為其列...

深入理解系統呼叫與庫函式呼叫

今天對系統呼叫和庫函式呼叫進行了一次總結 在這裡把心得寫出來,如果有什麼錯誤希望大家能指出 我們假設unix系統 庫函式有c編譯器提供 而你有用彙編編寫c庫函式的能力 首先要知道巨集觀上知道系統呼叫和庫函式的區別 系統呼叫由作業系統提供,我門假設用彙編編寫的 而庫函式是編譯器提供 而我們知道系統呼叫...