程序集重定位被截断以适合

Assembly relocation truncated to fit

您好,我一直在尝试用汇编语言编写一个简单的 hello world 程序并将其编译成一个 .o 文件,然后 link 它与标准 C 库一起创建一个 .exe,这样我就可以使用 gdb -tui 在我的系统上查看 'puts' 的反汇编。我将 Cygwin 与以下实用程序版本一起使用(通过 as --version && ld --version 获取这些版本)。我正在尝试在 Windows 8 x64.

上完成所有这些工作

作为版本 2.25

ld 版本 2.25

test.asm

在学习x86汇编的过程中,在网上看到了好几种汇编标准。我想我在这里写的是GAS。

.extern puts
_start:
    mov $msg, %rdi
    call puts
    xor %rax, %rax
    ret
msg: 
    .ascii "hello world"

assembler

我可以assemble上面的文件没问题,as实用程序没有给我警告或任何错误,这是我调用as实用程序的方式。

as test.asm -o test.o

linker

这是我遇到问题的地方,下面的命令是我认为我应该如何 link 带有标准 c 库的目标文件。

ld test.o -o test.exe -lc

此命令产生以下错误,我一直被这些错误难倒。我试图在其他帖子中和通过 google 找到答案,但也许我遗漏了什么。

test.o:fake:(.text+0x3): relocation truncated to fit: R_X86_64_32S against `.text`
/usr/lib/libc.a(t-d000957.o):fake:(.text+0x2): undefined reference to `__imp_puts`
/usr/lib/libc.a(t-d000957.o):fake:(.text+0x2): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__imp_puts`

带有一些注释的代码。您似乎通过在 RDI 中传递第一个参数来借鉴 Linux 64 位调用约定。在 windows 上,您在 RCX 中传递第一个参数。请参阅 Microsoft Windows 的 64-bit calling convention。您需要使用 movabsq 将标签的 64 位地址移动到 64 位寄存器中。您还应该确保正确对齐堆栈(16 字节边界);至少为 shadow space 分配 32 个字节;我添加了一个堆栈框架。

.extern puts
.global main
.text
main:
    push %rbp
    mov %rsp, %rbp         /* Setup stack frame */
    subq [=10=]x20, %rsp       /* Allocate space for 32 bytes shadow space 
                              no additional bytes to align stack are needed
                              since return address and rbp on stack maintain
                              16 byte alignment */ 
    movabsq $msg, %rcx     /* Loads a 64 bit register with a label's address
                              Windows 64-bit calling convention
                              passes param 1 in rcx */
    call puts
    xor %rax, %rax         /* Return value */
    mov %rbp, %rsp
    pop %rbp               /* Remove current stackframe */
    ret

.data
msg:
    .asciz "hello world"   /* Remember to zero terminate the string */

使用 .s 扩展名而不是 .asm 和 assemble 以及 link 重命名您的 assembler 文件:

gcc -o test.exe test.s

如果不将 .asm 重命名为 .s,您可能会发现 Cygwin 上的 GCC 会将您的 assembler 文件与 linker 脚本。


没有堆栈框架的版本

此代码与上面的代码类似,但删除了堆栈帧。 RBP/RSP 序言和结尾代码已在此版本中删除。我们仍然需要对齐堆栈。由于我们不再将 RBP 压入栈中,因此我们需要在栈上分配 32 字节的影子 space 和额外的 8 字节将栈放回 16 字节结盟。这种对齐和阴影 space 分配需要在我们的函数调用其他函数(如 Win32 API 和 C 库)之前完成。未能正确设置堆栈可能会导致对其他函数的调用神秘地出现段错误或出现意外行为。 64 位 Windows 调用约定在我之前在此答案开头提供的 link 中涵盖了这一点。

修改后的代码:

.extern puts
.global main
.text
main:
    subq [=12=]x28, %rsp       /* Allocate space for 32 bytes shadow space
                              Additional 8 bytes to align stack are needed
                              since 8 byte return address on stack no longer
                              makes the stack 16 byte aligned. 32+8=0x28 */
    movabsq $msg, %rcx     /* Loads a 64 bit register with a label's address
                              Windows 64-bit calling convention
                              passes param 1 in rcx */
    call puts
    xor %rax, %rax         /* Return value */
    addq [=12=]x28, %rsp       /* Restore stack pointer to state it was in prior
                              to executing this function so that `ret` won't fail */
    ret

.data
msg:
    .asciz "hello world"   /* Remember to zero terminate the string */

您所写的内容以及您为解决这些问题所做的尝试存在很多问题。首先,如 Jester 所说,如果您要使用 C 库函数,您的入口点应命名为 main。这使 C 运行时库有机会在调用 main 之前初始化自身。当您将入口点更改为 main 时,您也没有将其声明为全局。这意味着链接器无法找到它,这就是为什么您会收到关于找不到 WinMain 的错误。由于 Cygwin 的运行时库的编写方式最终会寻找一些不同的符号作为入口点,WinMain 就是其中之一,它最终抱怨找不到。但是,除非您正在编写 Win32 应用程序,否则您应该使用 main.

最后,relocation truncated to fit: R_X86_64_32S against '.text' 消息来自 mov $msg, %rdi 指令。 GNU 汇编程序将此指令解释为仅在左侧取一个 32 位立即操作数,但是 msg 是一个 64 位地址,因此它以 "truncated to fit" 结尾。解决方案是使用 movabs $msg,%rdi,它使用 64 位立即数,或者更好的是,lea msg(%rip),%rdi,它使用 RIP 相对寻址。