基础和位移之间的差异

Fut*_*get 2 x86 assembly intel addressing-mode

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

imul   eax,DWORD PTR [esi+ebx*4-0x4]
Run Code Online (Sandbox Code Playgroud)

该指令是否意味着 => 将您在括号之间计算的地址处的值乘以 eax 并将其存储在同一寄存器(eax)中?如果是的话,我们是否像这样计算括号之间的地址?

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

我解码的第二条指令是这个

jmp    DWORD PTR [eax*4+0x80497e8]
Run Code Online (Sandbox Code Playgroud)

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

那么要获取括号内的地址,我们应该遵循这个顺序吗?

  1. 电子轴 * 4
  2. 将1.中的结果添加到地址0x80497e8
  3. 跳转到该地址

根据我的理解, [base + index * scales] 用于获取数组内部的值。基数是指向数组中第一个元素的指针。索引实际上是存储我们想要的值的索引,而比例是数组包含的日期的大小

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

Pet*_*des 5

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

Similarly, the most obvious interpretation of [esi+ebx*4-0x4] is array[i-1], with i in EBX and esi holding the start address of the array. It's an obvious optimization for the compiler to fold the -1 into the addressing mode instead of computing ebx-1 in another register and using that as the index.

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

It doesn't "mean" anything. The hardware just does binary addition and uses the result. It's up to the programmer (or compiler) to use an addressing mode that accesses the byte you want.

My answer on Referencing the contents of a memory location. (x86 addressing modes) has examples of when you might use every possible addressing mode for array indexing, with either a pointer to an array or a static array (so you can hard-code the array start address as an absolute displacement).


In technical x86 addressing mode terminology:

  • displacement: the +- constant part of an address, encoded in a 2's complement sign-extended disp8, or a disp32. (In 64-bit addressing modes, the disp32 is sign-extended to 64 bits).
  • offset: the result of the esi+ebx*4-0x4 calculation: the offset relative to the segment base. (In a normal flat memory model with base=0, the offset = the whole address).

    People often use "offset" to describe the displacement, and usually there's no confusion because it's clear from context they're talking about a constant offset (using the English word offset in a sense other than x86 seg:off), but I like to stick to "displacement" to describe the displacement.

  • base: the non-index register component of the addressing mode, if there is one. (The encoding for "no base register" instead means there's a disp32, and you can think of that as a base. It implies the DS segment.)

    This includes the case of having only an index and no base register: [esi*4] can only be encoded as [dword 0 + esi*4].


imul   eax,DWORD PTR [esi+ebx*4-0x4]
Run Code Online (Sandbox Code Playgroud)

Yes, eax *= memory source operand.

And yes, your address calculation is correct. Base + scaled index + signed displacement, resulting in a virtual address1.

"go to the address (result) and get the value inside it" is a weird way to describe it. "go to" would normally mean a control transfer, fetching the bytes as code. But that's not what happens, this is just a data load from that address, fully handled by hardware.

A modern x86 CPU (like Intel Skylake for example) decodes the imul eax, [esi+ebx*4 - 4] into two uops: an imul ALU operation and a load. The ALU operation has to wait for the load result. (Fun fact: the two micro-ops are actually micro-fused into a single uop for most of the pipeline, except for in the out-of-order scheduler. See https://agner.org/optimize/ for more.)

When the load uop runs, the address-generation unit (AGU) gets the 2 register inputs, the index scale factor (left shift by 2), and the immediate displacement (-4). The shift-and-add hardware in the AGU calculates the load address.

The next step inside the load execution unit is to use that address to load from L1d cache (which has the first-level L1dTLB virtual->physical cache basically built-in. L1d is virtually indexed, so the TLB lookup can happen in parallel with fetching the set of 8 tags+data from that way of L1d cache). Assuming a hit in the L1dTLB and L1d cache, the load execution unit receives a load result ~5 cycles later.

That load result is forwarded to the ALU execution unit as a source operand. The ALU doesn't care whether it was imul eax, ebx or a memory source operand; that multiply uop is just dispatched to the ALU as soon as both input operands are ready.


jmp    DWORD PTR [eax*4+0x80497e8]
Run Code Online (Sandbox Code Playgroud)

Yes, eax *4 is the scaled index.

是的,0x80497e8就是disp32位移。在这种情况下,寻址模式的位移部分可能被用作静态跳转表的地址。 您可以将其视为此寻址模式的基础。

跳转到该地址

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

你所描述的是

lea   eax, [eax*4+0x80497e8]       ; address calc
jmp   eax                          ; jump to code at that address
Run Code Online (Sandbox Code Playgroud)

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


脚注 1:我们假设一个平面内存模型(段基数 = 0),因此我们可以忽略分段,就像在 Linux、Windows、OS X 或几乎任何 32 或 64 位等普通操作系统下运行的代码一样操作系统。因此地址计算给出了一个线性地址。

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