一个操作码字节如何根据“寄存器/操作码”字段解码为不同的指令?那是什么?

时间:2018-12-25 12:14:42

标签: assembly x86-64 disassembly machine-code

如何确定机器码将转换为什么字节数组?

我了解,如果我在开始时看到0f是2字节指令,但是我在x64调试器中看到了其他前缀和一些反汇编,则看到了奇怪的交互,例如48 83 C4 38,并且在操作码参考上可以看到48说操作数是64个字节。

但是83说它可以是7条不同的指令,具体取决于称为“寄存器/操作码字段”的字段。什么?

有人可以解释处理器如何使用这些字节来确定以下内容的逻辑:

  1. 运行了什么指令
  2. 指令在哪些寄存器和/或地址上使用(如果有的话)

3 个答案:

答案 0 :(得分:3)

0x48是REX前缀,W字段设置为1,表示64位操作数大小。  (不是64字节)。

许多用于指令即时版本的操作码,包括83,使用ModR / M字节中的3位/r字段作为3个额外的操作码位。英特尔的第2卷手册对此进行了说明,我认为附录中的操作码表中包括了它。

这就是为什么大多数原始8086立即指令(如and r/m, imm仍仅允许2个操作数,与shrd eax, edx, 4imul edx, [rdi], 12345不同的是,两个ModRM字段均用于编码操作数,以及操作码隐含的立​​即操作数。 SHRD / SHLD和386被添加,而imul-immediate被添加286。不幸的是,复制和(and eax, edx, 0xf)无法编码,但是至少x86可以使用LEA进行复制和-添加/订阅。


每条指令的文档,例如add (html extract of the vol2 manual),显示类似
的编码 REX.W + 83 /0 ib代表ADD r/m64, imm8,这就是您所拥有的。

  

diagram of the ModRM bit fields from wiki.osdev.org

  7                           0
+---+---+---+---+---+---+---+---+
|  mod  |    reg    |     rm    |
+---+---+---+---+---+---+---+---+

0xc4 = 0b11000100,因此reg字段=0。因此,我们的操作码为83 /0,以Intel的符号表示。

其余的ModRM字段为:

  • mode = 0b11,因此rm字段编码的是寄存器操作数,而不是寻址模式的基址寄存器。
  • rm = 0b100。规则#4 = SPL / SP / ESP / RSP。 (在本例中为RSP,因为它是64位操作数大小)。有关表格,请参阅英特尔手册,或https://wiki.osdev.org/X86-64_Instruction_Encoding#Registers

所以指令是add rsp, 0x38

ndisasm -b64同意:

$ cat > foo.asm
db 0x48, 0x83, 0xC4, 0x38
$ nasm foo.asm     # create a flat binary with those bytes, not an object file
$ ndisasm -b64 foo
00000000  4883C438          add rsp,byte +0x38

答案 1 :(得分:1)

这取决于特定的体系结构,不仅取决于x86-64,还取决于实际的芯片提供商。您可以检查例如intel's guide for architecture software developers

本章整章专门讨论字节码中命令的语法,然后在每个可用命令中专门讨论另一章。这是图2.1,让您有个主意:

Intel architecture format

摘自上述手册。例如,如果使用ARM,这将改变。

这是人们需要花费多年的时间才能能够“流利地读取”字节码的研究,因此,仅略读一下就只能使您对语法有一个大概的了解,也可以为找到特定内容提供很好的资源。

答案 2 :(得分:1)

我在页面上看到字母,字母a,这可能是许多不同的单词,它后面的字母是n。这可以是一个,也可以是任意数量的单词,所以我继续。

x86和那个时代的其他机器代码都是以这种方式工作的,特别是直接从中衍生出来的指令集。

首先,最重要的是,如果您只是占用程序的所有字节并跳到中间,这将毫无意义,很容易以错误的方式离开“快速的棕色狐狸”“ thequickbrownfox” ”“眉毛”是什么?处理器根据指令集的规则启动和继续,处理器非常愚蠢,它遵循定义或至少在处理器手册中记录的规则。只要程序员和工具创建了正确构建的程序,它就不会丢失,如果这样做的话,那是程序员/工具而不是处理器的错。处理器将开始将操作码字节解码为操作码字节。该字节可以是整个指令,也可以是基于特定字节的分数。如果是小数,则第一个字节加上其后的字节可以确定整个指令,也可以是小数。

CISC特别是操作码本身,并且下一个字节的一部分可能包含也可能不包含表示某些相关内容的位。在RISC中,例如mips或arm或其他形式的0000,请表示寄存器0,0001表示寄存器1,依此类推。但是在一些CISC指令中,即使没有很多指令,也没有一点区别寄存器x和寄存器y,寄存器a和寄存器b。必须在表中查找整个操作码,以了解其含义。

x86是一个可变长度的指令集,有些指令是一个字节,没有其他操作数,其他指令则需要更多的字节,然后可能是立即数。想要将立即值0x12345678移动到寄存器EAX中,而无需查看任何文档来说明这是5字节还是6字节指令,要么是将立即加载到ax的操作码,要么是将立即加载到ax的字节,然后将另一个字节表示这是斧头,然后是立即数的四个字节。

mov eax,0x12345678
mov ebx,0x12345678
mov ecx,0x12345678
mov edx,0x12345678

Disassembly of section .text:

00000000 <.text>:
   0:   b8 78 56 34 12          mov    eax,0x12345678
   5:   bb 78 56 34 12          mov    ebx,0x12345678
   a:   b9 78 56 34 12          mov    ecx,0x12345678
   f:   ba 78 56 34 12          mov    edx,0x12345678

原来是5个字节。尽管这些字节的位可能会直接解码为四个寄存器之一,但这不太可能,因为这不是这些指令集的设计方式。

您可能会为此复杂化,可悲的是intel和其他x86文档不如某些其他供应商那么好。但是它实际上只是一个流程图,相当容易解码第一个字节,根据其定义可以告诉您是否要查找另一个字节,下一个字节则指示是否需要进一步查找,依此类推。您不会像解码mips或arm或其他设计不同的解码器那样解码x86。它们所有人都有一个解码器,该解码器会查看这些位并确定指令或确定我是否需要更多位,但是x86以一种方式执行此操作,而mips以另一种方式执行,arm以另一种方式执行。两者各有利弊。

CISC像x86一样,虽然更像是流程图,但第一个字节告诉您转到X页,该页面要么具有完整的答案,要么表示获取下一个字节并基于附录X中的第Y页。

有些房屋只有一位,地址/位置会将您带到一个人。有些有一个以上,一旦您根据地址到达房屋,则需要更多信息来确定您对哪个人或宠物感兴趣。第一条信息,街道地址符合标准,但是隔离该房屋中的人/宠物的信息符合该房屋的标准。指令的第一个字节是操作码。但是基于操作码,如果还有其他字节,那么这些字节是特定于操作码的,如我们上面所见。对于0xB8,b8 78 56 34 12的第二个字节是立即值的一部分。您可以查找许多内容,其中第二个字节将进一步解码指令

mov eax,eax
mov eax,ebx
mov eax,ecx
mov eax,edx


   0:   89 c0                   mov eax,eax
   2:   89 d8                   mov eax,ebx
   4:   89 c8                   mov eax,ecx
   6:   89 d0                   mov eax,edx

对于0x89操作码,在这种情况下,第二个字节不是数据,而是进一步定义指令。

的确,第二个字节的解码并非仅是该操作码唯一,许多指令将共享这些位的相同解码,例如确定ah,al,ax,eax,bh,bl,bx等。 。英特尔文档以及无数其他书籍和网站中都对此进行了记录。

真正的文档是芯片本身的源代码,因为我们很少能获取到该文档,而文档通常不是由逻辑作者编写的,而是可能由技术作者在每一步骤中加以完善的。一些信息可能会丢失或令人困惑。一些供应商比其他供应商更好,他们的文档的某些版本比其他供应商更好。

x86几乎是您要学习的最后一个指令集,这不是一个正当的理由,因为您拥有的每个x86,在该框中都有许多非x86处理器,以及您拥有的每个x86相当数量的非x86设备。而且,如果以教育和学习为目标,那么无论如何,您都想从模拟器开始,可以大大提高成功的机会,而崩溃不会给您带来多大的伤害。有很多更好的指令集(例如msp430和pdp11)开始,这显然是影响它的原因。手臂,拇指,后来进入尖刺及其细微差别,那么在8位中,我不会以x86开头,我会使用其他6502或其他东西。然后也许是好奇的8088/8086使用了仿真器并在互联网上使用旧文档返回了计算机,那么最后是x86,如80386、80486和x86-64。首先进入x86-64必须完全与痛苦有关,真正让人们陷入自我虐待。如果您仍然需要执行此操作,那么此痛苦之门中的痛苦之门就是从使用旧手册,Dosbox,Bochs或许多其他仿真器的8088/8086开始。一旦掌握了基础,它们在步骤中添加到32位然后再添加到64位的方法可能更有意义,并且您不必为随着时间的推移而添加的大量保护感到困惑,您可以开始干净而纯净。

反汇编可变长指令集是一个要解决的巨大问题,但没人能解决,因为它们不能完全解决。不可能。我曾经从反汇编程序开始学习所有新的指令集。这些天,我可能会改用模拟器。拥有成功几率的唯一方法是从有效的入口点开始。并按执行顺序解码,而不是通过二进制线性进行。那只会暴露一些代码。其余的(如果有的话)是基于数据的,您可以尝试模仿,但是那也不完美。一方面,反汇编时的数据可能会更改运行时间。您甚至可以仿真该程序并将其运行数天/数周,以发现特定指令正在查看的各个位置的各种数据值,但仍未真正了解所有可能性。因此,有些反汇编程序只是把它弄错了,但向您显示它好像是正确的,而其他人则正确地向他们显示,只是说我不知道​​这是什么...

如今,

绝大多数二进制文件都已编译,因此数据路径通常是合理且完整的。但是,请从站立的视频游戏时代中获取一些乐趣,例如小行星。您将看到类似于此伪代码的内容:

a = 0
if(a == 0) goto somewhere
b = 7

我们可以很容易地看到条件分支实际上是一个无条件的反汇编,我们需要将条件分支之后的指令视为一条可能的执行路径。但是,您在该rom中发现的是,紧随其后的指令是实际数据,然后是一条指令。 a 1表示操作码字节a 2和3表示该指令的其他字节,更多伪代码

1 a = 0;
2
1 if(a == 0) goto somewhere
2
3
1 b = 7.
2
3
1
2
3

但是当我们继续解码所有假定有效的执行路径时,我们会发现

1 b = 7.
2 
3  <--- is a branch destination
1
2
3

这是一个操作码字节,而不是指令中的后一个字节,因此,现在存在冲突,一个好的反汇编程序将告诉您这一点。然后,人类必须去检查这些路径以确定a = 0 ....路径或b = 7哪一条路径是有效的。假设a = 0且其后的条件分支是有效反汇编的一部分,那么看起来那实际上是一个无条件分支,并且有几个数据字节或填充或随后在某些代码之后出现的任何内容。这可能是有意的,这在今天更常见的是有意抛弃反汇编程序的原因,或者这可能是由于手动破解二进制文件而不是重新构建整个项目并刻录ROM的结果。 (请继续阅读,我认为这是辩护人,是在第二天的贸易展览前一天晚上在酒店房间内窃取二进制文件)。这些字节可能是手动修改以绕过错误的其他指令。 6502是一个很好的起点,如果您想编写反汇编程序,那么很多此类游戏光盘都没有z80或8088/8086这样的指令,它们通过使用第二个字节将256条指令的原始潜能乘以更长的长度清单。早期的PIC或msp430作为第一个反汇编程序将容易得多,因为它们只有十几条或两条指令。 Msp430具有调试/支持的gnu后端(由于llvm尚未调试或不受支持,因此请避免使用它),因此,如果对学习指令集感兴趣,您可以轻松使用工具。

当您具有固定的指令长度(例如,不使用16位的mips时为mips)或当不使用16位的thumb时为arm时。 (并且指令集说指令必须对齐(而不是risc-v)。)您可以线性地分解通过内存,您发现的某些“指令”没有意义或不确定,但您稍后会仔细研究一下会将其视为数据而非指令,但将其视为指令将是有意义的。不幸的是,mips和arm具有完全不同的解码和不同规则的辅助指令集,因此您也不能简单地反汇编arm二进制文件,因为今天生成的某些编译器也需要按执行顺序进行操作,因此您更有可能获得大部分指令都已解码,但是会有一些跳转表使您的工作死胡同,从而使代码块无法正确反汇编。

因此,虽然有些罗word,但简短的答案仅是尽可能相信反汇编程序。如果从已知有效的入口点开始执行,并查看处理器的文档,则说明很容易解码。

相关问题