与 link.exe 链接时未重定位的地址

Unrelocated address when linking with link.exe

问题

当我使用 as (binutils) 和 link 使用 link.exe (Visual Studio 2015) 编译我的汇编代码时,程序因地址未重定位而崩溃。

当 link 使用 gcc (gcc hello-64-gas.obj -o hello-64-gas.exe) 时,程序 运行 可以正常运行而不会崩溃。 我是否正确地假设 as 生成的目标文件应该是独立于编译器的,因为 abi 兼容性问题在汇编代码编写者手中? 由于我是初学者,因此欢迎对我的 mistakes/incorrect 假设进行任何解释。

平台

例子

以下代码link正确:

# 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,如果 .data 部分位于零位置,则 18h 将是正确的。查看重定位 table 和 objdump -dr hello-64-gas.obj 产量:

...
19:   49 c7 c1 18 00 00 00    mov    [=12=]x18,%r9
                      1c: R_X86_64_32S        .data
...

变体(解决方法?)

movabs 替换 mov 似乎可行:

# 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 使用 link.exe 编辑时,这会以某种方式 运行 正确。

Microsoft 的链接器不支持 GNU 汇编器用于引用 myDouble 以及 fmtmsg 的重定位。此重定位被 GNU 实用程序称为 R_X86_64_32S,值为 0x11,未记录在 Microsoft's PECOFF specification 中。正如在您的目标文件上使用 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

作为解决方法,您可以使用以下任一方法:

  • 具有 RIP 相对寻址的 LEA 指令,它生成 R_X86_64_PC32/REL32 重定位
  • 正如您自己发现的那样,一条 MOVABS 指令会生成 R_X86_64_64/ADDR64 重定位
  • 生成 R_X86_64_32/ADDR32 重定位的 32 位 MOV 指令

为了这些将被写成:

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

这些指令连同 mov r9, offset myDouble 是四个不同的指令,具有不同的编码和细微不同的语义,每个指令都需要不同类型的重定位。

LEA 指令将 myDouble 编码为相对于 RIP 的 32 位有符号偏移量。这是在这里使用的更可取的指令,因为它只需要 4 个字节来编码地址,并且它允许可执行文件加载到 64 位地址 space 中的任何位置。唯一的限制是可执行文件的大小需要小于 2G,但无论如何这是 x64 PECOFF 可执行文件的基本限制。

MOVABS 将myDouble 编码为64 位绝对地址。虽然理论上这允许 myDouble 位于 64 位地址 space 中的任何位置,即使距离指令超过 2G,它也需要 8 个字节的编码 space 并且不会实际上可以在 Windows.

下为您提供任何东西

32位MOV指令将myDouble编码为一个无符号的32位绝对地址。它的缺点是需要将可执行文件加载到地址 space 的前 4G 中的某处。因此,您需要在 Microsoft 链接器中使用 /LARGEADDRESSAWARE:NO 标志,否则会出现错误。

您使用的 64 位 MOV 指令将 myDouble 编码为 32 位有符号绝对地址。这也限制了可以加载可执行文件的位置,并且需要一种 Microsoft 的 PECOFF 格式未记录为具有且不受 Microsoft 链接器支持的重定位类型。