理解C反汇编的电话

时间:2013-04-18 16:56:34

标签: 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;
}

然后我使用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是什么????什么都没有最初存储在那里???我觉得我很困惑。请帮助理解此功能的工作原理。     提前谢谢!

3 个答案:

答案 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 ABIamd64 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