x64 nasm:将内存地址推入堆栈&通话功能

时间:2012-10-26 17:58:04

标签: macos gcc assembly x86-64 nasm

我对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

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

3 个答案:

答案 0 :(得分:8)

64位OS X ABI符合System V ABI - AMD64 Architecture Processor Supplement的要求。其代码模型与小位置无关代码模型(PIC)非常相似,差异解释为here。在该代码模型中,使用RIP相对寻址直接访问所有本地和小数据。正如Z boson的评论中所指出的,64位Mach-O可执行文件的图像库超出了虚拟地址空间的前4 GiB,因此push msg不仅是放置{的地址的无效方式。堆栈上的{1}},但它也是不可能的,因为msg不支持64位立即值。代码应该类似于:

PUSH

但在特定情况下,根本不需要在堆栈上推送值。 64位调用约定强制要求第6个整数/指针参数在寄存器中传递 ; this is what you *would* do for later args on the stack lea rax, [rel msg] ; RIP-relative addressing push rax RDIRSIRDX,完全按照该顺序RCXR8。前8个浮点或矢量参数进入R9XMM0,...,XMM1。仅在使用所有可用寄存器或存在不能适合任何这些寄存器的参数(例如,80位XMM7值)之后,才使用该堆栈。使用long doubleMOV变体)而非QWORD执行64位立即推送。简单的返回值将传回PUSH寄存器。调用者还必须为被调用者提供堆栈空间以保存一些寄存器。

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

以下是RIPgcc转换为OS X上的程序集的方式:

printf("This is a test\n");

(这是AT& T样式汇编,源是左,目的地是正确的,寄存器名称以 xorl %eax, %eax # (1) leaq L_.str(%rip), %rdi # (2) callq _printf # (3) L_.str: .asciz "This is a test\n" 为前缀,数据宽度被编码为指令名称的后缀)

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

我不是NASM大师,但我认为以下代码应该这样做:

RIP

答案 1 :(得分:4)

还没有答案解释为什么NA​​SM报道

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

NASM不会这样做的原因在[{3}}手册的 3.3寻址模式部分的 32位绝对寻址64位模式下的部分中进行了解释

  

32位绝对地址不能在Mac OS X中使用,其中地址大于2 ^ 32 by   默认值。

这在Linux或Windows上不是问题。事实上,我已经在Agner Fog's Optimizing Assembly展示了这项工作。那个hello world代码使用32位绝对寻址与elf64并运行良好。

@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在同一本手册中写道:

  

绝对没有理由将简单的内存操作数用于绝对地址。 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。在示例3.11c中,您可以执行

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

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

lea rbx, [rel A]; rel tells nasm to do [rip + A]
mov eax, [rbx + 4*rcx] ; A[i]

答案 2 :(得分:2)

根据x86 64位指令集http://download.intel.com/products/processor/manual/325383.pdf

的文档

PUSH只接受8,16和32位立即数值(尽管允许使用64位寄存器和寄存器寻址存储器块。)

PUSH msg

msg是64位的立即地址,不会像你发现的那样编译。


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

是否期望堆栈上的参数或使用快速调用约定,其中寄存器中的参数?由于x86-64使更多通用寄存器可用,因此更频繁地使用快速调用约定。