理解C反汇编的电话

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系统调用的调用约定的答案.

  • 好吧,如果你想以英特尔格式生成asm,你可以使用`gcc -masm = intel -S`.这应该足够了. (2认同)

per*_*ror 6

该函数被分解为这样(我忽略了不必要的行):

首先,保存前一个堆栈帧:

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 ABIamd64系统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函数之前恢复先前的堆栈帧.

我希望现在这种行为更加清晰.