我有一个适用于Mac OS X的程序集hello world程序,如下所示:
global _main
section .text
_main:
mov rax, 0x2000004
mov rdi, 1
lea rsi, [rel msg]
mov rdx, msg.len
syscall
mov rax, 0x2000001
mov rdi, 0
syscall
section .data
msg: db "Hello, World!", 10
.len: equ $ - msg
我想知道线lea rsi, [rel msg]
。 NASM为什么强迫我这样做?据我了解,msg
只是指向可执行文件中某些数据的指针,而mov rsi, msg
会将该地址放入rsi
。但是,如果我用lea rsi, [rel msg]
替换行,NASM会抛出此错误(注意:我使用命令nasm -f macho64 hello.asm
):
hello.asm:9: fatal: No section for index 2 offset 0 found
为什么会这样? lea
mov
无法做到的{{1}}有什么特别之处?我怎么知道何时使用每一个?
答案 0 :(得分:5)
lea
mov
无法做到的mov reg,imm
有什么特别之处?
mov eax,someVar
将立即常量加载到其目标操作数中。立即常数直接编码在操作码中,例如,如果B8 EF CD AB 00
的地址为someVar
,0x00ABCDEF
将被编码为imm
。即要编写msg
地址为msg
的此类指令,您需要知道mov reg,[expression]
的确切地址。在与位置无关的代码中,您不是先验的。
expression
加载位于expression
描述的地址的值。 x86指令的复杂编码方案允许具有相当复杂的reg1+reg2*s+displ
:通常它是s
,其中reg1
可以是0,1,2,4,reg2
和{ {1}}可以是通用寄存器或零,displ
是立即置换。在64位模式下,expression
可以有另外一种形式:RIP+displ
,即地址是相对于下一条指令计算的。
lea reg,[expression]
使用所有这种复杂的计算地址的方式将地址本身加载到reg
(与mov
不同,后者取消引用计算的地址)。因此,在编译时不可用的信息,即将在RIP
中的绝对地址,可以在指令中编码而不知道其值。 nasm表达式lea rsi,[rel msg]
被翻译成类似
lea rsi,[rip+(msg-nextInsn)]
nextInsn:
使用相对地址msg-nextInsn
而不是msg
的绝对地址,从而允许汇编器不知道实际地址但仍然编码指令。
答案 1 :(得分:2)
mov不能做什么特别的?
LEA r, [rel symbol]
可以在运行时访问RIP。 mov r, imm
无法做到。立即数被编码到指令的二进制表示中,这意味着如果代码+数据被映射到链接时不知道的地址,它就不会工作。 (即它的位置相关代码。)
这就是为什么RIP相对寻址对于PIC(与位置无关的代码)非常好的原因:不需要通过全局偏移表的间接级别来访问在同一目标文件中定义的静态数据,您可以使用RIP相对地址。
MacOS X甚至在可执行文件(不仅仅是共享库)中也需要PIC,因此在所有情况下都需要避免使用绝对寻址。