【问题标题】:Assembler Error: Mach-O 64 bit does not support absolute 32 bit addresses汇编程序错误:Mach-O 64 位不支持绝对 32 位地址
【发布时间】:2011-09-28 11:32:46
【问题描述】:

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

【问题讨论】:

  • 一个建议:尝试用 C 编写相同的代码,使用 gcc -S 编译它,然后查看程序集以了解 GCC 是如何处理它的。
  • @bdonlan: 在.bss 部分,我有name: resb 8
  • @Nemo:我试过了,但我无法理解它。

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


【解决方案1】:

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

您可以在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 汇编器。


cmets 中的讨论指出 Linux 可以将符号地址用作 32 位立即数。这仅适用于与低 2GiB 虚拟地址空间中的基地址链接的非 PIE 可执行文件。但是 MacOS 选择将图像基地址放在 4GiB 以上,因此您不能将 mov r32, imm32cmp r64, sign_extended_imm32 与符号地址一起使用。

【讨论】:

  • 我试着做mov rcx, name+8 然后cmp rsi, rcx 但是当我用nasm 组装时它只是说Segmentation fault。如果我删除+8,它组装得很好。为什么要这样做?
  • 如果我在 mov rcx, name 后面跟着 add rcx, 8cmp rsi, rcx 它工作正常。但是为什么我不能只做mov rcx, name+8 呢?
  • 反汇编使用name+8的结果会怎样?
  • @bdonlan:嗯,这很奇怪,因为我可以为 x86-64 Linux/ELF 组装程序而不会出错。似乎 nasm 确实在使用 32 位地址。
  • @Mk12,如果 nasm 本身因分段错误而崩溃,则反汇编毫无意义,无论如何文件可能已损坏,因为 nasm 在编写过程中途死亡。但为了完整起见,objdump -d 应该对未损坏的文件执行此操作。您也可以通过 -x 转储标头,包括重定位表。
【解决方案2】:

我相信您面临的问题很简单: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(也是汇编)在某些非常特定的情况下复制错误的数据。

【讨论】:

  • x86-64 Mach-O 不必与位置无关。如果它使用 ASLR,它会应用文本重定位。唯一不支持的是 32 位绝对寻址,因为加载可执行文件的映像库高于 2^32。 (当然,相对 RIP 在 x86-64 上通常比 64 位绝对更有效,所以除了像静态指针数组之类的东西,例如跳转表之外,请使用它。)参见 developer.apple.com/library/content/documentation/…
猜你喜欢
  • 1970-01-01
  • 2014-12-11
  • 2018-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多