基础和位移之间的区别
Difference betwen Base and Displacement
我在理解我遇到的两条指令时遇到了一些问题。
第一个如下:
imul eax,DWORD PTR [esi+ebx*4-0x4]
这条指令的意思是 => 将你在括号之间计算出的地址的值乘以 eax 并将其存储在同一个寄存器 (eax) 中吗?
如果是这样,我们是否会像那样计算括号之间的地址?
- ebx * 4
- esi + 运算结果1
- 从结果中减去 4
- 转到地址(结果)并获取其中的值。
我遇到解码问题的第二条指令就是这条指令
jmp DWORD PTR [eax*4+0x80497e8]
-eax *4是否等价于index * scale?
-0x80497e8是位移吗?
所以获取括号内的地址是我们应该遵循的顺序吗?
- eax * 4
- 将1.中的结果加到地址0x80497e8中
- 跳转到那个地址
在我的理解中,[base + index * scales] 用于获取数组内部的值。
base 是指向数组中第一个元素的指针。
索引字面意思就是存放我们想要的值的索引
比例是数组包含的日期的大小
我的问题是当你在方程中添加位移时,位移是用来做什么的?
当位移为负值时,这意味着什么?
不要被术语所迷惑。 "base" 具有特定的技术含义,寻址模式的 "base" 组件 没有 作为数组的开头。例如[esp + 16 + esi*4]
可能正在索引从 esp+16
开始的本地数组,即使 esp
是基址寄存器。
同样,[esi+ebx*4-0x4]
最明显的解释是array[i-1]
,EBX中的i
和esi
保存数组的起始地址。编译器将 -1
折叠到寻址模式而不是在另一个寄存器中计算 ebx-1
并将其用作索引是一个明显的优化。
And what does it mean when the displacement has a negative value?
它没有 "mean" 任何东西。硬件只是进行二进制加法并使用结果。由程序员(或编译器)使用访问所需字节的寻址模式。
我在 上的回答中有一些示例,说明何时可以使用数组索引的所有可能寻址模式,使用指向数组的指针或静态数组(这样您就可以对数组起始地址进行硬编码作为绝对位移)。
在技术 x86 寻址模式术语中:
- 位移:地址的 +- 常量部分,以 2 的补码符号扩展
disp8
或 disp32
编码。 (在 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.
我在理解我遇到的两条指令时遇到了一些问题。
第一个如下:
imul eax,DWORD PTR [esi+ebx*4-0x4]
这条指令的意思是 => 将你在括号之间计算出的地址的值乘以 eax 并将其存储在同一个寄存器 (eax) 中吗? 如果是这样,我们是否会像那样计算括号之间的地址?
- ebx * 4
- esi + 运算结果1
- 从结果中减去 4
- 转到地址(结果)并获取其中的值。
我遇到解码问题的第二条指令就是这条指令
jmp DWORD PTR [eax*4+0x80497e8]
-eax *4是否等价于index * scale?
-0x80497e8是位移吗?
所以获取括号内的地址是我们应该遵循的顺序吗?
- eax * 4
- 将1.中的结果加到地址0x80497e8中
- 跳转到那个地址
在我的理解中,[base + index * scales] 用于获取数组内部的值。 base 是指向数组中第一个元素的指针。 索引字面意思就是存放我们想要的值的索引 比例是数组包含的日期的大小
我的问题是当你在方程中添加位移时,位移是用来做什么的? 当位移为负值时,这意味着什么?
不要被术语所迷惑。 "base" 具有特定的技术含义,寻址模式的 "base" 组件 没有 作为数组的开头。例如[esp + 16 + esi*4]
可能正在索引从 esp+16
开始的本地数组,即使 esp
是基址寄存器。
同样,[esi+ebx*4-0x4]
最明显的解释是array[i-1]
,EBX中的i
和esi
保存数组的起始地址。编译器将 -1
折叠到寻址模式而不是在另一个寄存器中计算 ebx-1
并将其用作索引是一个明显的优化。
And what does it mean when the displacement has a negative value?
它没有 "mean" 任何东西。硬件只是进行二进制加法并使用结果。由程序员(或编译器)使用访问所需字节的寻址模式。
我在
在技术 x86 寻址模式术语中:
- 位移:地址的 +- 常量部分,以 2 的补码符号扩展
disp8
或disp32
编码。 (在 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.