我想了解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是什么????什么都没有最初存储在那里???我觉得我很困惑。请帮助理解此功能的工作原理。 提前谢谢!
答案 0 :(得分:10)
您将AT&amp; T语法与英特尔语法混淆。
movq -24(%rbp), %rax
在英特尔语法中,它将是
mov rax,[rbp-24]
因此,它将rbp
处理的数据移至rax
,反之亦然。操作数的顺序是src,dest是AT&amp; T语法,而在Intel语法中它是dest,src。
然后,为了摆脱GAS指令以使反汇编更容易阅读,我用gcc test3.c
简单地用gcc汇编代码,并用ndisasm -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的答案。
答案 1 :(得分:6)
该函数被分解为这样(我忽略了不必要的行):
首先,保存前一个堆栈帧:
pushq %rbp
movq %rsp, %rbp
这里,旧的%rbp
被推入堆栈以存储直到函数结束。然后,%rbp
设置为新%rsp
的值(它在保存的%rbp
下方一行,发生push
。
movq %rdi, -24(%rbp)
首先,您必须了解i386 system V ABI和amd64 system V ABI之间的主要区别之一。
在i386 System V ABI中,函数参数通过堆栈传递(并且仅通过堆栈)。相反,在amd64 System V ABI中,参数首先通过寄存器(%rdi
,%rsi
,%rdx
,%rcx
,%r8
和{{ 1}}如果是整数,%r9
到%xmm0
如果这是浮点数)。一旦寄存器的数量耗尽,其余的参数就会像i386一样被推送到堆栈。
所以,在这里,机器只是将函数的第一个参数(这是一个整数)临时加载到堆栈上。
%xmm7
由于您无法将数据直接从一个寄存器传输到另一个寄存器,因此movq -24(%rbp), %rax
的内容会被加载到%rdi
。因此,%rax
现在存储此函数的第一个(也是唯一的)参数。
%rax
该指令只是取消引用指针并将结果存回movq (%rax), %rax
。
%rax
我们在指向的值中加上121。
addq $121, %rax
我们将获得的值存储到堆栈中。
movq %rax, -16(%rbp)
我们再次加载movq -24(%rbp), %rax
中函数的第一个参数(请记住我们将第一个参数存储在%rax
)。
-24(%rbp)
如前所述,我们取消引用指针并将获得的值存储在movl 8(%rax), %eax
addl $323, %eax
中,然后我们将其添加323并将其放回%eax
。
请注意,我们已将%eax
切换为%rax
,因为我们正在处理的值不再是%eax
(64位),而是void*
(32位)。
int
最后,我们将这个计算的结果存储到堆栈中(这里似乎没用,但它可能是编译器在编译时没有检测到的东西)。
movl %eax, -4(%rbp)
最后两条指令只是在将手牌交还给popq %rbp
ret
函数之前恢复上一个堆栈帧。
我希望这会使这种行为更加清晰。
答案 2 :(得分:1)
您可以输入以下命令更改为英特尔语法:
$ gcc -S -masm=intel test3.c -o test3.s