了解 X86 中寻址模式之间的细微差别

逆向工程 部件 x86
2021-06-28 22:37:12

我试图全面了解 X86 指令的所有可能寻址模式。从这里开始,我研究了英特尔 IA-32 参考资料和在线找到的多个辅助参考资料。

我想正确理解它们,所以这是我的疑问:

16 位寻址模式

  • mod == 0b11: 直接访问寄存器中包含的值,很清楚
  • mod != 0b11: 这些都是间接值,可选8位或16位到最终值的位移,所以我们指的是计算地址中包含的值。

我的疑惑:

  • 16 位位移是有符号的还是无符号的?例如mov ax, [SI + 40000]vsmov ax, [SI - 1000]
  • 究竟是怎么回事mod == 0b00 & R/M == 0b110它只是一个间接的绝对值,例如mov cl, [1234h],masm 编译为8b0e3412: mov cx, WORD PTR ds:0x1234
  • 所有这些间接寻址总是相对于一个段吗?从参考文献看来,在 16 位模式下,一切都始终与 DS 相关,除非 BP 包含在间接地址中,在这种情况下使用 SS(或使用特定的段覆盖)。所以基本上[BP+SI+10h]总是意味着SS:[BP+SI+10h]SS段向左移动4位。
  • 在这种情况下,67h 前缀的确切作用是什么?如果我使用 67h 前缀,就像将 16 位寻址表与 32 位寻址切换,反之亦然?(根据当前的执行模式)。
  • 那么 66 小时呢?它是否只是改变了在 16 位和 32 位之间移动的数据的“大小”?例如,强制 32 位操作数大小意味着将选择 32 位寄存器并且总是从间接地址获取 4 个字节的内存,反之亦然?

现在是 32 位寻址模式

32种寻址模式

  • mod == 0b11: 直接值,至于16位,很清楚
  • mod == 0b00 && R/M == 0b101:对于 16 位寻址情况的原始值
  • mod != 0b11 && R/M == 0b100: R/M不指定寄存器而是SIB模式,所以我们可以指定一个基址寄存器+一个索引寄存器+一个标度值

这里一切都足够清楚了,我只是想知道,对于 16 位,32 位的位移是有符号的还是无符号的?如果我理解正确,SIB 和位移可以很容易地组合在一起,例如[EAX + EBX*2 + 10]将生成一个mod == 01具有特定 SIB 字节和附加单字节的有符号位移。这些值在平面内存空间中是否被认为是绝对的,或者在这里也必须考虑段?

1个回答

一下子有很多问题,我至少会回答其中的一些。但是,请不要,除非您自己编写汇编器或反汇编器,否则您不应该真正深入了解每一位的血腥细节。除非您在汇编程序中进行了大量编程,并且阅读和理解了反汇编代码,否则您甚至不应该尝试自己编写汇编程序或反汇编程序。

不要误解我,写自己的汇编可以是一个有趣和教育经验。但是,要了解处理器的概念及其汇编语言,首先要了解的不是这些血腥的细节。

顺便说一句:

16 位位移是有符号还是无符号都没有关系。如果它溢出,它就会溢出,它总是被削减到 16 位。因此,如果您将 offset 添加0xfff0到 address 0x1234,您将得到0x1224结果。它不一样,如果解释为“真正的问题0xfff0是相等-0x0010的,所以我们减去0x100x1234”或“添加0xfff00x1234,拿0x11224,并去掉溢出位”。或者,如果添加0x89ab0x89ab,则会得到0x13560x11356确切地说,溢出位被剥离。如果您采用0x89abas (decimal)35243-30293. 可能的结果 - 35243+35243=70486, 35243-30293=4950, -30293-30293=-60586- 都具有相同的表示 - 0x1356- 16 位十六进制。

是的,mod=0b00和 R/M=0b110只是一个间接地址。mov cx, [1234h]并且mov cx, WORD PTR ds:0x1234是写同一件事的两种方式。注意我更正了你clcx是否使用 8 位或 16 位寄存器是指令的一部分,而不是寻址模式的一部分。如果您有一个寄存器,则从寄存器名称中可以清楚地mov [1234h],5知道大小,但是在类似 的指令中,您不知道 5 是字节、字还是双字值。mov word ptr ds:1234h, 5清楚地说明了这一点。

