因此,我正在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
CMP r/m32,
imm32
和CMP r/m64,
imm32
编码,它们允许比较32位立即数与32位和64位寄存器,但不比较CMP r/m64, imm64
。但是,有MOV r64, imm64
编码。或更妙的是,使用相对RIP的LEA:先使用default rel
,然后使用
lea r64, [name+8]
。这比mov r64, imm64
更有效,也更小。
由于nasm崩溃,MOV rcx, name+8
的失败只是nasm中的一个错误。请报告给nasm开发人员(确保您使用的是nasm最新版本;此外,请检查this patch不能解决问题)。无论如何,一种解决方法是在name
的末尾添加一个符号:
name:
resb 8
name_end:
现在只需使用MOV rcx, name_end
。这样做的好处是,当name
的大小更改时,不需要更新参考对象。或者,您可以使用其他汇编器,例如clang或GNU binutils汇编器。
讨论中指出Linux可以将符号地址用作32位立即数。这仅适用于在虚拟地址空间的低2GiB中与基地址链接的非PIE可执行文件。但是MacOS选择将图像基址放在4GiB之上,因此您不能将
mov r32, imm32
或cmp r64, sign_extended_imm32
与符号地址一起使用。
现在您知道数据的地址是相对于代码的地址而言的,请查看您是否可以理解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
,因为它太冗长且难以理解。)我的建议?除非您正在编写编译器(有时甚至是这样),否则请使用以下两种方法之一来编写汇编函数:
将所有必要的指针作为参数传递给该函数,或者,
将程序集编写为C函数内的内联程序集。
这通常也更易于移植,因为您将更少依赖ABI的某些细节。但是ABI仍然很重要!如果您不了解ABI并遵循它,那么您将导致很难发现的错误。例如,几年前,LibSDL汇编代码中存在一个错误,该错误导致libc的 memcpy
(也是汇编)在某些特定情况下复制错误的数据。