6

所以我在我的 Mac 上学习 x86_64 nasm 汇编是为了好玩。在 hello world 和一些基本算术之后,我尝试从这个站点复制一个稍微高级一点的 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
4

2 回答 2

4

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

您可以查看英特尔 ISA 手册中允许的指令编码(警告:巨大的 PDF)。正如您在 CMP 的条目中看到的那样,有CMP r/m32, imm32CMP r/m64, imm32编码,它允许将 32 位立即数与 32 位和 64 位寄存器进行比较,但不能将CMP r/m64, imm64. 但是,有一个MOV r64, imm64编码。

或者更好的是,使用 RIP 相关的 LEA:使用default relthen lea r64, [name+8]。这比 更有效且更小mov r64, imm64


由于 nasm 正在崩溃,因此失败MOV rcx, name+8只是 nasm 中的一个错误。请向 nasm 开发人员报告(在确保您使用的是最新版本的 nasm 之后;另外,请检查此补丁是否无法解决问题)。但是,无论如何,一种解决方法是在结尾添加一个符号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与符号地址一起使用。

于 2011-07-05T02:38:21.557 回答
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(也是汇编)在某些非常特定的情况下复制错误的数据。

于 2011-07-05T03:00:35.947 回答