【问题标题】:In x86, why do I have the same instruction two times, with reversed operands?在 x86 中,为什么我有两次相同的指令,但操作数是相反的?
【发布时间】:2015-01-27 16:00:44
【问题描述】:

我正在用x86 asm 做几个实验,试图了解通用语言构造如何映射到汇编中。在我目前的实验中,我试图具体了解 C 语言指针如何映射到寄存器间接寻址。我已经写了一个类似 hello-world 的指针程序:

#include <stdio.h>

int
main (void)
{
    int value    = 5;
    int *int_val = &value;

    printf ("The value we have is %d\n", *int_val);
    return 0;
}

并使用:gcc -o pointer.s -fno-asynchronous-unwind-tables pointer.c:[1][2]

将其编译为以下 asm
        .file   "pointer.c"
        .section        .rodata
.LC0:
        .string "The value we have is %d\n"
        .text
        .globl  main
        .type   main, @function
main:
;------- function prologue
        pushq   %rbp
        movq    %rsp, %rbp
;---------------------------------
        subq    $32, %rsp
        movq    %fs:40, %rax
        movq    %rax, -8(%rbp)
        xorl    %eax, %eax
;----------------------------------
        movl    $5, -20(%rbp)   ; This is where the value 5 is stored in `value` (automatic allocation)
;----------------------------------
        leaq    -20(%rbp), %rax ;; (GUESS) If I have understood correctly, this is where the address of `value` is 
                                ;; extracted, and stored into %rax
;----------------------------------
        movq    %rax, -16(%rbp) ;; 
        movq    -16(%rbp), %rax ;; Why do I have two times the same instructions, with reversed operands???
;----------------------------------
        movl    (%rax), %eax
        movl    %eax, %esi
        movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
;----------------------------------
        movl    $0, %eax
        movq    -8(%rbp), %rdx
        xorq    %fs:40, %rdx
        je      .L3
        call    __stack_chk_fail
.L3:
        leave
        ret
        .size   main, .-main
        .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
        .section        .note.GNU-stack,"",@progbits

我的问题是我不明白为什么它包含指令movq 两次,操作数相反。有人可以向我解释一下吗?

[1]:当我根本不需要 cfi 指令时,我想避免在我的 asm 代码中穿插它们。

[2]:我的环境是Ubuntu 14.10gcc 4.9.1(由ubuntu修改)和Gnu assembler (GNU Binutils for Ubuntu) 2.24.90.20141014,配置为目标x86_64-linux-gnu

【问题讨论】:

  • 如果你不告诉 gcc 进行优化,它会产生 非常 愚蠢的代码。如您所见,它将所有局部变量存储在堆栈中,即使它可以很容易地保存在寄存器中。要获得合理的代码,请告诉 gcc 使用 -O3 进行优化。
  • 我只能回应 EOF 的评论。我发现-O 对我来说是最易读的:-O3 可以应用阻碍程序集中源代码可追溯性的转换。出于与您使用-fno-asynchronous-unwind-tables 相同的原因,您可能也喜欢-fomit-frame-pointer(对于简单的功能,生成的程序集实际上更轻且更易于理解)。除非您有时间浪费,否则不要阅读使用 -O0 生成的代码。
  • @EOF 是的,我已经看到它对-O3 的作用,虽然它更干净,但它优化了我的指针(以及更多),这样我就失去了我的想看看原创:指针如何映射到寄存器间接寻址。
  • 如果不想优化变量,也可以使用-Og

标签: c linux gcc assembly x86-64


【解决方案1】:

如果你重新组织你的块可能会更清楚:

;----------------------------------
    leaq    -20(%rbp), %rax     ; &value
    movq    %rax, -16(%rbp)     ; int_val
;----------------------------------
    movq    -16(%rbp), %rax     ; int_val
    movl    (%rax), %eax        ; *int_val
    movl    %eax, %esi          ; printf-argument
    movl    $.LC0, %edi         ; printf-argument (format-string)
    movl    $0, %eax            ; no floating-point numbers
    call    printf
;----------------------------------

第一个块执行int *int_val = &amp;value;,第二个块执行printf ...。没有优化,块是独立的。

【讨论】:

    【解决方案2】:

    由于您没有进行任何优化,gcc 会创建非常简单的代码,该代码一次执行一个程序中的每个语句,而无需查看任何其他语句。因此,在您的示例中,它将一个值存储到变量int_val 中,然后下一条指令再次读取该变量作为下一条语句的一部分。在这两种情况下,它都使用%rax 作为临时保存值,因为这是通常用于事物的第一个寄存器。

    【讨论】:

      猜你喜欢
      • 2016-11-06
      • 1970-01-01
      • 2013-12-06
      • 2019-12-07
      • 2018-01-28
      • 1970-01-01
      • 1970-01-01
      • 2022-01-08
      • 2016-03-02
      相关资源
      最近更新 更多