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

svdrlsy4  于 2022-11-13  发布在  其他
关注(0)|答案(3)|浏览(136)

此问题在此处已有答案

How to read the Intel Opcode notation(3个答案)
三年前就关门了。
如何确定字节数组将转换为机器代码?
我知道,如果我在开头看到0 f,它是一个2字节的指令,但我看到了其他前缀,在我的x64调试器中的一些反汇编中,我看到了奇怪的交互,如48 83 C4 38,我可以在操作码引用中看到48表示操作数是64字节。

但是83说它可以是7个不同的指令,这取决于一个名为“寄存器/操作码字段”的字段..什么?

有人能解释一下处理器如何使用这些字节来确定:
1.运行了什么指令
1.指令使用的寄存器和/或地址(如果有)

nwo49xxi

nwo49xxi1#

0x48是雷克斯前缀,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添加了186。不幸的是,复制与与(and eax, edx, 0xf)是不可编码的,但至少x86可以使用LEA进行复制与添加/sub。
每个指令自己的文档(例如add (html extract of the vol2 manual))显示如下编码:
REX.W + 83 /0 ib转换为ADD r/m64, imm8,这就是您所拥有的。
www.example.com上的ModRM位字段示意图wiki.osdev.org

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

0xc 4 = 0 b11000100,因此reg字段= 0。因此,我们的操作码是83 /0(采用英特尔的表示法)。

其余ModRM字段为:

  • mode = 0 b11,所以RM字段编码寄存器操作数,而不是用于寻址模式的基址寄存器。
  • rm = 0 b100. reg #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
toiithl6

toiithl62#

我看到一页纸上的字母,字母a,这可能是很多不同的单词,后面的字母是n。这可能是an,答案是,任何数量的单词,所以我继续。
x86和那个时代的其他机器代码,特别是直接从其派生的指令集,就是这样工作的。
首先,也是最重要的一点,如果你只是把一个程序的所有字节都取出来,然后跳到中间,这将没有任何意义,这非常非常容易让人一开始就犯错误“快速的棕色狐狸”“快速的棕色狐狸”“ickbrow”那是什么?处理器根据指令集的规则启动和继续,处理器是相当愚蠢的,它遵循处理器手册中定义的或至少文档中记录的规则。2只要程序员和工具创建了一个正确构造的程序,它就不会丢失,如果是,则是程序员/工具的错误,而不是处理器的错误。处理器将开始将操作码字节解码为操作码字节。该字节可以是整个指令,也可以是基于特定字节的一部分。如果是分数,则第一个字节加上其后的字节可以确定整个指令或者是分数。
特别是CISC中的操作码本身和部分后续字节可能包含或不包含表示相关内容的位。在RISC中,如mips或arm或其他指令中,0000在特定的please中表示寄存器0,0001表示寄存器1,以此类推。但在一些(如果不是很多)CISC指令中,没有一个位来区分寄存器x和寄存器y。必须在表中查找整个操作码才能知道它的含义。
x86是可变长度指令集,有些指令只有一个字节,没有其他操作数,有些则需要更多字节,然后可能是后面得立即数.要将立即数0x 12345678移到寄存器EAX,而不查看任何说明文档,说明它是5或6字节指令,或者是表示将立即数加载到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

虽然这些字节的位可能直接解码到四个寄存器中的一个,但不太可能,因为这些指令集不是这样设计的。
你可能把这个问题弄得过于复杂了,遗憾的是Intel和其他x86文档不如其他一些供应商。但它实际上只是一个流程图,相当容易解码第一个字节告诉你是否在寻找另一个字节,或者不是通过它的定义,下一个字节指示您是否需要进一步查看,等等。您不像解码MIPS或ARM或其他设计不同的代码那样解码X86。所有这些都有一个解码,说看看这些位,确定指令或确定我是否需要更多的位,但x86做一种方式,mips做另一种方式,arm做另一种方式,每一种都有利弊。
CISC与x86类似,但更像是一个流程图,第一个字节告诉你转到第X页,该页要么有完整的答案,要么说获得下一个字节,并根据该字节转到附录X中的第Y页。
有些房子只有一个居住者,地址/位置会带你找到一个人。有些房子有多个人,一旦你根据地址到达房子,那么你需要进一步的信息来确定哪个人或宠物是你感兴趣的。第一条信息,街道地址符合一个标准,但是用于隔离该房屋内的人/宠物的信息符合该房屋的标准。但根据操作码,如果存在额外的字节,则这些字节是特定于操作码的,如上文所述。对于0xB 8,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

对于0x 89操作码,则第二字节在这些情况下不是数据,而是进一步定义指令。
的确,第二个字节的解码并不仅仅是该操作码所特有的,许多指令将共享这些位的相同解码,例如,确定ah、al、ax、eax、bh、bl、bx......等。英特尔文档以及无数其他书籍和网站中都对此进行了记录。
真正的文档是芯片本身的源代码,因为我们很少有机会接触到我们所获得的文档,这些文档通常不是由逻辑作者编写的,然后可能由技术作者润色,在每一步中,一些信息可能会丢失或留下混乱。一些供应商比其他供应商更好,他们的文档的一些版本比其他版本更好。

