【问题标题】:Understanding C disassembled call理解 C 反汇编调用
【发布时间】:2013-04-11 22:07:12
【问题描述】:

我想了解 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 中有什么????最初什么都没有存储在那里???我想我很困惑。请帮助了解此功能的工作原理。 提前致谢!

【问题讨论】:

  • 使用 gcc -fverbose-asm -S 甚至可能使用 gcc -fverbose-asm -O -S 编译;另请参阅this reply,它提供了 很多 的参考资料。
  • 感谢所有参考资料和编译提示。

标签: c assembly x86-64 calling-convention att


【解决方案1】:

您将 AT&T 语法与 Intel 语法混淆了。

movq -24(%rbp), %rax

在 Intel 语法中是

mov rax,[rbp-24]

因此它将rbp 寻址的数据移动到rax,反之亦然。操作数的顺序在 AT&T 语法中是 src、dest,而在 Intel 语法中是 dest、src。

然后,为了摆脱 GAS 指令以使反汇编更易于阅读,我用 gcc 简单地用gcc test3.c 组装代码并用ndisasm -b 64 a.out 反汇编它。注意下面由 NDISASM 产生的my_func 函数的反汇编是 Intel 语法:

000005EF 55 推送 rbp 000005F0 4889E5 移动 rbp,rsp ;创建堆栈帧。 000005F3 48897DE8 mov [rbp-0x18],rdi ; s1 到一个局部变量中。 000005F7 488B45E8 mov rax,[rbp-0x18] ; rax = s1(它是一个指针) 000005FB 488B00 移动拉克斯,[拉克斯];取消引用 rax,存储到 rax。 000005FE 4883C079 添加 rax,byte +0x79 ;拉克斯 = 拉克斯 + 121 00000602 488945F8 mov [rbp-0x8],rax ;无效* n = s1 -> sp + 121 00000606 488B45E8 移动拉克斯,[rbp-0x18]; rax = 指向 s1 的指针 0000060A 8B4008 移动 eax,[rax+0x8] ;取消引用 rax+8,存储到 eax。 0000060D 0543010000 添加 eax,0x143 ; eax = eax + 323 00000612 8945F4 移动 [rbp-0xc],eax ;诠释 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 的答案。

【讨论】:

  • 那么命令 movq %rdi,-24(%rbp) 的含义是什么?寄存器 %rdi 中有什么?
  • @user2290802 %rdi 是第一个参数,在本例中为 strc* s1。有关反汇编的解释,请参阅我编辑的答案。
  • 好吧,如果你想产生Intel格式的asm,你可以使用gcc -masm=intel -S。这应该足够了。
  • @perror 这不是关于 Intel 与 AT&T 语法,而是关于实际的 x86-64 指令与 GAS 指令。 gcc -masm=intel -S 仍然产生 GAS 指令而不是实际指令。
  • @perror 实际上那些 GAS 指令并没有转换为指令。但对我来说,它们只是我需要摆脱的垃圾;)
【解决方案2】:

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

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

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%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 函数之前恢复先前的堆栈帧。

我希望现在可以让这种行为更加清晰。

【讨论】:

  • 谢谢,看起来很清楚。所以我找到了我困惑的根源。这是我遇到的 32 位和 64 位问题。
  • @perror 小说明:“这里,旧的 %rbp 被压入堆栈以存储直到函数结束。然后,将 %rsp 设置为新 %rbp 的值(当推送发生时,它比保存的 %rbp 低一行)。” -> 我认为你混合了 rsp 和 rbp。 "movq %rsp, %rbp" 将 RBP 设置为 RSP 的值,反之则不然...(AT&T 语法)
  • 你说得对,我的意思是:“那么,%rbp 被设置为新的%rsp 的值...”(我交换了 rsp 和 rbp)。我在文本中解决了这个问题。感谢您的关注。
【解决方案3】:

您可以通过输入以下命令更改为 intel 语法:

$ gcc -S -masm=intel test3.c -o test3.s

【讨论】:

  • 你好。欢迎来到 StackOverflow。请熟悉如何很好地回答问题 :) 请注意,您的回答不是答案,而是提示,因此应该放在评论中。干杯:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 2018-01-04
  • 1970-01-01
  • 1970-01-01
  • 2018-10-22
  • 1970-01-01
相关资源
最近更新 更多