基础和位移之间的区别

Difference betwen Base and Displacement

我在理解我遇到的两条指令时遇到了一些问题。
第一个如下:

imul   eax,DWORD PTR [esi+ebx*4-0x4]

这条指令的意思是 => 将你在括号之间计算出的地址的值乘以 eax 并将其存储在同一个寄存器 (eax) 中吗? 如果是这样,我们是否会像那样计算括号之间的地址?

  1. ebx * 4
  2. esi + 运算结果1
  3. 从结果中减去 4
  4. 转到地址(结果)并获取其中的值。

我遇到解码问题的第二条指令就是这条指令

jmp    DWORD PTR [eax*4+0x80497e8]

-eax *4是否等价于index * scale?
-0x80497e8是位移吗?

所以获取括号内的地址是我们应该遵循的顺序吗?

  1. eax * 4
  2. 将1.中的结果加到地址0x80497e8中
  3. 跳转到那个地址

在我的理解中,[base + index * scales] 用于获取数组内部的值。 base 是指向数组中第一个元素的指针。 索引字面意思就是存放我们想要的值的索引 比例是数组包含的日期的大小

我的问题是当你在方程中添加位移时,位移是用来做什么的? 当位移为负值时,这意味着什么?

不要被术语所迷惑。 "base" 具有特定的技术含义,寻址模式的 "base" 组件 没有 作为数组的开头。例如[esp + 16 + esi*4] 可能正在索引从 esp+16 开始的本地数组,即使 esp 是基址寄存器。

同样,[esi+ebx*4-0x4]最明显的解释是array[i-1],EBX中的iesi保存数组的起始地址。编译器将 -1 折叠到寻址模式而不是在另一个寄存器中计算 ebx-1 并将其用作索引是一个明显的优化。

And what does it mean when the displacement has a negative value?

它没有 "mean" 任何东西。硬件只是进行二进制加法并使用结果。由程序员(或编译器)使用访问所需字节的寻址模式。

我在 上的回答中有一些示例,说明何时可以使用数组索引的所有可能寻址模式,使用指向数组的指针或静态数组(这样您就可以对数组起始地址进行硬编码作为绝对位移)。


在技术 x86 寻址模式术语中:

  • 位移:地址的 +- 常量部分,以 2 的补码符号扩展 disp8disp32 编码。 (在 64 位寻址模式下,disp32 被符号扩展为 64 位)。
  • offset:esi+ebx*4-0x4计算的结果:相对于段base的偏移量。 (在 base=0 的普通平面内存模型中,偏移量 = 整个地址)。

    人们经常使用 "offset" 来描述位移,通常不会造成混淆,因为从上下文中可以清楚地看出他们在谈论常量偏移(在 x86 以外的意义上使用英文单词偏移 seg:off), 但我喜欢坚持用"displacement"来描述位移

  • base:寻址方式的非变址寄存器组成部分,如果有的话。 ("no base register" 的编码意味着有一个 disp32,您可以将其视为一个基础。它意味着 DS 段。)

    这包括只有索引没有基址寄存器的情况:[esi*4]只能编码为[dword 0 + esi*4]


imul   eax,DWORD PTR [esi+ebx*4-0x4]

是的,eax *= memory source operand

是的,您的地址计算是正确的。 Base + scaled index + signed displacement,得到一个虚拟地址1.

"go to the address (result) and get the value inside it" 是一种奇怪的描述方式。 "go to" 通常意味着控制传输,将字节作为代码获取。但事实并非如此,这只是来自该地址的数据加载,完全由硬件处理。

现代 x86 CPU(例如 Intel Skylake)将 imul eax, [esi+ebx*4 - 4] 解码为两个 uops:一个 imul ALU 操作和一个加载。 ALU 操作必须等待加载结果。 (有趣的事实:对于大部分管道,这两个微操作实际上被微融合成一个 uop,乱序调度程序除外。请参阅 https://agner.org/optimize/ 了解更多信息。)

加载uop运行时,地址生成单元(AGU)得到2个寄存器输入、索引比例因子(左移2)和立即位移(-4)。 AGU中的移位加硬件计算加载地址。

加载执行单元内部的下一步是使用该地址从L1d缓存加载(它具有一级L1dTLB虚拟->物理缓存基本上是内置的。L1d是虚拟索引的,所以TLB查找可以与从 L1d 缓存中获取 8 个标签+数据的集合并行发生)。假设在 L1dTLB 和 L1d 缓存中命中,加载执行单元会在大约 5 个周期后收到加载结果。

该加载结果作为源操作数转发给 ALU 执行单元。 ALU 不关心它是 imul eax, ebx 还是内存源操作数;一旦两个输入操作数都准备好,乘法运算符就被分派到 ALU。


jmp    DWORD PTR [eax*4+0x80497e8]

是的,eax *4是缩放索引。

对,0x80497e8就是disp32位移。在这种情况下,寻址模式的位移分量可能被用作静态跳转的地址table。你可以认为它是这种寻址模式的基础.

jump to that address

不,从该地址加载一个新的 EIP 值。由于方括号,这是一个内存间接跳转。

你描述的应该是

lea   eax, [eax*4+0x80497e8]       ; address calc
jmp   eax                          ; jump to code at that address

无法在一条指令中执行计算跳转,您始终需要将新的 EIP 值放在寄存器中或作为数据从内存中获取。


脚注 1:我们假设一个平面内存模型(段基数 = 0),因此我们可以忽略分段,就像代码 运行 在正常 OS 下的正常 [=122] =]、Windows、OS X,或几乎任何 32 位或 64 位 OS。所以地址计算给你一个线性地址。

我还假设分页已启用,就像主流 OS 下的正常情况一样,因此它是一个虚拟地址,必须通过缓存的页面 table 转换为物理地址TLB.