汇编器错误:Mach-O 64位不支持绝对的32位地址

问题描述 投票:5回答:2

因此,我正在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
macos assembly x86-64 nasm mach-o
2个回答
4
投票
您会看到Intel ISA manual中允许使用哪些指令编码(警告:巨大的PDF)。在CMP条目上可以看到,有CMP r/m32, imm32CMP 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, imm32cmp r64, sign_extended_imm32与符号地址一起使用。


3
投票
not常量,数据可以位于任何地址。

现在您知道数据的地址是相对于代码的地址而言的,请查看您是否可以理解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(也是汇编)在某些特定情况下复制错误的数据。
  • © www.soinside.com 2019 - 2024. All rights reserved.