IDM说,如果使用EBP作为基寄存器,则内存op使用SS段。因此,[ebp + esi]和[esi + ebp]分别引用SS和DS段。参见NASM的文档:3.3有效地址。
在上述章节中,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]使用哪个片段吗?
发布于 2018-04-08 21:36:33
Intel手册告诉我们下面的图3-11,它处理Offset = Base + (Index * Scale) + Displacement
一般用途寄存器作为基本或索引组件的使用受到下列限制:
这意味着,当将[ebp*2]转换为[ebp+ebp]时,NASM是错误的(以避免32位移位)。
[ebp*2]使用DS,因为ebp不用作基
[ebp+ebp]使用SS,因为其中一个ebp用作基
然后最好指定您不想要NASM的这种行为。
在NASM作者意识到自己的错误之前,您可以通过以下方式禁用这种行为(其中EBP被用作索引):
[NoSplit ebp*2]
发布于 2018-04-10 03:53:32
事实上,NASM的优化选择是不一致的,假设ss和ds在将[ebp*2]拆分为[ebp+ebp]以保存3个字节(disp32与disp8)时是可互换的(即一个平面内存模型),而不是将[ebp + esi]优化为[esi + ebp]以避免disp8。
( NASM手册甚至提到了不同的默认段。,与你从关于[0 + ebp*2]和[0+ebp+ebp*1]的错误信息中得出的结论相矛盾。)
EBP或ESP 作为基本寄存器意味着SS,否则默认为DS。当在NASM寻址模式中使用两个寄存器时,第一个寄存器是基本寄存器,除非您编写[ebp*1 + esi],否则显式地将比例因子应用于第一个寄存器。索引寄存器绝不意味着段,如果考虑到设计意图,这是有意义的:一个相对于段的索引:由基本寄存器或绝对disp32提供的偏移量。
正如所写的,[ebp*2]是一种索引寻址模式,隐式地需要4字节的零作为32位的移位。您可以让NASM用[nosplit ebp*2]那样对其进行编码。
可能忽略了这一情况,因为平面内存模型在16位代码之外几乎是通用的,(和16位寻址模式不同,不支持规模因素)。尽管您可以在16位代码中使用32位寻址模式来利用规模因素和更广泛的寄存器选择,即使是在纯实模式下,而不是允许您使用“虚幻”模式的set segment limits high enough that offsets > 2^16 are usable模式下)。
所有主流的32位和64位x86 OSes都使用一个平面内存模型,其中SS和DS是可互换的,当您不做任何奇怪的事情时,这种优化在这些OSes下是安全的。分割有时是用于在页表支持之前创建不可执行的堆栈。,但这仍然是一个扁平的内存模型。(64位代码修正了CS/DS/ES/SS的基本/限制,因此这种优化在那里总是安全的,除非SS是一个完全不可用的段,如果可能的话,可能是写保护的。)
仍然认为,任何关于平面内存模型的假设都应该是可选的。这是NASM和YASM中的一个bug。他们要么尊重SS和DS之间的区别,要么充分利用平面内存模型来帮助那些不记得哪些寻址模式需要“隐藏”额外字节的程序员,比如优化[ebp+esi]而不将其替换成[esi+ebp]。最好应该有一个选项或指令来告诉汇编程序它可以假设SS和DS是相同的。
LEA的操作数总是可以利用的,因为LEA只处理地址的偏移部分,因此段是无关的。(这将是像[ebp*2]这样没有移位的寻址模式的最常见的用例:使用它作为内存地址可能会模仿可寻址的内存?这很奇怪,通常有一个真正的指针作为地址的一个组成部分。)
理解x86 32/64位寻址模式
除64位RIP相对寻址外,32/64位寻址模式是disp0/8/32 + base_reg + idx_reg*1/2/4/8,的任意子集,其中三个项/组件中的每一个都是可选的。但至少需要一个disp32或基本寄存器。(另见引用内存位置的内容。(x86寻址模式))。
[disp32=0 + ebp*2] (与disp32=zero一起)具有默认段= DS。您可以从[nosplit ebp*2]获得NASM中的这种编码,并且不能分割像[ebp*4]这样的地址。
[ebp + ebp + disp8=0]具有默认段= SS,因为EBP用作基寄存器。
编码意味着没有位移的ebp实际上意味着disp32没有基本reg,因此disp32实际上是基(意味着段寄存器DS,因为基不是EBP或ESP)。这是有或没有SIB字节的情况,所以[ebp + ebp*1]仍然必须使用disp8=0进行编码。其他寄存器没有这个问题,因此通常情况下,拆分将节省4个字节,而不是仅为EBP保存3个字节。(除了使用与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],它不保存任何字节,实际上也不保存影响LEA (但不是加载/存储)在沙洲桥-家庭CPU上的性能。。3组件lea eax, [symbol + ebp + ebp*1]比2组件lea eax, [symbol + ebp*2]慢:更高的延迟和1/时钟的吞吐量,而不是2。根据http://agner.org/optimize/的说法,在AMD /Ryzen上,这些都会同样慢,因为一个缩放的指数使得一个“慢速-LEA”,即使只有两个组件。
对于LEA或实际内存操作数,如果有任何旧的CPU使用非缩放索引和三分量寻址模式,则可以使用IDK。
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相同。
https://stackoverflow.com/questions/49721801
复制相似问题