Неразмещенный адрес при связывании с link.exe

Проблема

Когда я компилирую свой ассемблерный код с as (binutils) и связываю с помощью link.exe (Visual Studio 2015), программа вылетает из-за нерасположенного адреса.

Однако при компоновке с gcc (gcc hello-64-gas.obj -o hello-64-gas.exe) программа работает правильно, без сбоев. Правильно ли я предполагаю, что объектный файл, сгенерированный as, должен быть независимым от компилятора, поскольку проблемы совместимости с abi находятся в руках автора кода сборки? Поскольку я новичок, приветствуются любые объяснения моих ошибок / неверных предположений.

Платформа

  • Windows 10, 64 бит
  • Компоновщик: Visual Studio 2015 с использованием командной строки собственных командных инструментов (x64)
  • Компилятор: as из MinGW-w64

Пример

Следующий код неверно связывает правильно:

# hello-64-gas.asm    print a string using printf
# Assemble:   as hello-64-gas.asm -o hello-64-gas.obj --64
# Link:       link -subsystem:CONSOLE hello-64-gas.obj -out:hello-64-gas.exe libcmt.lib libvcruntime.lib libucrt.lib legacy_stdio_definitions.lib
.intel_syntax noprefix

.global main

# Declare needed C  functions
.extern printf

.section .data
msg:       .asciz "Hello world"
fmt:       .asciz "%s(%d; %f)\n"
myDouble:   .double 2.33, -1.0

.text
main:
    sub rsp, 8*5
    mov rcx, offset flat: fmt
    mov rdx, offset flat: msg
    mov r8, 0xFF
    mov r9, offset flat: myDouble
    mov r9, [r9]
    movq xmm4, r9
    call printf
    add rsp, 8*5

    mov rax, 0
    ret

При отладке кажется, что mov r9, offset flat: myDouble не перемещается: mov r9,18h, где 18h было бы правильным, если бы раздел .data находился в нулевой позиции. Если посмотреть на таблицу перемещений с objdump -dr hello-64-gas.obj доходностью:

...
19:   49 c7 c1 18 00 00 00    mov    $0x18,%r9
                      1c: R_X86_64_32S        .data
...

Вариант (обходной путь?)

Замена mov на movabs вроде работает:

# hello-64-gas.asm    print a string using printf
# Assemble:       as hello-64-gas.asm -o hello-64-gas.obj --64
# Link:           link -subsystem:CONSOLE hello-64-gas.obj -out:hello-64-gas.exe libcmt.lib libvcruntime.lib libucrt.lib legacy_stdio_definitions.lib
.intel_syntax noprefix

.global main

# Declare needed C  functions
.extern printf

.section .data
msg:       .asciz "Hello world"
fmt:       .asciz "%s(%d; %f)\n"
myDouble:   .double 2.33, -1.0

.text
main:
    sub rsp, 8*5
    movabs rcx, offset flat: fmt
    movabs rdx, offset flat: msg
    mov r8, 0xFF
    movabs r9, offset flat: myDouble
    mov r9, [r9]
    movq xmm4, r9
    call printf
    add rsp, 8*5

    mov rax, 0
    ret

Это каким-то образом работает правильно при связывании с использованием link.exe.


person Araeos    schedule 24.03.2016    source источник
comment
За самые глупые вопросы, не связанные с исследованиями, проголосовали, но если кто-то действительно попытается отладить или иным образом найти причину проблемы, даст правильные детали в хорошо отформатированном вопросе с правильными тегами и даже обходным путем, тогда он будет отвергнут? Я больше не понимаю этот мир.   -  person Jester    schedule 24.03.2016
comment
@Jester, я только что проголосовал за ;-) но да, должен признать, что это гораздо лучший вопрос, чем то, что мы обычно видим!   -  person Michael Petch    schedule 24.03.2016


Ответы (1)


Перемещение, которое ассемблер GNU использует для ваших ссылок на myDouble вместе с fmt и msg, не поддерживается компоновщиком Microsoft. Это перемещение, называемое R_X86_64_32S утилитами GNU и имеющее значение 0x11, не задокументировано в спецификация Microsoft PECOFF. Как видно из использования Microsoft DUMPBIN в вашем объектном файле, компоновщик Microsoft, похоже, использует перемещения с этим значением для некоторых других недокументированных целей:

RELOCATIONS #1
                                                Symbol    Symbol
 Offset    Type              Applied To         Index     Name
 --------  ----------------  -----------------  --------  ------
 00000007  EHANDLER                                    7  .data
 0000000E  EHANDLER                                    7  .data
 0000001C  EHANDLER                                    7  .data
 00000029  REL32                      00000000         C  printf

В качестве обходного пути вы можете использовать:

  • инструкция LEA с относительной адресацией RIP, которая генерирует перемещение R_X86_64_PC32 / REL32
  • как вы сами выяснили, инструкция MOVABS, генерирующая перемещение R_X86_64_64 / ADDR64
  • 32-битная инструкция MOV, которая генерирует перемещение R_X86_64_32 / ADDR32

Чтобы они были записаны как:

lea r9, [rip + myDouble]
movabs r9, offset myDouble
mov r9d, offset myDouble

Это, наряду с mov r9, offset myDouble, четыре разные инструкции с разными кодировками и слегка различающейся семантикой, каждая из которых требует разного типа перемещения.

Инструкция LEA кодирует myDouble как 32-битное смещение со знаком относительно RIP. Это предпочтительная инструкция для использования здесь, поскольку для кодирования адреса требуется всего 4 байта, и она позволяет загружать исполняемый файл в любое место в 64-битном адресном пространстве. Единственное ограничение заключается в том, что размер исполняемого файла должен быть меньше 2 ГБ, но в любом случае это фундаментальное ограничение для исполняемых файлов x64 PECOFF.

MOVABS кодирует myDouble как 64-битный абсолютный адрес. Хотя теоретически это позволяет размещать myDouble в любом месте 64-битного адресного пространства, даже более чем на 2 ГБ от инструкции, это занимает 8 байтов пространства кодирования и на самом деле ничего не дает вам под Windows.

32-битная инструкция MOV кодирует myDouble как 32-битный абсолютный адрес без знака. Его недостаток состоит в том, что исполняемый файл должен быть загружен где-то в первом 4G адресного пространства. Из-за этого вам нужно использовать флаг /LARGEADDRESSAWARE:NO с компоновщиком Microsoft, иначе вы получите ошибку.

64-битная инструкция MOV, которую вы используете, кодирует myDouble как 32-битный абсолютный адрес со знаком. Это также ограничивает то, где может быть загружен исполняемый файл, и требует типа перемещения, которое формат Microsoft PECOFF не задокументировано и не поддерживается компоновщиком Microsoft.

person Ross Ridge    schedule 24.03.2016
comment
Так это ошибка в GNU или они не должны быть совместимы? - person Harry Johnston; 25.03.2016
comment
@HarryJohnston Я бы назвал это ошибкой в ​​GNU binutils. Хотя они не должны быть совместимы с этим конкретным типом перемещения, это расширение GNU, проблема в том, что когда они создавали свои собственные типы перемещения, они выбирали значения, которые перекрывали значения, уже определенные Microsoft. Это один из последних официально определенных типов переселения. Если бы они выбрали гораздо более высокие значения типа перемещения, они 16-битные, то компоновщик Microsoft пожаловался бы на неизвестное перемещение. - person Ross Ridge; 25.03.2016