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

Macos x64 nasm:将内存地址推送到堆栈&;调用函数,macos,gcc,assembly,x86-64,nasm,Macos,Gcc,Assembly,X86 64,Nasm,我对Mac上的x64程序集非常陌生,所以我对在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,

我对Mac上的x64程序集非常陌生,所以我对在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

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

根据x86 64位指令集的文档

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

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


64位库中定义的调用约定是什么


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

64位OS X ABI大体上符合。其代码模型与小位置独立代码模型(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个整数/指针参数以该顺序准确地传递到寄存器
RDI
RSI
RDX
RCX
R8
R9
。前8个浮点或向量参数进入
XMM0
XMM1
,…,
XMM7
。只有在使用了所有可用寄存器或存在无法放入任何寄存器的参数(例如,80位
长双精度值)后,才使用堆栈。使用
MOV
QWORD
变体)执行64位即时推送,而不是
PUSH
。简单返回值在
RAX
寄存器中传回。调用者还必须为被调用者提供堆栈空间以保存一些寄存器

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

以下是
gcc
如何翻译
printf(“这是一个测试”)编码到OS X上的程序集:

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

L_.str:
    .asciz   "This is a test\n"
(这是AT&T样式的程序集,源在左侧,目标在右侧,寄存器名称的前缀为
%
,数据宽度编码为指令名称的后缀)

(1)
zero被放入
AL
(通过将整个RAX归零,避免部分寄存器延迟),因为没有传递浮点参数。在
(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

尚未有答案解释NASM报告的原因

Mach-O 64-bit format does not support 32-bit absolute addresses
NASM不这样做的原因在手册的第3.3节“寻址模式”中解释,在他编写的标题为“64位模式下的32位绝对寻址”的小节中

32位绝对地址不能在Mac OS X中使用,因为在Mac OS X中,地址超过2^32个字节 默认

这在Linux或Windows上不是问题。事实上,我已经在上一次展示了它的工作原理。hello world代码使用32位绝对地址和elf64,运行良好

@HristoIliev建议使用rip相对寻址,但没有解释在Linux中使用32位绝对寻址也可以。事实上,如果您将
leardi,[rel msg]
更改为
leardi,[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
相对的rip地址。然而,需要指出的是,首选的方法仍然是rip相对寻址。阿格纳在同一本手册中写道:

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

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

mov eax, [A+rcx*4]
其中A是静态数组的绝对32位地址。这在Linux上可以很好地工作,但在Mac OS X上不能这样做,因为默认情况下映像库大于2^32。要在Mac OS X上实现这一点,请参阅Agner手册中的示例3.11c和3.11d。例如
mov eax, [A+rcx*4]
mov eax, [(imagerel A) + rbx + rcx*4]
lea rbx, [rel A]; rel tells nasm to do [rip + A]
mov eax, [rbx + 4*rcx] ; A[i]