我基本上是在学习如何在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
表示法时做错了什么?
"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
。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将为您填充它,这样您就可以查看要读取和写入的操作数。 (Godbolt和期望
g++ -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。