x64 nasm:将内存地址推送到堆栈和调用函数

qwe*_*rtz 5 macos assembly gcc x86-64 nasm

我对Mac上的x64-assembly很新,所以我很困惑在64位中移植一些32位代码.
程序应该只通过printfC标准库中的函数打印出一条消息.
我已经开始使用这段代码:

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
Run Code Online (Sandbox Code Playgroud)

用这种方式用nasm编译它:

$ nasm -f macho64 main.s
Run Code Online (Sandbox Code Playgroud)

返回以下错误:

main.s:12: error: Mach-O 64-bit format does not support 32-bit absolute addresses
Run Code Online (Sandbox Code Playgroud)

我试图修复问题字节,将代码更改为:

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
Run Code Online (Sandbox Code Playgroud)

它使用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
Run Code Online (Sandbox Code Playgroud)

因为它是一个警告而不是错误我已经执行了a.out文件:

$ ./a.out
Segmentation fault: 11
Run Code Online (Sandbox Code Playgroud)

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

Hri*_*iev 8

64位OS X ABI完全符合System V ABI - AMD64架构处理器补充资料.它的代码模型非常类似于小位置无关代码模型(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
Run Code Online (Sandbox Code Playgroud)

但是在那种特殊情况下,根本不需要在堆栈上推送值.64位调用约定强制要求的拳头6整数/指针参数在寄存器传递RDI,RSI,RDX,RCX,R8,和R9,恰好以该顺序.前8个浮点或载体参数进入XMM0,XMM1,... XMM7.仅在使用所有可用寄存器或存在不能适合任何这些寄存器的参数(例如,80位long double值)之后,才使用堆栈.使用MOV(QWORD变体)执行64位立即推送而不是PUSH.简单的返回值将传回RAX寄存器中.调用者还必须为被调用者提供堆栈空间以保存一些寄存器.

printf是一个特殊的函数,因为它需要可变数量的参数.调用此类函数时,AL应将其设置为浮点参数的数量,并在向量寄存器中传递.另请注意,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"
Run Code Online (Sandbox Code Playgroud)

(这是AT&T样式程序集,源是左,目标是正确的,寄存器名称是前缀%,数据宽度编码为指令名称的后缀)

(1)零投入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
Run Code Online (Sandbox Code Playgroud)


Z b*_*son 5

还没有答案解释为什么 NASM 报告

Mach-O 64-bit format does not support 32-bit absolute addresses
Run Code Online (Sandbox Code Playgroud)

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

32 位绝对地址不能在 Mac OS X 中使用,默认情况下地址在 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

mov eax, [A+rcx*4]
Run Code Online (Sandbox Code Playgroud)

其中 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]
Run Code Online (Sandbox Code Playgroud)

使用 Mach O 中的 extern 引用__mh_execute_header来获取图像库的地方。在示例 3.11c 中,您使用 rip 相对寻址并像这样加载地址

lea rbx, [rel A]; rel tells nasm to do [rip + A]
mov eax, [rbx + 4*rcx] ; A[i]
Run Code Online (Sandbox Code Playgroud)