解码为机器语言后,指令重复两次,

问题描述 投票:0回答:1

我基本上是在学习如何在X86体系结构中编写自己的指令,但是要做到这一点,我正在理解如何将其解码和解释为低级语言,

[以简单的mov指令为例,并使用.byte表示法,我想详细了解指令的解码方式,

我的简单代码如下:

#include <stdio.h>
#include <iostream>



int main(int argc, char const *argv[])
{
    int x{5};
    int y{0};

    // mov %%eax, %0

asm (".byte 0x8b,0x45,0xf8\n\t" //mov %1, eax
    ".byte 0x89, 0xC0\n\t"
    : "=r" (y)
    : "r" (x)

   );



   printf ("dst value : %d\n", y);

    return 0;
} 

并且当我使用objdump分析它如何分解为机器语言时,我得到以下输出:

000000000000078a <main>:
 78a:    55                       push   %ebp
 78b:    48                       dec    %eax
 78c:    89 e5                    mov    %esp,%ebp
 78e:    48                       dec    %eax
 78f:    83 ec 20                 sub    $0x20,%esp
 792:    89 7d ec                 mov    %edi,-0x14(%ebp)
 795:    48                       dec    %eax
 796:    89 75 e0                 mov    %esi,-0x20(%ebp)
 799:    c7 45 f8 05 00 00 00     movl   $0x5,-0x8(%ebp)
 7a0:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%ebp)
 7a7:    8b 45 f8                 mov    -0x8(%ebp),%eax
 7aa:    8b 45 f8                 mov    -0x8(%ebp),%eax
 7ad:    89 c0                    mov    %eax,%eax
 7af:    89 45 fc                 mov    %eax,-0x4(%ebp)
 7b2:    8b 45 fc                 mov    -0x4(%ebp),%eax
 7b5:    89 c6                    mov    %eax,%esi
 7b7:    48                       dec    %eax
 7b8:    8d 3d f7 00 00 00        lea    0xf7,%edi
 7be:    b8 00 00 00 00           mov    $0x0,%eax
 7c3:    e8 78 fe ff ff           call   640 <printf@plt>
 7c8:    b8 00 00 00 00           mov    $0x0,%eax
 7cd:    c9                       leave  
 7ce:    c3                       ret    

关于objdump的输出,为什么7aa: 8b 45 f8 mov -0x8(%ebp),%eax指令重复两次,其背后有任何原因,或者我在使用.byte表示法时做错了什么?

gcc x86 inline-assembly objdump
1个回答
1
投票
其中之一是编译器生成的,因为您要求GCC为您选择的寄存器具有输入。这就是"r"(x)的意思。然后您在禁用优化的情况下进行了编译(默认为-O0),因此它实际上将x存储到了内存中,然后在asm语句之前重新加载了它。

您的代码与存储器的内容或EBP指向的位置无关。

由于您正在使用89 c0 mov %eax,%eax

asm语句的唯一安全约束是输入和输出的"a"显式寄存器约束,强制

编译器选择该约束。如果编译启用优化后,您的代码将完全中断,因为您向编译器撒谎了代码的实际功能。
// constraints that match your manually-encoded instruction asm (".byte 0x89, 0xC0\n\t" : "=a" (y) : "a" (x) );
没有强制GCC为"m"源或"=m"目标操作数选择某种寻址模式的约束,因此您需要在特定寄存器中请求输入/输出。

如果要从标准mov编码自己的mov指令

不同地

,请参见which MOV instructions in the x86 are not used or the least used, and can be used for a custom MOV extension-您可能希望在常规mov操作码之前使用前缀,以便可以让汇编器对寄存器进行编码和寻址模式,例如.byte something; mov %1, %0

查看编译器生成的asm输出(gcc -S,而不是.o或可执行文件的反汇编)。然后,您可以查看哪些指令来自asm语句,以及哪些指令由GCC发出。

如果您没有在asm模板中明确引用某些操作数,但仍想查看编译器选择了什么,则可以在

asm

注释中使用它们,如下所示:
asm (".byte 0x8b,0x45,0xf8 # 0 = %0 1 = %1 \n\t" ".byte 0x89, 0xC0\n\t" : "=r" (y) : "r" (x) );
和gcc将为您填充它,这样您就可以查看要读取和写入的操作数

期望

。 (Godboltg++ -m32 -O3)。我将您的代码放在void foo(){}而不是main中,因为GCC -m32认为它需要在main顶部重新对齐堆栈。这使得代码很难遵循。
# gcc-9.2 -O3 -m32 -fverbose-asm .LC0: .string "dst value : %d\n" foo(): subl $20, %esp #, movl $5, %eax #, tmp84 ## Notice that GCC hasn't set up EBP at all before it runs your asm, ## and hasn't stored x in memory. ## It only put it in a register like you asked it to. .byte 0x8b,0x45,0xf8 # 0 = %eax 1 = %eax # y, tmp84 .byte 0x89, 0xC0 pushl %eax # y pushl $.LC0 # call printf # addl $28, %esp #, ret
还请注意,如果您使用64位编译,则它可能会选择%esi作为寄存器,因为printf希望在那里有第二个arg。因此,实际上"a"而不是"r"约束很重要。

如果您要分配必须在函数调用中保留的变量,则可以让32位GCC使用其他寄存器;那么GCC将选择像EBX这样的保留呼叫的规则,而不是EAX。

© www.soinside.com 2019 - 2024. All rights reserved.