2013-04-18 108 views
5

我想了解C調用約定。要做到這一點,我寫了下面的代碼:瞭解C拆卸呼叫

#include <stdio.h> 
#include <stdlib.h> 

struct tstStruct 
{ 
    void *sp; 
    int k; 
}; 

void my_func(struct tstStruct*); 

typedef struct tstStruct strc; 

int main() 
{ 
    char a; 
    a = 'b'; 
    strc* t1 = (strc*) malloc(sizeof(strc)); 
    t1 -> sp = &a; 
    t1 -> k = 40; 
    my_func(t1); 
    return 0; 
} 

void my_func(strc* s1) 
{ 
     void* n = s1 -> sp + 121; 
     int d = s1 -> k + 323; 
} 

然後我用GCC使用以下命令:

gcc -S test3.c 

,並用其組裝上來。我不會顯示我得到的整個代碼,而是粘貼函數my_func的代碼。它是這樣的:

my_func: 
.LFB1: 
.cfi_startproc 
pushq %rbp 
.cfi_def_cfa_offset 16 
.cfi_offset 6, -16 
movq %rsp, %rbp 
.cfi_def_cfa_register 6 
movq %rdi, -24(%rbp) 
movq -24(%rbp), %rax 
movq (%rax), %rax 
addq $121, %rax 
movq %rax, -16(%rbp) 
movq -24(%rbp), %rax 
movl 8(%rax), %eax 
addl $323, %eax 
movl %eax, -4(%rbp) 
popq %rbp 
.cfi_def_cfa 7, 8 
ret 
.cfi_endproc 

據我瞭解,這是發生了什麼: 首先,呼叫者基指針被壓入堆棧和堆棧指針由新的基本指針設置堆棧新功能。但其餘的我不明白。據我所知,參數(或參數指針)存儲在堆棧中。如果是的話是什麼第二個指令的目的,

movq  -24(%rbp), %rax 

這裏,RAX%寄存器的內容被移動到地址從寄存器%RBP地址24個字節的路程。但是%rax是什麼?最初沒有存儲?我覺得我很困惑。請幫助理解此功能的工作原理。 在此先感謝!

+2

