关于 [base + index*scale + disp] 和 AT&T disp(base, index, scale) 的几个问题
A couple of questions about [base + index*scale + disp] and AT&T disp(base, index, scale)
Intel 和 AT&T 语法中内存寻址的一般形式如下:
[base + index*scale + disp] # Intel, including GAS .intel_syntax noprefix
disp(base, index, scale) # AT&T
我的问题如下:
base
和index
可以是任何寄存器吗?
scale
可以取什么值,是 1、2、4 还是 8(默认为 1)?
index
和disp
可以互换吗(唯一的区别是index
是一个寄存器,而disp
是一个立即数)?
这在英特尔的手册中有描述:
3.7.5 Specifying an Offset
The offset part of a memory address can be specified directly as a static value (called a displacement) or through
an address computation made up of one or more of the following components:
- Displacement — An 8-, 16-, or 32-bit value.
- Base — The value in a general-purpose register.
- Index — The value in a general-purpose register. [can't be ESP/RSP]
- Scale factor — A value of 2, 4, or 8 that is multiplied by the index value.
添加这些组件产生的偏移称为有效地址。
比例因子被编码为 2 位移位计数 (0,1,2,3),比例因子为 1、2、4 或 8。是的,*1
( shift count = 0) 是默认值,如果你写 (%edi, %edx)
;这相当于 (%edi, %edx, 1)
在 AT&T 语法中,它是 disp(base, index, scale)
- 常量在括号之外。一些 Intel 语法 assemblers 也允许像 1234[ebx]
这样的语法,其他的则不允许。但是 AT&T 的语法是死板的;寻址模式的每一个组成部分都只能进入其适当的位置。例如:
movzwl foo-0x10(,%edx,2), %eax
从地址 foo-0x10 + edx*2
将零扩展 16 位(“字”)加载到 EAX 中。 EDX 是索引寄存器,比例因子为 2。没有基址寄存器。 foo
和-0x10
都是位移的一部分,都是link-时间常数。 foo
是一个符号地址,linker 将填写并从中减去 0x10(因为 -0x10
assemble-时间偏移)。
如果可以选择,请仅使用基数而不是标度为 1 的索引。索引需要 SIB 字节进行编码,从而使指令更长。这就是为什么编译器选择像 8(%ebp)
这样的寻址模式来访问堆栈内存,而不是 8(,%ebp)
.
另请参阅 以了解有关何时可以使用基数、and/or 索引、and/or 位移的更多信息。
16 位位移只能在 16 位寻址模式下编码,它使用 different format that can't include a scale factor, and has a very limited selection of which registers 可以是基数或索引。
所以像 1234(%edx)
这样的模式必须在 32 位机器代码中将 1234 编码为 32 位 disp32
。
从 -128 到 +127 的字节偏移可以使用短格式的 8 位编码。您的 assembler 会为您解决这个问题,使用最短的有效位移编码。
对于 64 位寻址模式,所有这些在 64 位模式下都是相同的,disp32 也像 disp8 一样被符号扩展为 64 位。
Intel 和 AT&T 语法中内存寻址的一般形式如下:
[base + index*scale + disp] # Intel, including GAS .intel_syntax noprefix
disp(base, index, scale) # AT&T
我的问题如下:
base
和index
可以是任何寄存器吗?scale
可以取什么值,是 1、2、4 还是 8(默认为 1)?index
和disp
可以互换吗(唯一的区别是index
是一个寄存器,而disp
是一个立即数)?
这在英特尔的手册中有描述:
3.7.5 Specifying an Offset
The offset part of a memory address can be specified directly as a static value (called a displacement) or through an address computation made up of one or more of the following components:
- Displacement — An 8-, 16-, or 32-bit value.
- Base — The value in a general-purpose register.
- Index — The value in a general-purpose register. [can't be ESP/RSP]
- Scale factor — A value of 2, 4, or 8 that is multiplied by the index value.
添加这些组件产生的偏移称为有效地址。
比例因子被编码为 2 位移位计数 (0,1,2,3),比例因子为 1、2、4 或 8。是的,*1
( shift count = 0) 是默认值,如果你写 (%edi, %edx)
;这相当于 (%edi, %edx, 1)
在 AT&T 语法中,它是 disp(base, index, scale)
- 常量在括号之外。一些 Intel 语法 assemblers 也允许像 1234[ebx]
这样的语法,其他的则不允许。但是 AT&T 的语法是死板的;寻址模式的每一个组成部分都只能进入其适当的位置。例如:
movzwl foo-0x10(,%edx,2), %eax
从地址 foo-0x10 + edx*2
将零扩展 16 位(“字”)加载到 EAX 中。 EDX 是索引寄存器,比例因子为 2。没有基址寄存器。 foo
和-0x10
都是位移的一部分,都是link-时间常数。 foo
是一个符号地址,linker 将填写并从中减去 0x10(因为 -0x10
assemble-时间偏移)。
如果可以选择,请仅使用基数而不是标度为 1 的索引。索引需要 SIB 字节进行编码,从而使指令更长。这就是为什么编译器选择像 8(%ebp)
这样的寻址模式来访问堆栈内存,而不是 8(,%ebp)
.
另请参阅
16 位位移只能在 16 位寻址模式下编码,它使用 different format that can't include a scale factor, and has a very limited selection of which registers 可以是基数或索引。
所以像 1234(%edx)
这样的模式必须在 32 位机器代码中将 1234 编码为 32 位 disp32
。
从 -128 到 +127 的字节偏移可以使用短格式的 8 位编码。您的 assembler 会为您解决这个问题,使用最短的有效位移编码。
对于 64 位寻址模式,所有这些在 64 位模式下都是相同的,disp32 也像 disp8 一样被符号扩展为 64 位。