我在一页上看到字母,字母a,这可能是许多不同的单词,它后面的字母是n。这可能是一个,并且,答案,任意数量的单词,所以我继续。
x86 和那个时代的其他机器代码以这种方式工作,尤其是直接派生它的指令集。
首先也是最重要的,如果你只是拿走程序的所有字节然后跳到中间,这没有任何意义,很容易走错脚“the quick brown fox”“thequickbrownfox ” “ickbrow”那是什么?处理器根据指令集的规则启动和继续,处理器相当愚蠢,它遵循处理器手册中定义或至少记录的规则。只要程序员和工具创建了一个正确构造的程序,它就不会丢失,如果是这样,那是程序员/工具的错,而不是处理器的错。处理器将开始将操作码字节解码为操作码字节。该字节可以是整个指令,也可以只是基于特定字节的一小部分。如果是分数,那么第一个字节加上它后面的字节可能决定整个指令或者是一个分数。
CISC 尤其是操作码本身以及部分下一个字节可能包含也可能不包含表示相关内容的位。在像 mips 或 arm 或其他特定的 RISC 中,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 个字节。虽然这些字节的位可能会直接解码到四个寄存器之一,但不太可能,因为这些指令集不是这样设计的。
您可能过于复杂了,遗憾的是,英特尔和其他 x86 文档不如其他一些供应商好。但它实际上只是一个流程图,相当容易解码第一个字节根据其定义告诉您是否正在寻找另一个字节,下一个字节指示您是否需要进一步查看等等。您不会像解码 mips 或 arm 或其他设计不同的东西那样解码 x86。它们都有一个解码,说查看这些位并确定指令或确定我是否需要更多位,但是 x86 以一种方式执行,mips 以另一种方式执行,arm 以另一种方式执行。各有利弊。
像 x86 一样的 CISC 虽然更像是一个流程图,但第一个字节告诉您转到第 X 页,该页要么有完整的答案,要么它说获取下一个字节并基于此转到附录 X 中的第 Y 页。
有些房子只有一个住户,地址/位置会将您带到一个人。有些人不止一个,一旦你根据地址到达房子,那么你需要更多信息来确定你感兴趣的人或宠物。第一条信息,街道地址符合标准,但用于隔离该房屋内的人/宠物的信息符合该房屋的标准。指令的第一个字节是操作码。但是基于操作码,如果有额外的字节,那么这些字节是特定于操作码的,正如我们在上面看到的。 b8 78 56 34 12 对于 0xB8,第二个字节是立即值的一部分。有很多你可以查找第二个字节在哪里进一步解码指令
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 ,这显然是影响它的原因。手臂,拇指,后来进入 mips 及其细微差别,然后在我不会从 x86 开始的 8 位中,我会选择其他 6502 或其他东西。然后也许如果好奇 8088/8086 使用模拟器和互联网回程机器上的旧文档,那么最后是 x86,如 80386、80486 和 x86-64。首先深入 x86-64 肯定是为了痛苦,真正让人们自虐。如果你仍然觉得你必须这样做,那么这条痛苦路径中不那么痛苦的路径是从 8088/8086 开始,使用旧手册和 dosbox 或 bochs 或许多其他仿真器。一旦你打好了基础,那么他们在 32 位和 64 位的步骤中添加的内容可能更有意义,并且您不必被随着时间的推移添加的大量保护所迷惑,您可以开始干净和纯粹。
可变长度指令集的反汇编是一个需要解决的大问题,没有人解决它,因为他们不能完全解决。不可能。我曾经从反汇编程序开始学习所有新指令集。这些天我可能会做一个模拟器。获得一半成功机会的唯一方法是从有效的入口点开始。并按执行顺序解码,而不是通过二进制线性解码。那只会暴露一些代码。其余的(如果有的话)是基于数据的,您可以尝试模仿,但这也不是完美的。一方面,反汇编时的数据可能会改变运行时间。您甚至可以模拟该程序并运行它数天/数周,以发现特定指令正在查看的不同位置的各种数据值,但仍不能真正了解所有可能性。所以有些反汇编程序只是弄错了,但向您展示它好像它是正确的,而其他人则正确地告诉您,只是说我不知道这是什么......
如今,绝大多数二进制文件都已编译,因此数据路径大多是健全和完整的。但是从站立视频游戏日中获取一些 rom,例如小行星。你会看到类似这样的伪代码:
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 是一个很好的起点,如果您想编写反汇编程序,那么许多游戏 rom 没有像 z80 或 8088/8086 那样多的指令,它们通过使用第二个字节将 256 条指令的原始潜力乘以更长的列表。早期的 PIC 或 msp430 作为第一个反汇编程序会容易得多,因为它们只有一打或两条指令。 Msp430 有一个经过调试/支持的 gnu 后端(llvm 没有经过调试也不支持,因此请避免使用它),因此如果对学习指令集感兴趣,您可以轻松获得工具。
当你有一个固定的指令长度时,比如不使用 16 位指令时的 mips 或不使用 16 位拇指时的 arm。 (并且指令集说指令必须对齐(而不是 risc-v))您可以通过内存线性反汇编,您发现某些“指令”没有意义或未定义,但您只是磨练,稍后人类会将这些视为数据而不是指令,但那些指令将是有意义的。不幸的是 mips 和 arm 有辅助指令集,它们的解码方式和规则完全不同,所以你也不能简单地反汇编一个 arm 二进制文件,对于今天编译器生成的东西,你也需要按执行顺序来做,你更有可能得到大多数指令已解码,但会有一些跳转表使您的工作陷入僵局,导致代码块未正确反汇编。
因此,虽然罗嗦,但简短的回答是尽可能相信反汇编程序。如果您从已知有效的入口点按执行顺序执行指令并查看处理器的文档,这些指令很容易解码。