x86几乎是您最不想学习的指令集,拥有一个指令集并不是一个合理的理由,对于您拥有的每一个x86来说,它的内部都有许多非x86处理器,而且对于您拥有的每一个x86来说,您都拥有相当多的非x86设备。如果教育和学习是您的目标,那么无论如何,您都应该从模拟器开始,这将大大提高您成功的机会。而崩溃的伤害也没有那么大。有更好的指令集可以开始使用,如msp430和pdp 11,这显然是什么影响了它。手臂,拇指,后来进入mips和它的细微差别,然后8位,我不会从x86开始,我会去与其他6502或其他东西。
然后,也许如果好奇的8088/8086使用模拟器和旧的docs在互联网上的方式回到机器,然后最后x86在80386,80486和x86-64。潜入x86-64首先必须是所有关于痛苦,真正为乡亲进入自我虐待。如果你仍然觉得你必须这样做,这条痛苦的道路较少痛苦的道路是开始与8088/8086使用旧的手册和dosbox或bochs或其他一些模拟器。一旦你得到了基础,然后他们在步骤中添加到32位,然后64位可能会更有意义,你不必被大量的保护随着时间的推移增加困惑,你可以开始干净和纯粹。
可变长度指令集的反汇编是一个需要解决的大问题,没有人解决过这个问题,因为他们不能完全解决。不可能。我过去学习所有新的指令集都是从反汇编器开始的。现在我可能会做一个模拟器来代替。有一半成功机会的唯一方法是从有效的入口点开始(s)。并按执行顺序解码,而不是线性地通过二进制。这将只暴露部分代码。剩余的代码(如果有的话)是基于数据的,您可以尝试模拟,但这也不是完美的。首先,反汇编时的数据可能会改变运行时间。你甚至可以模拟程序并运行它几天。数周的时间来发现特定指令正在查看的不同位置中的不同数据值,但仍然无法真正了解所有可能性。因此,一些反汇编程序只是出错,但却将其显示为正确的,而其他正确的反汇编程序则简单地说我不知道这是什么...
今天绝大多数的二进制文件都是编译的,所以数据路径基本上是健全和完整的。2但是去找一些单口相声视频游戏时代的ROM,比如小行星。3你会看到看起来像这样的伪代码:

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

我们可以很容易地看到条件分支实际上是一个无条件的反汇编,我们需要把条件分支后面的指令当作一个可能的执行路径。但是你会发现在ROM中后面的指令是实际的数据,而不是指令。a 1代表操作码字节a 2和3代表该指令的附加字节,更多的是伪代码
但是当我们继续解码所有假定有效的执行路径时,我们发现
即操作码字节而不是指令中的后面的字节,所以现在有一个冲突一个好的反汇编器会告诉你这个。然后人类必须去检查这些路径确定哪个是有效的a=0....路径或者b = 7。假设a = 0并且后面的条件分支是有效反汇编的一部分,则它看起来实际上是一个无条件分支,并且是一对数据字节或填充或其他什么,然后一些代码随后。这可能是故意的,因为这是更常见的一天,故意抛出一个反汇编程序,或者它可能是手动黑客的二进制,而不是重建整个项目和燃烧的ROM的结果。(读一读我想是后卫,在贸易展的前一天晚上在酒店房间里黑了二进制文件,然后第二天)。
这些字节可能是其他指令,被手工修改以绕过一个bug。6502是一个很好的起点,如果你想写一个反汇编程序,那么这些游戏ROM中的一些指令并不像z 80或8088/8086那样多,它们通过使用第二个字节将原来潜在的256条指令增加到一个更长的列表中。早期的PIC或msp430作为第一个反汇编器要容易得多,因为它们只有十几条或两条指令。Msp430有一个调试过的/支持的gnu后端(llvm后端没有调试过,也不支持,所以避免它),所以如果你对学习指令集感兴趣的话,你很容易得到工具。

当你有一个固定的指令长度,如mips当16位的一个不使用或arm当16位thumb不使用。(而且指令集说指令必须对齐(而不是risc-v))你可以通过内存线性反汇编,你发现的一些“指令”没有意义或者没有定义,但你只是磨过,人类以后会把那些看作数据而不是指令,但那些是指令的将有意义。不幸的是,mips和arm有二级指令集,解码完全不同,规则也不同,所以你也不能简单地反汇编arm二进制代码,对于今天编译器生成的东西,你也需要按照执行顺序来做,你更有可能得到大多数的指令解码,但是会有一些跳转表,使你的努力陷入死胡同,留下大块的代码没有被正确地反汇编。
因此,虽然很罗嗦,但简短的答案是,只要你能抛出反汇编程序,就相信它。如果你从一个已知有效的入口点开始按照执行顺序,并查看处理器的文档,这些指令就很容易解码。

8qgya5xd

8qgya5xd3#

这取决于具体的体系结构,不仅仅是x86-64,还有实际的芯片供应商。
本书有一整章专门介绍字节码中的命令语法,然后还有一章介绍每个可用的命令,图2.1给予了一个概念:

取自上述手册。例如,如果您使用ARM,则会发生变化。
这是人们可能要花几年时间才能“流利地阅读”字节码的东西,所以仅仅略读一下它只能给予你对语法有一个粗略的了解,或者是一个定位特定事物的好资源。

相关问题