在从头开始编程,在第 3 章中我读到了
内存地址引用的一般形式是这样的:
ADDRESS_OR_OFFSET(%BASE_OR_OFFSET, %INDEX, MULTIPLIER)
所有字段都是可选的。要计算地址,只需执行以下计算:
FINAL ADDRESS = ADDRESS_OR_OFFSET + %BASE_OR_OFFSET + MULTIPLIER * %INDEX
和ADDRESS_OR_OFFSET
必须都是常量,而另外两个必须是寄存器。如果遗漏其中一个,则只需在方程中用零代替即可。MULTIPLIER
现在,我假设用零替换是一个拼写错误,因为如果
MULTIPLIER
的默认值为0,那么%INDEX
的值将是无关紧要的,因为无论如何产品总是为零(确实) 。我猜其他 3 个默认为 0?
尽管如此,最让我困惑的是,从上面的描述中我了解到括号和逗号的功能是确定我们编写的内容的哪些部分映射到寻址的 4 个“操作数”。
但是,在接下来的章节中我读到了
例如,以下代码将堆栈顶部的所有内容移动到
中:%eax
movl (%esp), %eax
如果我们只是这样做
movl %esp, %eax
只会保存指向堆栈顶部的指针,而不是顶部的值。%eax
但我不明白为什么。我的意思是,
鉴于上面的
FINAL ADDRESS
表达式,我会这么说
%esp
放在括号里,它将起到%BASE_OR_OFFSET
的作用,ADDRESS_OR_OFFSET
和%INDEX
默认为0,MULTIPLIER
默认为1,%esp
放在括号中,它将起到ADDRESS_OR_OFFSET
的作用,%BASE_OR_OFFSET
和%INDEX
默认为0,MULTIPLIER
默认为1,总和仍然是一样的。
此外,
%esp
如何保持不变?
%esp
的内容?
%esp
是常数,因为它是物理固定寄存器的名称,那么在这种情况下什么是非常量?正确,默认乘数是
1
。
movl %esp, %eax
根本不使用内存寻址模式。 它是寄存器直接操作数,因此在语法上与 mov symbol_name, %eax
(从绝对地址加载)不同。
有一个寄存器,但它不在
()
内部,因此 disp(base,idx,scale)
语法不适用。
在机器代码中,ModRM 字节的 2 位“模式”字段使用
0b11
来编码它是寄存器操作数而不是内存。 (其他 3 种编码选择无位移内存、disp8 与 disp32:https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM_and_SIB_bytes。另请参阅rbp 不允许作为 SIB 基础? 对于有趣的特殊情况,允许 disp32
没有寄存器,并使 SIB 字节可选,以保存简单寻址模式的机器代码大小。)对于 ModR/M.mode = 11
,该字段只是一个简单的寄存器号。类似地,在汇编语言中,当您使用裸寄存器名称时,您只是直接获取寄存器操作数,而不是将其用作访问内存的地址。
还有相关:
1
而不是 0
。 shift count 在机器代码中是 0
,但是源代码级 asm 语法使用 2 的幂乘数(在我见过的所有 x86 汇编语法中,包括 AT&T、所有类型的 Intel 和 Go 的汇编)方言。当然可以发明一种在 asm 源代码中使用移位计数的新语法。)