我对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
希望有人知道我做错了什么。
答案 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
,RDI
,RSI
,RDX
,完全按照该顺序RCX
和R8
。前8个浮点或矢量参数进入R9
,XMM0
,...,XMM1
。仅在使用所有可用寄存器或存在不能适合任何这些寄存器的参数(例如,80位XMM7
值)之后,才使用该堆栈。使用long double
(MOV
变体)而非QWORD
执行64位立即推送。简单的返回值将传回PUSH
寄存器。调用者还必须为被调用者提供堆栈空间以保存一些寄存器。
RAX
是一个特殊函数,因为它需要可变数量的参数。调用此类函数时printf
(RAX的低字节)应设置为浮点参数的数量,并在向量寄存器中传递。另请注意,AL
- 对于位于代码2 GiB内的数据,首选相对寻址。
以下是RIP
将gcc
转换为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)
还没有答案解释为什么NASM报道
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使更多通用寄存器可用,因此更频繁地使用快速调用约定。