Macos x64 nasm:将内存地址推送到堆栈&;调用函数
我对Mac上的x64程序集非常陌生,所以我对在64位中移植一些32位代码感到困惑。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,
程序只需通过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]