2013-04-18 107 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