使用 gcc/ld 为 x86 编译的 32/16 位引导加载程序的相对调用地址不正确

Incorrect Relative call address for 32/16bit bootloader compiled using gcc/ld for x86

这个问题和Incorrect relative address using GNU LD w/ 16-bit x86类似,但是我建了一个cross-compiler无法解决。

场景是,我有一个以 16 位启动的第二阶段引导加载程序,然后将其自身提升到 32 位。结果,我将汇编和 C 代码与 32 位和 16 位代码混合在一起。

我已经包含了一个汇编文件,它定义了一个我将从 C 调用的全局变量,主要目的是返回到 REAL 模式以根据需要从受保护模式 C 环境执行 BIOS 中断。到目前为止,该函数除了被调用、压入和弹出一些寄存器以及 return:

之外没有做任何事情
[bits 32]
BIOS_Interrupt:
PUSHF
...
ret
global BIOS_Interrupt

这包含在我的主要 bootloader.asm 文件中,该文件由阶段 1 mbr 加载。

在C中,我定义了:

extern void BIOS_Interrupt(uint32_t intno, uint32_t ax, uint32_t bx, uint32_t cx, uint32_t dx, uint32_t es, uint32_t ds, uint32_t di, uint32_t si);

在 header 和

BIOS_Interrupt(0x15,0,0,0,0,0,0,0,0);

在代码中,只是为了测试调用

我可以在生成的反汇编链接二进制文件中看到调用总是在 RAM 中设置太低的 2 个字节:

00000132  0100              add [bx+si],ax
00000134  009CFA9D          add [si-0x6206],bl
00000138  6650              push eax
0000013A  6653              push ebx
...
00001625  6A00              push byte +0x0
00001627  6A00              push byte +0x0
00001629  6A00              push byte +0x0
0000162B  6A00              push byte +0x0
0000162D  6A00              push byte +0x0
0000162F  6A00              push byte +0x0
00001631  6A00              push byte +0x0
00001633  6A00              push byte +0x0
00001635  6A15              push byte +0x15
00001637  E8F9EA            call 0x133

135 处的指令应该是到达的第一条指令 (0x9C = PUSHF),但调用在 133 处的内存中少了 2 个字节,导致运行时错误。

我注意到通过使用 NASM .align 关键字,生成的额外 NOP 确实可以补偿不正确的相对地址。

这是链接过程的问题吗?我有 LD 运行 -melf_i386、NASM 和 -f elf 以及 GCC 和 -m32 -ffreestanding -0s -fno-pie -nostdlib

编辑:为@MichaelPetch 添加的图像。代码由 MBR 在 0x9000 加载。有趣的是,调用显示正确的相对跳转,到 0x135,但在 0x135 执行反汇编看起来像 0x133 (0x00, 0x00) 的代码。

Bochs 即将 call BIOS_Interrupt

Bochs 在 call start

编辑 2:调用后刷新 memdump 后对图像 2 的更正

memdump and dissasembly after calling BIOS_Interrupt (call 0x135)

再次感谢@MichaelPetch 给我的一些建议。

我认为链接器没有问题,反汇编是在“欺骗”我,因为 16 位和 32 位代码的组合导致代码不准确。

最终,这是由于覆盖了先前操作的内存值。在 BIOS_Interrupt 标签之前的代码中,我定义了一个双字 dd IDT_REAL,用于存储用于实模式处理的 IDT。但是,我没有意识到(或忘记)SIDT/LIDT 指令需要 6 个字节的数据,所以当我调用 SIDT 时,它覆盖了 RAM 中标签位置的前 2 个字节,导致 运行时间错误。将变量的大小从 dword 增加到 qword 后,我可以 运行 就好了 w/o 错误。

linker/compiler 的建议似乎是在转移注意力,我因为 objdump 的礼貌而堕落了。但是,我至少从中学到了 Bochs 的好处,并在下结论之前仔细检查了代码!