[ebp * 2]引用DS或SS段吗?

时间:2018-04-08 19:19:40

标签: assembly x86 nasm instructions opcode

IDM表示如果EBP用作基址寄存器,则内存操作使用SS段。因此,[ebp + esi][esi + ebp]分别引用了SS和DS细分。请参阅NASM的文档:3.3 Effective Address

在上述同一部分中,NASM提到了如何通过将[eax*2]替换为[eax+eax]来生成更短的机器代码。

但是,NASM还为[ebp + ebp]生成[ebp*2](即没有基址寄存器)。

我怀疑[ebp+ebp]引用了SS段,[ebp*2]引用了DS段。

我问NASM这个问题。他们认为[ebp*2][ebp+ebp]是相同的,但它对我没有意义。显然,[ebp+ebp](ebp作为基址寄存器)引用SS段。如果它们相同,[ebp*2也必须引用SS。这意味着只要ebp是基数或索引寄存器,就会引用SS,这反过来意味着[ebp + esi][esi + ebp]引用SS段,因此它们必须相同。

有谁知道[ebp*2]使用哪个细分受众群?

2 个答案:

答案 0 :(得分:12)

英特尔手册告诉我们下面的图3-11,它涉及Offset = Base + (Index * Scale) + Displacement

  

通用寄存器作为基本组件或索引组件的使用受到以下方式的限制:

     
      
  • ESP寄存器不能用作索引寄存器。
  •   
  • 当ESP或EBP寄存器用作 base 时,SS段是默认段。在所有其他情况中,DS细分是默认细分。
  •   

这意味着将[ebp*2]更改为[ebp+ebp]时NASM出错(为了避免32位位移)。

[ebp*2]使用DS,因为ebp 未用作基础
[ebp+ebp]使用SS,因为其中一个ebp 用作基础

最好指明您不希望NASM发生此行为 在NASM作者意识到他们的错误之前,您可以通过编写以下内容来禁用此行为(将EBP用作索引):

[NoSplit ebp*2]

答案 1 :(得分:1)

确实,NASM的优化选择是不一致的,假设ssds在将[ebp*2]拆分为[ebp+ebp]时可以互换(即平面内存模型)保存3个字节(disp32与disp8),但优化[ebp + esi][esi + ebp]以避免disp8。

(和the NASM manual even mentions the different default segment,与您从[0 + ebp*2][0+ebp+ebp*1]的错误信息中得出的结论相矛盾。)

EBP或ESP 作为基址寄存器表示SS,否则默认为DS。当在NASM寻址模式中使用两个寄存器时,第一个寄存器是基数,除非您编写[ebp*1 + esi],明确将比例因子应用于第一个。索引寄存器从不暗示一个段,如果你考虑设计意图,这是有意义的:相对于一个段的索引:由基址寄存器给出的偏移量或绝对值disp32

如上所述,[ebp*2]是一种索引寻址模式,隐式地需要4个字节的零作为32位位移。您可以让NASM使用[nosplit ebp*2]进行编码。

也许NASM和YASM忽略了这个角落的情况,因为平面内存模型在16位代码之外几乎是通用的。(和16位寻址模式不同,不支持扩展虽然你可以在16位代码中使用32位寻址模式来利用比例因子和更广泛的寄存器选择,即使在纯实模式而不是"unreal" modeset segment limits high enough that offsets > 2^16 are usable。)

所有主流的32位和64位x86操作系统都使用平面内存模型,其中SS和DS是可互换的,当您不做任何奇怪的操作时,在这些操作系统下进行优化是非常安全的。分段有时是used to make non-executable stacks before that was supported by page tables,但这仍然是一个平坦的内存模型。 (64位代码修复了CS / DS / ES / SS的基本/限制,因此除非SS完全不可用,否则此优化始终是安全的,如果可能的话,可能会写保护。 )

但是,任何平坦内存模型的假设都应该是可选的。这是NASM和YASM中的一个错误。他们要么应该尊重SS和DS之间的区别,要么应该充分利用平坦的内存模型来帮助那些不记得哪些寻址方式有隐藏的程序员。需要额外的字节,例如优化[ebp+esi]而没有位移到[esi+ebp]。最好应该有一个选项或指令告诉汇编程序它可以假设SS和DS是相同的。

LEA的操作数总是可以利用,因为LEA只处理地址的偏移部分,因此段是无关紧要的。 (对于像[ebp*2]这样没有位移的寻址模式,这将是最常见的用例:使用它作为内存地址可能会模拟字可寻址的内存吗?这很奇怪,通常在那里&#39 ;一个真正的指针作为地址的一个组成部分。)

了解x86 32/64位寻址模式

除64位RIP相对寻址外,32/64位寻址模式是 disp0/8/32 + base_reg + idx_reg*1/2/4/8 的任何子集,其中3个术语/组件中的每一个都是可选的。 但是至少需要一个disp32或基址寄存器。 (另见Referencing the contents of a memory location. (x86 addressing modes))。

[disp32=0 + ebp*2](使用disp32 = 0)具有默认段= DS。您可以从[nosplit ebp*2]在NASM中获取此编码,并且[ebp*4]之类的地址无法拆分。

[ebp + ebp + disp8=0]具有默认段= SS,因为EBP用作基址寄存器。

意味着ebp没有位移的编码实际上意味着没有基本注册的disp32,因此disp32实际上是基数(暗示段寄存器DS,因为基础不是EBP或ESP)。无论是否有SIB字节都是这种情况,因此[ebp + ebp*1]仍然必须使用disp8 = 0进行编码。其他寄存器没有这个问题,因此通常分割可以节省4个字节而不是3个EBP。 (除了使用与RBP相同的ModR / M编码的r13之外,我想解码硬件的一部分并不需要来自REX前缀的额外位。)

ESP不能成为索引寄存器,因此无论是否进行拆分,[esp*2]都无法进行编码。因此,NASM的优化的特殊情况会影响EBP*2。 (base = ESP是SIB字节的转义码,SIB字节中的index = ESP表示没有索引,允许您对[esp + 12]进行编码。)

但遗憾的是,NASM / YASM拆分EBP*2即使有一个常量需要一个disp32 ,例如[symbol + ebp*2],它不会保存任何字节事实上hurts performance for LEA (but not loads/stores) on Sandybridge-family CPUs。 3分量lea eax, [symbol + ebp + ebp*1]慢于2分量lea eax, [symbol + ebp*2]:更高的延迟和1个每时钟的吞吐量而不是2个。根据http://agner.org/optimize/,那些在AMD Bulldozer上同样会很慢/ Ryzen,因为一个缩放的索引使得一个"慢LEA"即使只有2个组件。

IDK,如果任何旧CPU在未缩放索引和3分量寻址模式下表现更好,对于LEA或实际内存操作数。

NASM和YASM行为

 $ nasm -felf32 -g -Fdwarf foo.asm
 $ objdump -drwC -Mintel -S foo.o | sed 's/DWORD PTR//'
 # (edited to put the NASM source line's addressing mode onto the same line as the disassembler output, instead of separate lines)
00000000 <sym-0x2c>:
   0:   8b 04 2e                mov    eax, [esi+ebp*1]         ; [esi+ebp]
   3:   8b 44 35 00             mov    eax, [ebp+esi*1+0x0]     ; [ebp + esi]
   7:   8b 04 2e                mov    eax, [esi+ebp*1]         ; [ebp*1 + esi]
   a:   8b 44 2d 00             mov    eax, [ebp+ebp*1+0x0]     ; [ebp*2]
   e:   8b 04 6d 00 00 00 00    mov    eax, [ebp*2+0x0]         ; [nosplit ebp*2]
  15:   8b 45 00                mov    eax, [ebp+0x0]           ; [ebp*1]   ; "split" into base=ebp with no SIB byte
  18:   8b 04 2d 00 00 00 00    mov    eax, [ebp*1+0x0]         ; [nosplit ebp*1]
  1f:   8b 84 2d d2 04 00 00    mov    eax, [ebp+ebp*1+0x4d2]   ; [ebp*2 + 1234]   ; bad split for LEA, neutral on modern CPUs for load/store
  26:   8b 85 15 cd 5b 07       mov    eax, [ebp+0x75bcd15]     ; [ebp*1 + 123456789]
sym:       ; using a symbol reference instead of a numeric constant doesn't change anything
  2c:   8b 84 2d 2c 00 00 00    mov    eax, [ebp+ebp*1+0x2c]    2f: R_386_32    .text   ; [ebp*2 + sym]
  33:   8b 84 2d 2c 00 00 00    mov    eax, [ebp+ebp*1+0x2c]    36: R_386_32    .text   ; [sym + ebp*2]

YASM将所有这些案件与NASM完全相同。