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

时间:2011-07-05 02:19:53

标签: macos assembly x86-64 nasm mach-o

所以我正在我的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

2 个答案:

答案 0 :(得分:4)

cmp指令不支持64位立即数操作数。因此,您不能在其操作数之一中放置64位立即数地址 - 将name+8加载到寄存器中(使用普通的MOV),然后与该寄存器进行比较。

您可以在Intel ISA manual中查看允许的指令编码(警告:巨大的PDF)。正如您在CMP条目中看到的那样,有CMP r/m32, imm32CMP 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,因为它太冗长,更难以理解。)

我的建议?除非您正在编写编译器(有时甚至是这样),否则请使用以下两种方法之一编写汇编函数:

  • 将所有必要的指针作为参数传递给函数,或者
  • 将程序集作为内联汇编写入C函数。

这通常也更便于携带,因为您将更少依赖ABI的某些细节。但ABI仍然很重要!如果您不知道ABI并遵循它,那么您将导致相当难以检测的错误。例如,多年前LibSDL汇编代码中存在一个错误,导致libc的memcpy(也是汇编)在某些特定情况下复制错误的数据。