如何根据实模式偏移和寻址确定x86机器指令的值?

4
我想将原始机器码字节以0和1的形式写入文本文件,并通过BIOS执行它。然而,我有些问题不理解寻址、乘法、偏移量、操作数和指令在组合排列中的工作方式,例如MOV AL,07和MOV BL,AL之间的区别。在汇编语言中这是有意义的,但在机器码中变得非常难以理解参数的概念。因此,我想知道的是:如何更好地理解这个问题?我没有找到准确解释/描述指令中0和1的组合相关性或数据传递、MMIO、寻址模式、算术等之间联系的教程。这个网站http://ref.x86asm.net/coder32.html#x00尝试了解释,但我不理解。

举个例子:如果我想将数字5移入AL寄存器中,我需要在机器码的前缀中指定二进制字面量'5'吗?还是每个指令都有一个固定的二进制代码,无论值为多少都一样?我想知道的是如何理解机器码的编写方式。


http://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM_and_SIB_bytes - Jens Björnhager
2个回答

5
很不幸,x86编码是复杂和不规则的,理解它需要付出艰苦的努力。编码的最佳“快速入门”是sandpile.org上的一组HTML页面(内容简洁但相当全面)。
首先,http://sandpile.org/x86/opc_enc.htm - “指令编码”表格显示了指令编码的十几种方式。每行中的白色单元格表示指令中的强制字节;后续的灰色单元格存在或不存在,取决于前面出现在操作码中的各个字段。你应该查看以白色“0Fh”开头的行,以及第一行。同一页底部还有出现在各个“扩展”操作码字段中的位字段,只需忽略除“modrm/sib”行(第一行)之外的所有行即可。
请注意,对于除第一行(1字节操作码)之外的所有行,操作码后必须跟着一个“mod r/m”字节(对于1字节操作码,这取决于指令)。这编码了大多数双参数指令的参数。网页http://sandpile.org/x86/opc_rm.htm上的表格列出了含义:其中一个参数必须是寄存器,另一个参数可以是寄存器或内存间接寻址(“reg”字段编码寄存器,“mod”和“r/m”字段编码另一个参数)。通常还有一个“方向”位在操作码的其他地方表示参数的顺序。操作码还指示我们是否正在操作AL、AX、EAX或RAX(即不同的大小),或者扩展寄存器之一,这就是为什么每个3位字段都列出了许多不同的寄存器。
在modrm中,如果“mod”位为“11”,则“r/m”字段也指的是寄存器。否则,它通常指通过将命名的寄存器添加到modrm字节后面的(可选)位移来构造的内存地址(此常量的长度取决于“mod”位,为0、1或4个字节)。例外情况是当“r/m”位为“100”(即0x4)时,通常会命名“SP” - 在这种情况下,内存参数由紧随modrm字节的附加“sib”字节描述(任何modrm位移均出现在sib之后)。有关SIB的编码,请查看http://sandpile.org/x86/opc_sib.htm,或从modrm页面链接过去。

最后,要理解方向和大小的来源,请查看一些操作码:http://sandpile.org/x86/opc_1.htm。前四个条目都是“ADD”,参数顺序不同,并且宽度也不同。因此,在这种情况下,指令的底部位编码了方向和宽度。


2
还有一件事:如果你懂C语言,你可以看看其中一个(多个)开源汇编器或反汇编器,它们将所有操作码信息组织在表中。例如,(GPL的)GNU binutils x86表在一个名为“i386-opc.c”或“i386-opc.tbl”的文件中,具体取决于版本(谷歌文件名);这里有一个副本:https://github.com/adobe-flash/crossbridge/blob/master/gdb-7.3/opcodes/i386-opc.tbl - mike
你给我的那些链接非常复杂,我甚至无法开始解密每个指令之间的关联。我需要请专家帮助我逐步分析二进制编码。不过还是谢谢你。 - Jump if not Equal
您可以在此处查看解码tbl的方法:https://cygwin.com/ml/binutils/2010-09/msg00277.html,该表格基于此处的头文件:https://github.com/arrogantpenguin/PenguinoOS/blob/73608ed45a03cd3b013303e60accc5dee473ec53/sources/binutils/opcodes/i386-opc.h#L660 - h4ck3rm1k3
关于使用“r/m”源或目的地形式对mov al,bl进行编码的两种方式 - x86 XOR操作码差异 - Peter Cordes

1

汇编助记符和机器指令之间(大多数情况下)存在一对一的映射关系。您可以在Intel软件开发人员手册第2卷中找到这些映射,其中包含完整的x86 16位、32位和64位指令集。您可能需要从第2章:指令格式开始,该章节描述了您要创建的翻译。

对于mov al, 5,正如您所说,您只需将文字放在那里即可。机器码指令为:

b0 05

这是MOV指令的MOV r8, imm8形式。对于mov bl, al,您需要使用MOV r/m8,r8形式,这在您的情况下将被编码为:

88 c3

你可以在《表2-2带有ModR/M字节的32位寻址形式》中查找c3,在那里你会在BL行和AL列的交叉点看到它。(如果你处于16位模式下,也有一个16位表格,此时的值相同。)

但是机器码是二进制的;你引用了十六进制。b0 05 是176,或10110000,而五是101。我应该直接打入它们各自的二进制等价物,编码会是一样的吗? - Jump if not Equal
那么,你想表达什么?10110000 0000010110001000 11000011,如果这样做会让你感觉更好的话。 - Carl Norum
数字只是数字,你用什么进制来表示它们只是为了方便。 - Carl Norum
CPU的固件只能解析二进制代码,所以我在想直接用十六进制编写是否会有所不同。 - Jump if not Equal
1
十六进制只是一种表示法。你必须把正确的字节放在正确的位置上。无论你将一个字节写成两位十六进制数还是八位二进制数,除了你自己以外,没有人会在意。计算机解析你放入内存的字节,这些字节只是晶体管上的电压水平。你可以将其视为二进制或十六进制或任何你想要的形式,只要正确的晶体管获得正确的值即可。 - Carl Norum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接