編譯'GCC -fverbose-ASM -S',甚至'GCC -fverbose-ASM -O -S';另請參閱[本回復](http://stackoverflow.com/a/16088155/841108),它提供了很多參考文獻。 –

+0

感謝您提供的所有參考資料和編譯提示。 – user2290802

回答

9

您將AT & T語法與Intel語法混淆。

 
movq -24(%rbp), %rax 

在Intel語句這將是

 
mov rax,[rbp-24] 

因此,通過移動尋址rbprax數據,而不是相反。操作數的順序爲src,dest爲AT & T語法,而在Intel語法中則爲dest,src。

然後,甩掉GAS指令,使拆卸更容易閱讀,我組裝用gcc的代碼只需用gcc test3.cndisasm -b 64 a.out拆開它。注意下面的NDISASM生產my_func功能的拆卸是英特爾的語法:

 
000005EF 55    push rbp 
000005F0 4889E5   mov rbp,rsp  ; create the stack frame. 
000005F3 48897DE8   mov [rbp-0x18],rdi ; s1 into a local variable. 
000005F7 488B45E8   mov rax,[rbp-0x18] ; rax = s1 (it's a pointer) 
000005FB 488B00   mov rax,[rax]  ; dereference rax, store into rax. 
000005FE 4883C079   add rax,byte +0x79 ; rax = rax + 121 
00000602 488945F8   mov [rbp-0x8],rax ; void* n = s1 -> sp + 121 
00000606 488B45E8   mov rax,[rbp-0x18] ; rax = pointer to s1 
0000060A 8B4008   mov eax,[rax+0x8] ; dereference rax+8, store into eax. 
0000060D 0543010000  add eax,0x143  ; eax = eax + 323 
00000612 8945F4   mov [rbp-0xc],eax ; int d = s1 -> k + 323 
00000615 5D    pop rbp 
00000616 C3    ret 

有關Linux的信息X86-64調用約定(System V的ABI),看到答案What are the calling conventions for UNIX & Linux system calls on x86-64

+0

那麼命令movq%rdi,-24(%rbp)的含義是什麼?註冊%rdi中的內容是什麼? – user2290802

+0

@ user2290802'%rdi'是第一個參數,在本例中爲'strc * s1'。請參閱我編輯的答案以獲取反彙編的解釋。 – nrz

+2

那麼,如果你想產生Intel格式的asm,你可以使用'gcc -masm = intel -S'。這應該就夠了。 – perror

6

該功能(I忽略不必要的行)分解這樣的:

首先,存在前一堆棧幀的節約:

pushq %rbp 
movq %rsp, %rbp 

在此,舊%rbp被推到堆棧要存儲直到函數結束。然後,%rbp被設置爲新的%rsp的值(作爲push發生時,它比保存的%rbp低一行)。

movq %rdi, -24(%rbp) 

在這裏,你首先要知道的i386 system V ABIamd64 system V ABI之間的主要區別之一。

在i386 System V ABI中,函數參數通過堆棧傳遞(並且僅通過堆棧)。與此相反,在AMD64系統V ABI,參數首先通過寄存器傳輸(%rdi%rsi%rdx%rcx%r8%r9如果它是整數,%xmm0%xmm7如果這是浮動)。一旦寄存器的數量已經用完,剩下的參數就會像i386中一樣被推入堆棧。

所以,在這裏,機器只是將函數的第一個參數(這是一個整數)臨時加載到堆棧上。

movq -24(%rbp), %rax 

由於無法從一個寄存器數據直接傳輸到另一個,的%rdi內容然後裝入%rax。因此,%rax現在存儲此函數的第一個(也是唯一的)參數。

movq (%rax), %rax 

該指令只是取消引用指針和存儲結果回%rax

addq $121, %rax 

我們將121添加到指定值。

movq %rax, -16(%rbp) 

我們將獲得的值存儲在堆棧中。

movq -24(%rbp), %rax 

我們裝載,又是第一個功能的%rax(還記得我們存儲的第一個參數在-24(%rbp))的說法。

movl 8(%rax), %eax 
addl $323, %eax 

如前所述,我們取消引用指針和存儲在%eax所獲得的值,然後再加323,並把它回%eax

注意,在這裏,我們從%rax切換到%eax,因爲我們正在處理的值不再一個void*(64位),如以前,但一個int(32位)。

movl %eax, -4(%rbp) 

最後,我們店這個計算堆棧的結果(這似乎是沒用這裏,但它可能是一些不必要的編譯器並沒有在編譯時檢測)。

popq %rbp 
ret 

兩個最終指令給手回main函數之前剛剛恢復先前的堆棧幀。

我希望這可以讓這種行爲更清晰。

+0

感謝它看起來很清楚。所以我發現了我的困惑之源。這是我遇到的32位和64位問題。 – user2290802

+0

@perror小小的說明:「在這裏,舊的%rbp被壓入堆棧直到函數結束,然後%rsp被設置爲新的%rbp的值(它比保存%rbp作爲推送發生)「。 - >我認爲你混合使用rsp和rbp。 「movq%rsp,%rbp」 將RBP設置爲RSP的值,反之亦然(AT&T語法) – libjup

+0

您說得對,我的意思是:「然後,將'%rbp'設置爲新的'%rsp' ...「(我交換了rsp和rbp)。我在文中解釋了這一點。感謝您的注意。 – perror

1

您可以通過輸入以下命令切換到英特爾的語法:

$ gcc -S -masm=intel test3.c -o test3.s 
+0

你好。歡迎來到StackOverflow。請熟悉如何回答問題:)請注意,您的答案不是答案,而是一個提示,因此應置於評論中。乾杯:) – DawidPi