使用 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 的好处,并在下结论之前仔细检查了代码!
这个问题和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 的好处,并在下结论之前仔细检查了代码!