我在遇到两条指令时遇到了一些问题。
第一个如下:
imul eax,DWORD PTR [esi+ebx*4-0x4]
此指令是否意味着=>将您在括号之间计算的地址的值乘以eax并将其存储在同一寄存器(eax)中? 如果是这样,我们是否像这样计算括号之间的地址?
我解码的第二条指令就是这个
jmp DWORD PTR [eax*4+0x80497e8]
-eax * 4等于索引*标度吗?
-0x80497e8是位移吗?
因此要在方括号内获取地址,这是我们应该遵循的顺序吗?
在我的理解中,[base + index * scales]用于获取内部和数组中的值。 基数是指向数组中第一个元素的指针。 索引实际上是我们想要的值存储在其中的索引 比例尺是数组包含的日期的大小
我的问题是,当您在方程式中添加位移时,位移用于什么目的? 当位移为负值时,这意味着什么?
答案 0 :(得分:2)
不要被术语愚弄。 “基础”具有特定的技术含义,寻址模式的“基础”组件不必是数组的开始。例如[esp + 16 + esi*4]
可能正在索引以esp+16
开始的本地数组,即使esp
是基址寄存器。
类似地,对[esi+ebx*4-0x4]
的最明显的解释是array[i-1]
,其中EBX中的i
和esi
保存着数组的起始地址。对于编译器而言,将-1
折叠为寻址模式而不是在另一个寄存器中计算ebx-1
并将其用作索引是显而易见的优化。
当位移为负值时是什么意思?
它不“意味着”任何东西。硬件只是进行二进制加法并使用结果。由程序员(或编译器)决定使用一种寻址模式来访问所需的字节。
我对Referencing the contents of a memory location. (x86 addressing modes)的回答提供了一些示例,说明了何时可以使用每种可能的寻址模式进行数组索引,并使用指向数组或静态数组的指针(因此您可以将数组的起始地址硬编码为绝对位移)。
在x86技术寻址模式术语中:
disp8
或disp32
编码。 (在64位寻址模式下,disp32
被符号扩展为64位。) 偏移量:esi+ebx*4-0x4
计算的结果:相对于线段基准的偏移量。 (在base = 0的普通平面内存模型中,偏移量=整个地址)。
人们经常使用“偏移量”来描述位移,并且通常没有混淆,因为从上下文中很明显,他们在谈论恒定偏移量(使用x86 seg:off
以外的英语单词offset) ,但是我喜欢使用“位移”来描述位移。
基:寻址模式的非索引寄存器组件(如果有的话)。 (“无基址寄存器”的编码表示有一个disp32
,您可以将其视为基数。它表示DS段。)
这包括只有索引而没有基址寄存器的情况:[esi*4]
只能编码为[dword 0 + esi*4]
。
imul eax,DWORD PTR [esi+ebx*4-0x4]
是的,eax *= memory source operand
。
是的,您的地址计算是正确的。底数+缩放的索引+带符号的位移,产生一个虚拟地址 1 。
“转到地址(结果)并获取其中的值”是一种怪异的描述方式。 “转到”通常意味着控制转移,将字节作为代码获取。但这不是事实,这只是从该地址加载的数据,完全由硬件处理。
现代的x86 CPU(例如Intel Skylake)将imul eax, [esi+ebx*4 - 4]
解码为两个指令:一个脉冲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
还是内存源操作数。一旦两个输入操作数都准备就绪,乘法uop就会立即分派到ALU。
jmp DWORD PTR [eax*4+0x80497e8]
是的,eax *4
是缩放的索引。
是的,0x80497e8
是disp32位移。在这种情况下,寻址模式的位移部分可能被用作静态跳转表的地址。您可以将其视为该寻址模式的基础。
跳转到该地址
否,请从该地址加载新的EIP值。由于方括号,这是内存间接跳转。
您描述的将会是
lea eax, [eax*4+0x80497e8] ; address calc
jmp eax ; jump to code at that address
无法在一条指令中进行计算的跳转,您始终需要将新的EIP值存储在寄存器中或从内存中获取数据。
脚注1:我们假设使用平面内存模型(段基= 0),因此我们可以忽略分段,就像在Linux,Windows,OS X或几乎任何32或32 64位操作系统。因此,地址计算会为您提供线性地址。
我还假设像在主流OS上正常启用分页一样,因此这是一个虚拟地址,必须通过TLB缓存的页表将其转换为物理地址。