与 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 假设进行任何解释。
平台
- Windows10、64 位
- 链接器:Visual Studio 2015 使用本机命令工具命令提示符 (x64)
- 编译器:
as
来自 MinGW-w64
例子
以下代码不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
以及 fmt
和 msg
的重定位。此重定位被 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 链接器支持的重定位类型。
问题
当我使用 as
(binutils) 和 link 使用 link.exe (Visual Studio 2015) 编译我的汇编代码时,程序因地址未重定位而崩溃。
当 link 使用 gcc (gcc hello-64-gas.obj -o hello-64-gas.exe
) 时,程序 运行 可以正常运行而不会崩溃。
我是否正确地假设 as
生成的目标文件应该是独立于编译器的,因为 abi 兼容性问题在汇编代码编写者手中?
由于我是初学者,因此欢迎对我的 mistakes/incorrect 假设进行任何解释。
平台
- Windows10、64 位
- 链接器:Visual Studio 2015 使用本机命令工具命令提示符 (x64)
- 编译器:
as
来自 MinGW-w64
例子
以下代码不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
以及 fmt
和 msg
的重定位。此重定位被 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 链接器支持的重定位类型。