use*_*802 6 c assembly x86-64 calling-convention att
我想学习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;
}
Run Code Online (Sandbox Code Playgroud)
然后我使用GCC使用以下命令:
gcc -S test3.c
Run Code Online (Sandbox Code Playgroud)
并想出了它的装配.我不会显示我得到的整个代码,而是粘贴函数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
Run Code Online (Sandbox Code Playgroud)
据我所知,这就是:首先将调用者基指针推入堆栈,并使其堆栈指针成为新的基指针,以便为新函数设置堆栈.但其余的我不明白.据我所知,参数(或指针参数)被存储在堆栈中.如果是这样,第二条指令的目的是什么,
movq -24(%rbp), %rax
Run Code Online (Sandbox Code Playgroud)
这里,%rax寄存器的内容被移动到远离寄存器%rbp中的地址24字节的地址.但%rax是什么???? 什么都没有最初存储在那里??? 我觉得我很困惑.请帮助理解此功能的工作原理.提前致谢!
nrz*_*nrz 10
您将AT&T语法与Intel语法混淆.
movq -24(%rbp), %rax
在英特尔语法中它会是
mov rax,[rbp-24]
因此它将移动的数据rbp移至rax,而不是相反.操作数的顺序是src,dest是AT&T语法,而在Intel语法中它是dest,src.
然后,为了摆脱GAS指令以使反汇编更容易阅读,我用gcc简单地组装代码并用gcc test3.c它反汇编ndisasm -b 64 a.out.请注意my_func下面NDISASM生成的函数的反汇编是Intel语法:
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)的信息,请参阅x86-64上有关UNIX和Linux系统调用的调用约定的答案.
该函数被分解为这样(我忽略了不必要的行):
首先,保存前一个堆栈帧:
pushq %rbp
movq %rsp, %rbp
Run Code Online (Sandbox Code Playgroud)
这里,旧%rbp的被推入堆栈以存储直到函数结束.然后,将%rbp其设置为新值%rsp(它是%rbp作为已push发生的保存下方的一行).
movq %rdi, -24(%rbp)
Run Code Online (Sandbox Code Playgroud)
在这里,您首先要了解i386系统V ABI和amd64系统V ABI之间的主要区别之一.
在i386 System V ABI中,函数参数通过堆栈传递(并且仅通过堆栈).与此相反,在AMD64系统V ABI,参数首先通过寄存器传输(%rdi,%rsi,%rdx,%rcx,%r8并且%r9如果它是整数,并%xmm0以%xmm7如果这是浮动).一旦寄存器数量耗尽,其余参数就会像i386一样被推送到堆栈.
所以,在这里,机器只是将函数的第一个参数(这是一个整数)临时加载到堆栈上.
movq -24(%rbp), %rax
Run Code Online (Sandbox Code Playgroud)
因为您无法直接将数据从一个寄存器传输到另一个寄存器,所以会将其内容%rdi加载到%rax.所以,%rax现在存储这个函数的第一个(也是唯一的)参数.
movq (%rax), %rax
Run Code Online (Sandbox Code Playgroud)
该指令只是取消引用指针并将结果存回%rax.
addq $121, %rax
Run Code Online (Sandbox Code Playgroud)
我们在指向的值上加121.
movq %rax, -16(%rbp)
Run Code Online (Sandbox Code Playgroud)
我们将获得的值存储到堆栈中.
movq -24(%rbp), %rax
Run Code Online (Sandbox Code Playgroud)
我们再次加载函数的第一个参数%rax(记住我们存储了第一个参数-24(%rbp)).
movl 8(%rax), %eax
addl $323, %eax
Run Code Online (Sandbox Code Playgroud)
如前所述,我们取消引用指针并将获得的值存储在中%eax,然后我们将323添加到它并将其重新放入%eax.
注意,这里,我们切换%rax到%eax因为我们处理的值不再是void*先前的int(64位)而是(32位).
movl %eax, -4(%rbp)
Run Code Online (Sandbox Code Playgroud)
最后,我们将这个计算的结果存储到堆栈中(这里似乎没用,但它可能是编译器在编译时没有检测到的不必要的东西).
popq %rbp
ret
Run Code Online (Sandbox Code Playgroud)
最后两条指令只是在将手返回main函数之前恢复先前的堆栈帧.
我希望现在这种行为更加清晰.
| 归档时间: |
|
| 查看次数: |
6309 次 |
| 最近记录: |