assembly Mach-O 64位格式不支持32位绝对地址,NASM访问阵列

ryoqjall  于 2022-11-13  发布在  Mac
关注(0)|答案(1)|浏览(171)

在我的Mac电脑上运行以下代码,使用命令:

nasm -f macho64 -o max.a maximum.asm

这是我试图在我的计算机上运行的代码,它在数组中查找最大的数字。

section .data

data_items:
    dd 3,67,34,222,45,75,54,34,44,33,22,11,66,0

    section .text

global _start

_start:
    mov edi, 0
    mov eax, [data_items + edi*4]
    mov ebx, eax

start_loop:
    cmp eax, 0
    je loop_exit
    inc edi
    mov eax, [data_items + edi*4]
    cmp eax, ebx
    jle start_loop

mov ebx, eax
jmp start_loop

loop_exit:

mov eax, 1
int 0x80

错误:

maximum.asm:14: error: Mach-O 64-bit format does not support 32-bit absolute addresses
maximum.asm:21: error: Mach-O 64-bit format does not support 32-bit absolute addresses
0s0u357o

0s0u357o1#

首先,注意64-bit absolute addressing (NASM 2.13.02+)RIP-relative in NASM 2.11.08的macho 64输出格式的NASM错误。不建议使用64位绝对寻址,因此即使对于有错误的NASM 2.13.02和更高版本,这个答案也应该有效。(这些错误不会导致此错误,它们会导致在运行时使用错误的地址。)
[data_items + edi*4]是一种32位寻址模式。即使[data_items + rdi*4]也只能使用32位绝对位移,因此它也不起作用。请注意,使用地址作为32位(符号扩展)立即数,如cmp rdi, data_items is also a problem:只有mov允许使用64位立即数。

OS X上的64位代码根本无法使用32位绝对寻址。可执行文件加载在4GiB以上的基址,因此标签地址不适合带有零或符号扩展的32位整数。RIP相对寻址是最佳/最有效的解决方案,无论您是否需要它与位置无关1。
**在NASM中,文件顶部的default rel**将使所有[]内存操作数优先选择RIP相对寻址。另请参阅NASM手册中的“3.3节有效地址”。

default rel                     ; near the top of file; affects all instructions

my_func:
    ...
    mov   ecx, [data_items]         ; uses the default: RIP-relative

    ;mov  ecx, [abs data_items]     ; override to absolute [disp32], unusuable
    mov   ecx, [rel data_items]     ; explicitly RIP-relative

但是RIP相对只有在不涉及其他寄存器时才可能,因此要索引静态数组,您需要首先获取寄存器中的地址。使用RIP相对lea rsi, [rel data_items]

lea   rsi, [data_items]    ; can be outside the loop
 ...
 mov   eax, [rsi + rdi*4]

或者,您可以在循环内使用add rsi, 4,并使用mov eax, [rsi]之类的更简单的寻址模式。
请注意,mov rsi, data_items可以用于将地址写入寄存器,但您不希望这样做,因为它效率较低。
从技术上讲,数组的+-2GiB范围内的任何地址都可以使用,因此如果您有多个数组,您可以相对于一个公共基址对其他数组进行寻址,只需使用一个指针绑定一个寄存器。例如lea rbx, [arr1]/.../mov eax, [rbx + rdi*4 + arr2-arr1]Relative Addressing errors - Mac 10.10提到Agner Fog的“优化汇编”指南中有一些数组寻址的示例,包括一个使用__mh_execute_header作为参考点的例子。(这个问题中的代码看起来像是在学习asm的同时,又一次尝试将PGU书中的这个32位Linux示例移植到64位OSX。)
注意,在Linux上,位置相关的可执行文件被加载到虚拟地址空间的低32位中,因此您将在Linux示例或http://gcc.godbolt.org/上的编译器输出中看到类似mov eax, [array + rdi*4]mov edi, symbol_name的代码。gcc -pie -fPIE将在Linux和is the default on many recent distros上生成位置无关的可执行文件,但Godbolt不会。
这对MacOS没有帮助,但我提到它是为了防止有人对他们在其他操作系统上看到的代码感到困惑,或者为什么AMD 64架构师费心在x86-64上允许[disp32]寻址模式。
顺便说一句,更喜欢在64位代码中使用64位寻址模式。例如,使用[rsi + rdi*4],而不是[esi + edi*4]。您通常不希望将指针截断为32位,这会花费额外的地址大小前缀来编码。
类似地,您应该使用syscall来进行64位系统调用,而不是int 0x80What are the calling conventions for UNIX & Linux system calls on i386 and x86-64的区别在于传入参数的寄存器。

**脚注1:**OS X支持64位绝对寻址,但仅支持位置相关的可执行文件(非PIE)。此相关问题x64 nasm: pushing memory addresses onto the stack & call function包含使用gcc main.o链接时的ld警告:

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

所以链接器检查是否使用了任何64位绝对重定位,如果是,则禁止创建位置无关的可执行文件。PIE可以从ASLR中获益,以获得安全性。我不知道是否允许跳转表或指针作为数据的其他情况(即,由动态链接器修复),或者如果您不创建位置相关的可执行文件,它们是否需要在运行时初始化。
mov r64, imm64lea r64, [RIP_rel32]大(10字节),但不快(7字节)。
因此,您 * 可以 * 使用mov rsi, qword data_items来代替与RIP相关的莱亚,后者运行速度差不多,并且占用代码缓存和uop缓存的空间更少。64位立即数在Sandybridge系列(http://agner.org/optimize/)上也有uop缓存提取损失:它们花费2个周期而不是1个周期来从微操作高速缓存行读取。
x86也有mov的形式,可以从64位绝对地址加载/存储数据,但仅适用于AL/AX/EAX/RAX。请参见http://felixcloutier.com/x86/MOV.html。您也不希望这样,因为它比mov eax, [rel foo]更大,速度也不快。
(相关:an AT&T syntax version of the same question

相关问题