是的,所有地址都与所选段相关 -ds在大多数情况下,ss如果您使用bp,并且如果您使用显式覆盖前缀,则给定寄存器。请注意,sp在 16 位模式下没有相对于索引的方法,并且bp,如果使用,则始终是R1+R2组合中的第一个寄存器,强制ssbp. 在 32 位模式下,可以有更多的组合,并且[ebp+ebx]使用ss,而[ebx+ebp]使用 ds。(但是,32 位模式也意味着受保护模式,并且在除最病态的情况之外的所有情况下,操作系统对ss使用相同的选择器值dscs以及。见下文)。

所以[BP+SI+10h]意味着[SS:BP+SI+10h],这意味着(SS<<4 + BP + SI + 10h)在地址总线上。请注意,那些 16 位处理器在地址总线上有 20 位,这意味着可能会发生溢出,并且溢出位也被切断了。所以,FFF0:0010 和 0000:0000 实际上是 8086 上的相同地址00000——因为第 20 位100000被切断了。在 32 位处理器上,这个位 20 实际上存在。这意味着一些使用该机制来混淆其复制保护的程序在引入 80386 时停止工作。或者,如果 IBM 没有围绕它发明一种机制 - 邪恶的 A20 门。如果您愿意,请谷歌搜索。

前缀66h67h- 问别人。尽管我已经阅读和编写汇编代码 20 多年了,但我从来没有理由了解十六进制字节和处理器指令之间的关系。往上看。好吧,我想有两个例外:90hisNOPcchis INT3而像PQRST, 之50h 51h 52h 53h 54h类的字节序列是压入寄存器指令,这使得它们对于定位过程开始很有用。

在 32 位模式中,位移就像在 16 位模式中一样“有符号”或“无符号”。只需将它们视为添加的 32 位值,这可能会导致溢出并被丢弃。

当然,这些值也是相对于“段”来考虑的。只是那个 32 位意味着保护模式,这意味着这些段被称为选择器,具有不同的语义,并且通常被大多数应用程序程序员忽略。

一个词来细分,为什么它们很重要,并且(通常)不再重要:

起初,当 8086 推出时,它旨在取代旧的 8080 处理器(Z80 来自不同公司,与 8080 兼容,但更好、更成功)。8080 总共最多有 64 KB,因此程序员必须将所有内容——代码、数据、堆栈——压缩到这 64 KB 中,而且大多数情况下,这 64 KB 的一部分被硬件使用,所以你有更少.

当设计 8086 和段寄存器时,英特尔的某个人可能认为“我们给人们提供了更多的空间 - 64 kb 代码和 64 kb 数据和 64 kb 堆栈,因此程序可以更大;我们可以在几个程序,操作系统会管理段寄存器为每个程序分配空间,每个程序都可以比今天大得多”。

但事实上,程序变得越来越大,所以“段寄存器应该只关注操作系统”的想法从未被使用过。相反,程序必须自己处理段,这是从编译器构建者到应用程序程序员的每个人的主要 PITA,每个人都必须学习 - 并了解 - 以完成任何事情。

当 32 位处理器开始时,4 GB 可以以线性方式寻址,段突然变得足够大,以至于应用程序程序员不必再关心它们了。如今,处理段并将它们分配给内存映射严格来说是操作系统的任务,并且由于保护模式,程序即使想要也无法更改它们。那么,是什么大多数操作系统做的是提供一个内存单平块的程序,有csdsesss映射到该块相同。您的应用程序只看到 4 GB 的可寻址内存(但并非所有这些都需要真正映射到物理内存),并且应用程序使用哪个段寄存器不再重要 -[DS:1234][ES:1234]与 相同[SS:1234]与 相同[CS:1234]

唯一的例外,这是新的寄存器FSGS,例如,Windows使用FS结构化异常处理和Linux使用GS线程本地存储这些段没有映射到标准的 4 GB 块,但应用程序不会注意到,因为这些寄存器都不会在没有明确前缀的情况下使用。(注意ES不能以同样的方式使用,因为指令喜欢stos[bwd]默认movs[bwd]使用ES:EDI)。