所以我正在我的mac上学习x86_64 nasm程序集以获得乐趣。在hello world和一些基本算术之后,我尝试从this site复制一个稍高级的hello world程序并将其修改为64位intel,但是我无法摆脱这一个错误消息:hello.s:53: error: Mach-O 64-bit format does not support 32-bit absolute addresses
。这是我用来汇编和链接的命令:nasm -f macho64 hello.s && ld -macosx_version_min 10.6 hello.o
。这是相关的一行:
cmp rsi, name+8
rsi是我在循环中用于索引的寄存器,name是为用户输入保留的四字,这是名称,到目前为止已经写入了。
这是代码的一部分(要查看其余部分,请单击链接并转到底部,唯一的区别是我使用64位寄存器):
loopAgain:
mov al, [rsi] ; al is a 1 byte register
cmp al, 0x0a ; if al holds an ascii newline...
je exitLoop ; then jump to label exitLoop
; If al does not hold an ascii newline...
mov rax, 0x2000004 ; System call write = 4
mov rdi, 1 ; Write to stdout = 1
mov rdx, 1 ; Size to write
syscall
inc rsi
cmp rsi, name+8 ; LINE THAT CAUSES ERROR
jl loopAgain
答案 0 :(得分:4)
cmp
指令不支持64位立即数操作数。因此,您不能在其操作数之一中放置64位立即数地址 - 将name+8
加载到寄存器中(使用普通的MOV
),然后与该寄存器进行比较。
您可以在Intel ISA manual中查看允许的指令编码(警告:巨大的PDF)。正如您在CMP条目中看到的那样,有CMP r/m32,
imm32
和CMP r/m64,
imm32
编码,可以比较32位立即数与32位和64位寄存器,但不能是{ {1}}。但是,有一个CMP r/m64, imm64
编码。
由于nasm崩溃,MOV r64, imm64
的失败只是鼻子里的一个错误。请将其报告给nasm开发人员(在确定您使用的是最新版本的nasm之后;另外,请检查this patch是否无法解决问题)。但无论如何,一种解决方法是在MOV rcx, name+8
的末尾添加一个符号:
name
现在只需使用name:
resb 8
name_end:
即可。这具有以下优点:当MOV rcx, name_end
的大小改变时不需要更新所指对象。或者你可以使用不同的汇编程序,例如clang或GNU binutils汇编程序。
答案 1 :(得分:3)
我相信你面临的问题很简单:Mach-O格式强制要求可重定位代码,这意味着数据必须不是通过绝对地址而是通过相对地址来访问。也就是说,汇编程序无法将name
解析为常量,因为它不是常量,数据可能位于任何地址。
既然您知道数据的地址是相对于代码的地址的,那么看看您是否能够理解GCC的输出。例如,
static unsigned global_var;
unsigned inc(void)
{
return ++global_var;
}
_inc:
mflr r0 ; Save old link register
bcl 20,31,"L00000000001$pb" ; Jump
"L00000000001$pb":
mflr r10 ; Get address of jump
mtlr r0 ; Restore old link register
addis r2,r10,ha16(_global_var-"L00000000001$pb") ; Add offset to address
lwz r3,lo16(_global_var-"L00000000001$pb")(r2) ; Load global_var
addi r3,r3,1 ; Increment global_var
stw r3,lo16(_global_var-"L00000000001$pb")(r2) ; Store global_var
blr ; Return
请注意,这是在PowerPC上,因为我不知道x86-64的Mach-O ABI。在PowerPC上,您执行跳转,保存程序计数器,然后对结果进行算术运算。我相信x86-64会发生完全不同的事情。
(注意:如果你看看GCC的汇编输出,试着用-O2
查看它。我不打扰-O0
,因为它太冗长,更难以理解。)
我的建议?除非您正在编写编译器(有时甚至是这样),否则请使用以下两种方法之一编写汇编函数:
这通常也更便于携带,因为您将更少依赖ABI的某些细节。但ABI仍然很重要!如果您不知道ABI并遵循它,那么您将导致相当难以检测的错误。例如,多年前LibSDL汇编代码中存在一个错误,导致libc的memcpy
(也是汇编)在某些特定情况下复制错误的数据。