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

Ara*_*eos 3 windows assembly linker x86-64 mingw-w64

问题

当我使用as(binutils)编译我的汇编代码并使用 link.exe (Visual Studio 2015) 进行链接时,由于地址未重定位,程序崩溃。

当与 gcc ( gcc hello-64-gas.obj -o hello-64-gas.exe)链接时,程序可以正常运行而不会崩溃。我是否正确地假设生成的目标文件as应该与编译器无关,因为 abi 兼容性问题掌握在汇编代码编写者手中?由于我是初学者,因此对我的错误/不正确假设的任何解释表示赞赏。

平台

  • Windows 10,64 位
  • 链接器:使用本机命令工具命令提示符 (x64) 的 Visual Studio 2015
  • 编译器: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
Run Code Online (Sandbox Code Playgroud)

调试时,它似乎mov r9, offset flat: myDouble没有重新定位:mov r9,18h18h如果.data位置为零的部分在哪里是正确的。查看带有objdump -dr hello-64-gas.obj收益率的重定位表:

...
19:   49 c7 c1 18 00 00 00    mov    $0x18,%r9
                      1c: R_X86_64_32S        .data
...
Run Code Online (Sandbox Code Playgroud)

变化(解决方法?)

替换movmovabs似乎有效:

# 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
Run Code Online (Sandbox Code Playgroud)

当使用link.exe.

Ros*_*dge 5

即GNU汇编器正在使用您的引用搬迁myDouble,随着fmtmsg,不是由微软的连接支持。这种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
Run Code Online (Sandbox Code Playgroud)

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

  • 带有 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
Run Code Online (Sandbox Code Playgroud)

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

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

MOVABS 编码myDouble为 64 位绝对地址。虽然理论上这允许myDouble位于 64 位地址空间中的任何位置,甚至距离指令超过 2G,但它需要 8 个字节的编码空间并且实际上在 Windows 下并没有给你任何东西。

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

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