6

我对 Mac 上的 x64-assembly 还很陌生,所以我对在 64 位中移植一些 32 位代码感到困惑。程序应该通过C 标准库中
的函数简单地打印出一条消息。 我从这段代码开始:printf

section .data
    msg db 'This is a test', 10, 0    ; something stupid here

section .text
    global _main
    extern _printf

_main:
    push    rbp
    mov     rbp, rsp       

    push    msg
    call    _printf

    mov     rsp, rbp
    pop     rbp
    ret

以这种方式用 nasm 编译它:

$ nasm -f macho64 main.s

返回以下错误:

main.s:12: error: Mach-O 64-bit format does not support 32-bit absolute addresses

我试图解决这个问题,将代码更改为:

section .data
    msg db 'This is a test', 10, 0    ; something stupid here

section .text
    global _main
    extern _printf

_main:
    push    rbp
    mov     rbp, rsp       

    mov     rax, msg    ; shouldn't rax now contain the address of msg?
    push    rax         ; push the address
    call    _printf

    mov     rsp, rbp
    pop     rbp
    ret

它使用上面的命令编译得很好,nasm但现在在将目标文件编译gcc为实际程序时出现警告:

$ gcc main.o
ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not
allowed in code signed PIE, but used in _main from main.o. To fix this warning,
don't compile with -mdynamic-no-pic or link with -Wl,-no_pie

由于这是警告而不是错误,因此我执行了该a.out文件:

$ ./a.out
Segmentation fault: 11

希望有人知道我做错了什么。

4

3 回答 3

9

64 位 OS X ABI 完全符合System V ABI-AMD64 Architecture Processor Supplement它的代码模型与小位置无关代码模型 (PIC)非常相似,但此处解释了不同之处。在该代码模型中,所有本地和小数据都使用 RIP 相对寻址直接访问。正如 Z boson 在评论中指出的那样,64 位 Mach-O 可执行文件的映像库超出了虚拟地址空间的前 4 GiB,因此push msg不仅将地址放入msg堆栈是一种无效的方式,而且它也是不可能的,因为PUSH不支持 64 位立即数。代码应该看起来类似于:

   ; this is what you *would* do for later args on the stack
lea   rax, [rel msg]  ; RIP-relative addressing
push  rax

但在这种特殊情况下,根本不需要将值压入堆栈。64 位调用约定要求前 6 个整数/指针参数 完全按照该顺序在寄存器RDIRSIRDXRCXR8和中传递。R9前 8 个浮点或向量参数进入XMM0, XMM1, ..., XMM7。只有在使用了所有可用的寄存器或存在不适合任何这些寄存器的参数(例如 80 位long double值)后,才会使用堆栈。64 位立即推送是使用MOV(QWORD变体) 而不是PUSH. 简单的返回值在RAX登记。调用者还必须为被调用者提供堆栈空间以保存一些寄存器。

printf是一个特殊的函数,因为它接受可变数量的参数。调用此类函数时AL(RAX 的低字节)应设置为浮点参数的数量,并在向量寄存器中传递。另请注意,RIP对于位于代码 2 GiB 内的数据,首选相对寻址。

以下是如何在 OS X 上gcc转换为程序集:printf("This is a test\n");

    xorl    %eax, %eax             # (1)
    leaq    L_.str(%rip), %rdi     # (2)
    callq   _printf                # (3)

L_.str:
    .asciz   "This is a test\n"

(这是 AT&T 风格的程序集,左边是源,右边是目的地,寄存器名称以 为前缀%,数据宽度编码为指令名称的后缀)

由于没有传递浮点参数,因此将(1)置零(通过将整个 RAX 归零以避免部分寄存器延迟)。AL(2)字符串的地址被加载RDI。请注意,该值实际上是与 的当前值的偏移量RIP。由于汇编器不知道这个值是什么,它会在目标文件中放置一个重定位请求。然后链接器会看到重定位并在链接时放置正确的值。

我不是 NASM 大师,但我认为下面的代码应该做到这一点:

default rel             ; make [rel msg] the default for [msg]
section .data
    msg:  db 'This is a test', 10, 0    ; something stupid here

section .text
    global _main
    extern _printf

_main:
    push    rbp                 ; re-aligns the stack by 16 before call
    mov     rbp, rsp       

    xor     eax, eax            ; al = 0 FP args in XMM regs
    lea     rdi, [rel msg]
    call    _printf

    mov     rsp, rbp
    pop     rbp
    ret
于 2012-10-26T18:51:48.097 回答
5

尚无答案解释为什么 NASM 报告

Mach-O 64-bit format does not support 32-bit absolute addresses

NASM 不这样做的原因在Agner Fog 的 Optimizing Assembly手册中的第3.3 节寻址模式下的标题为64 位模式下的 32 位绝对寻址小节下进行了解释,他写道

Mac OS X 中不能使用 32 位绝对地址,默认情况下地址大于 2^32。

这在 Linux 或 Windows 上不是问题。事实上,我已经在static-linkage-with-glibc-without-calling-main展示了这个作品。hello world 代码使用带有 elf64 的 32 位绝对寻址并且运行良好。

@HristoIliev 建议使用 rip 相对寻址,但没有解释 Linux 中的 32 位绝对寻址也可以。实际上,如果您更改lea rdi, [rel msg]lea rdi, [msg]它,它会组装并运行良好,nasm -efl64但会失败nasm -macho64

像这样:

section .data
    msg db 'This is a test', 10, 0    ; something stupid here

section .text
    global _main
    extern _printf

_main:
    push    rbp
    mov     rbp, rsp       

    xor     al, al
    lea     rdi, [msg]
    call    _printf

    mov     rsp, rbp
    pop     rbp
    ret

您可以检查这是一个绝对的 32 位地址,而不是相对于objdump. 但是,重要的是要指出,首选方法仍然是翻录相对寻址。Agner 在同一手册中写道:

绝对没有理由对简单的内存操作数使用绝对地址。相对地址使指令更短,它们消除了在加载时重新定位的需要,并且它们在所有系统中都可以安全使用。

那么什么时候在 64 位模式下使用 32 位绝对地址呢?静态数组是一个不错的选择。请参阅以下小节以 64 位模式寻址静态数组。简单的情况是例如:

mov eax, [A+rcx*4]

其中 A 是静态数组的绝对 32 位地址。这在 Linux 上工作得很好,但在 Mac OS X 上你又不能这样做,因为默认情况下图像基数大于 2^32。要在 Mac OS X 上执行此操作,请参阅 Agner 手册中的示例 3.11c 和 3.11d。在示例 3.11c 中,您可以执行

mov eax, [(imagerel A) + rbx + rcx*4]

在哪里使用 Mach O 的外部参考__mh_execute_header来获取图像库。在示例 3.11c 中,您使用 rip 相对寻址并像这样加载地址

lea rbx, [rel A]; rel tells nasm to do [rip + A]
mov eax, [rbx + 4*rcx] ; A[i]
于 2014-10-16T14:56:04.727 回答
2

根据 x86 64bit 指令集的文档http://download.intel.com/products/processor/manual/325383.pdf

PUSH 仅接受 8、16 和 32 位立即数(但允许使用 64 位寄存器和寄存器寻址的内存块)。

PUSH msg

其中 msg 是 64 位立即地址不会像您发现的那样编译。


_printf 在您的 64 位库中定义的调用约定是什么?

它是期望堆栈上的参数还是使用寄存器中的参数的快速调用约定?因为 x86-64 使更多通用寄存器可用,所以更频繁地使用快速调用约定。

于 2012-10-26T18:11:11.410 回答