Ошибка ассемблера: 64-битный Mach-O не поддерживает абсолютные 32-битные адреса

Так что я изучаю сборку носа x86_64 на моем Mac для удовольствия. После hello world и некоторой базовой арифметики я попытался скопировать немного более продвинутую программу hello world с этого сайта и изменить ее для 64-битной Intel, но я не могу избавиться от этого одного сообщения об ошибке: hello.s:53: error: Mach-O 64-bit format does not support 32-bit absolute addresses, Вот команда, которую я использую для сборки и ссылки: nasm -f macho64 hello.s && ld -macosx_version_min 10.6 hello.o, И вот соответствующая строка:

cmp rsi, name+8

rsi - это регистр, который я использую для своего индекса в цикле, а name - это четверное слово, зарезервированное для пользовательского ввода, которое является именем, которое к этому моменту уже было записано.

Вот часть кода (чтобы увидеть остальные, нажмите на ссылку и перейдите вниз, единственное отличие состоит в том, что я использую 64-битные регистры):

loopAgain:
mov al, [rsi]           ; al is a 1 byte register
cmp al, 0x0a            ; if al holds an ascii newline...
je exitLoop             ; then jump to label exitLoop

; If al does not hold an ascii newline...
mov rax, 0x2000004      ; System call write = 4
mov rdi, 1              ; Write to stdout = 1
mov rdx, 1              ; Size to write
syscall

inc rsi

cmp rsi, name+8         ; LINE THAT CAUSES ERROR
jl loopAgain

2 ответа

Решение

cmp Инструкция не поддерживает 64-битный непосредственный операнд. Таким образом, вы не можете поместить 64-битную непосредственную ссылку на адрес в один из ее операндов - загрузить name+8 в реестр (используя обычный MOV), затем сравните с этим регистром.

Вы можете увидеть, какие кодировки команд разрешены в руководстве Intel ISA (предупреждение: огромный PDF). Как вы можете видеть на записи для CMP, есть CMP r/m32, imm32 а также CMP r/m64, imm32 кодировки, которые позволяют сравнивать 32-битные непосредственные с 32-битными и 64-битными регистрами, но не CMP r/m64, imm64, Однако есть MOV r64, imm64 кодирование.

Так как насм разрушается, провал MOV rcx, name+8 просто ошибка в насм. Пожалуйста, сообщите об этом разработчикам nasm (убедившись, что вы используете последнюю версию nasm; также убедитесь, что этот патч не решает проблему). В любом случае, однако, одним из обходных путей будет добавление символа в конце name:

name:
    resb 8
name_end:

Теперь просто используйте MOV rcx, name_end, Это имеет то преимущество, что нет необходимости обновлять ссылки, когда размер name изменения. В качестве альтернативы вы можете использовать другой ассемблер, например, ассемблеры clang или GNU binutils.

Я считаю, что проблема, с которой вы сталкиваетесь, проста: формат Mach-O требует перемещения кода, что означает, что доступ к данным должен осуществляться не по абсолютному адресу, а по относительному адресу. То есть ассемблер не может разрешить name к константе, потому что это не константа, данные могут быть по любому адресу.

Теперь, когда вы знаете, что адрес данных относится к адресу вашего кода, посмотрите, сможете ли вы понять вывод из GCC. Например,

static unsigned global_var;
unsigned inc(void)
{
    return ++global_var;
}

_inc:
    mflr r0                                           ; Save old link register
    bcl 20,31,"L00000000001$pb"                       ; Jump
"L00000000001$pb":
    mflr r10                                          ; Get address of jump
    mtlr r0                                           ; Restore old link register
    addis r2,r10,ha16(_global_var-"L00000000001$pb")  ; Add offset to address
    lwz r3,lo16(_global_var-"L00000000001$pb")(r2)    ; Load global_var
    addi r3,r3,1                                      ; Increment global_var
    stw r3,lo16(_global_var-"L00000000001$pb")(r2)    ; Store global_var
    blr                                               ; Return

Обратите внимание, что это на PowerPC, потому что я не знаю ABI Mach-O для x86-64. На PowerPC вы делаете прыжок, сохраняя счетчик программ, а затем выполняете арифметику с результатом. Я считаю, что что-то совершенно другое происходит на x86-64.

(Примечание: если вы посмотрите на вывод сборки GCC, попробуйте посмотреть на него с помощью -O2, Я не смотрю на -O0 потому что это слишком многословно и труднее понять.)

Моя рекомендация? Если вы не пишете компилятор (а иногда даже тогда), пишите свои ассемблерные функции одним из двух способов:

  • Передайте все необходимые указатели в качестве аргументов функции, или,
  • Запишите сборку как встроенную сборку внутри функции C.

Как правило, это также будет более переносимым, так как вы будете меньше полагаться на некоторые детали ABI. Но ABI все еще важен! Если вы не знаете ABI и не следуете ему, вы вызовете ошибки, которые довольно сложно обнаружить. Например, несколько лет назад в сборочном коде LibSDL была ошибка, из-за которой libc memcpy (также сборка), чтобы скопировать неправильные данные при некоторых очень специфических обстоятельствах.

Другие вопросы по тегам