nasm/ld "relocation truncated to fit: R_386_16"

nasm/ld "relocation truncated to fit: R_386_16"

程序集:

[BITS 16]

global _start

_start:
    mov ax, 0x07C0
    mov ds, ax

    mov si, hw
    call print_string
    jmp $

print_string:
    mov ah, 0x0E
.char:
    lodsb
    cmp al, 0
    je .exit
    int 0x10
    jmp .char
.exit: ret

times 0x100-($-$$) db 0

hw: db "Hello, World!", 0

times 510-($-$$) db 0
dw 0xAA55

将其组装为:

$ nasm file.asm -felf -o file.o

然后链接到:

$ ld -melf_i386 -o file.bin file.o --oformat binary

出现以下错误:

file.asm:(.text+0x6): relocation truncated to fit: R_386_16 against `.text'

稍微修改一下代码后,我发现将 mov si, hw 更改为 mov si, 0x100 效果很好。但是标签的意义何在?

我的猜测是 ld 不能生成 16 位二进制文​​件,因此它将 hw 替换为 32 位地址而不是 16 位地址。然后它抱怨,因为我的程序试图将 32 位值放入 16 位寄存器。

是否有一些参数我可以传递给 nasm/ld 以使其工作?


编辑:

elf 不支持 16 位,唯一的输出格式 nasm 支持,实际上它在 nasm -hf 中声明它支持 16 位是 .obj,但我找不到它的链接器.


NASM 手册:

The ELF32 specification doesn't provide relocations for 8- and 16-bit values, but the GNU ld linker adds these as an extension. NASM can generate GNU-compatible relocations, to allow 16-bit code to be linked as ELF using GNU ld. If NASM is used with the -w+gnu-elf-extensions option, a warning is issued when one of these relocations is generated.

添加-w+gnu-elf-extensions确实显示警告,但ld仍然给出相同的错误。

我在这里找到了解决方案:Looking for 16-bit x86 compiler

something I learned the hard way; -Ttext 0x0 is critical, otherwise the .text segment is pushed outside of 16bit addressing range (don't ask me why)

首先,我建议您考虑使用 i686 ELF Cross compiler 来避免一些以后在您开发内核时可能会咬到您的陷阱。


使用 NASM 和 -f bin 输出选项

没有什么可以阻止您使用 ELF 作为带有 NASM 的目标文件类型,但它通常更易于使用-f bin 选项生成完全解析的平面二进制文件,不需要修正。它可以用作引导扇区而无需任何链接步骤。不利的一面是所有代码都必须相同。外部 assembler 语句可以包含在 %include 指令中,类似于 Cinclude 指令。

为此,您必须将原点放在 assembler 文件中,以便 NASM 知道基准偏移量(原点) 需要生成绝对地址(用于标签等)。您将修改您的汇编代码并将其添加到顶部:

[ORG 0x0000]

这仅适用于使用 -f bin 输出选项时,此指令将抛出其他输出类型(如 -f elf)的错误。在这种情况下,我们使用 0x0000,因为您的代码假定的段是 0x07c0,它已移入 DS。 0x07c0:0x0000 映射到物理地址 (0x07c0<<4)+0x0000 = 0x07c00,这是我们的引导加载程序将加载到内存中的位置。

如果您不指定 [org 0x0000],那么 org = 0x0000 是使用 -f bin 输出选项时的默认值,因此实际上没有必要指定它。它只是通过显式使用它使 reader 更清晰。

为了 assemble 把它变成一个二进制文件,你可以这样做:

nasm file.asm -fbin -o file.bin

这将从 file.asm 输出一个名为 file.bin assembled 的平面二进制文件。不需要链接步骤。


使用 NASM 和 -f elf 输出选项

在您的示例中,您使用的是 ELF。这样做可能有几个原因。您生成的二进制文件可能是多个对象 (.o) 文件的组合,或者您可能希望生成调试符号以与 GDB 等调试器一起使用。无论您出于何种原因,都可以使用以下命令完成此操作:

nasm file.asm -felf -o file.o
ld -melf_i386 -Ttext 0x0 -o file.bin file.o --oformat binary

-Ttext 0x0 将是与您的代码匹配的原点。在这种情况下,0x0000 与 ORG 指令使用的值相同,如果您将 NASM-f bin 输出选项一起使用。如果您编写的代码假定偏移量为 0x7c00,代码如下:

xor ax, ax     ; AX = 0
mov ds, ax     ; DS = 0

然后 TEXT 段必须指定为:

ld -melf_i386 -Ttext 0x7c00 -o file.bin file.o --oformat binary

您的问题可能是:为什么我们需要明确地为TEXT段的基数设置一个值?原因是 LD 的默认值取决于您定位的 OS(通常针对您当前 运行 所在的平台)。如果您在 Linux,默认情况下 LD 将尝试为 Linux 创建输出。在 Linux 上,当指定 -m elf_i386 时,TEXT 段的默认开头通常是 0x08048000。这当然是一个32位的值。

任何需要绝对地址的地方,它都会尝试向其添加 0x08048000(或可能是其他一些大地址)。所以像这样的指令:

mov si, hw

会尝试将 hw 的地址移动到 16 位寄存器 SI 中。在创建平面二进制输出文件时,链接器会尝试将其解析为 0x08048000 + hw 的偏移量。因为您在一条仅采用 16 位值的指令中使用了 32 位值,所以您将得到 warning/error。 LD 会将 32 位值截断为 16 位,不幸的是,这可能会产生不正确的 16 位地址。