一般问题
我一直在开发一个简单的引导加载程序,并在某些环境中偶然发现了一个问题,这些指令不起作用:
mov si, call_tbl ; SI=Call table pointer
call [call_tbl] ; Call print_char using near indirect absolute call
; via memory operand
call [ds:call_tbl] ; Call print_char using near indirect absolute call
; via memory operand w/segment override
call near [si] ; Call print_char using near indirect absolute call
; via register
其中每一个碰巧都涉及到绝对内存偏移的间接近调用。我发现如果我使用类似的JMP表会出现问题。相对的调用和跳转似乎没有受到影响。像这样的代码有效:
call print_char
我已经通过讨论编写引导加载程序的注意事项的海报接受了 Stackoverflow 上提出的建议。特别是,我看到了这个Stackoverflow的答案与General Bootloader Tips。第一个提示是:
- 当 BIOS 跳转到您的代码时,您不能依赖具有有效或预期值的CS、DS、ES、SS、SP寄存器。当您的引导加载程序启动时,它们应该被适当地设置。您只能保证您的引导加载程序将从物理地址 0x07c00 加载和运行,并且引导驱动器编号已加载到DL寄存器中。
接受所有建议,我不依赖CS,我设置了一个堆栈,并将DS设置为适合我使用的ORG(原点偏移)。我创建了一个 Minimal Complete Verifiable 示例来演示该问题。我使用NASM构建了它,但这似乎不是NASM特有的问题。
最小的例子
测试代码如下:
[ORG 0x7c00]
[Bits 16]
section .text
main:
xor ax, ax
mov ds, ax ; DS=0x0000 since OFFSET=0x7c00
cli ; Turn off interrupts for potentially buggy 8088
mov ss, ax
mov sp, 0x7c00 ; SS:SP = Stack just below 0x7c00
sti ; Turn interrupts back on
mov si, call_tbl ; SI=Call table pointer
mov al, [char_arr] ; First char to print 'B' (beginning)
call print_char ; Call print_char directly (relative jump)
mov al, [char_arr+1] ; Character to print 'M' (middle)
call [call_tbl] ; Call print_char using near indirect absolute call
; via memory operand
call [ds:call_tbl] ; Call print_char using near indirect absolute call
; via memory operand w/segment override
call near [si] ; Call print_char using near indirect absolute call
; via register
mov al, [char_arr+2] ; Third char to print 'E' (end)
call print_char ; Call print_char directly (relative jump)
end:
cli
.endloop:
hlt ; Halt processor
jmp .endloop
print_char:
mov ah, 0x0e ; Write CHAR/Attrib as TTY
mov bx, 0x00 ; Page 0
int 0x10
retn
; Near call address table with one entry
call_tbl: dw print_char
; Simple array of characters
char_arr: db 'BME'
; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55
出于测试目的,我构建了一个ISO映像和一个 1.44MB 软盘映像。我使用的是 Debian Jessie 环境,但大多数 Linux 发行版都类似:
nasm -f bin boot.asm -o boot.bin
dd if=/dev/zero of=floppy.img bs=1024 count=1440
dd if=boot.bin of=floppy.img conv=notrunc
mkdir iso
cp floppy.img iso/
genisoimage -quiet -V 'MYBOOT' -input-charset iso8859-1 -o myos.iso -b floppy.img -hide floppy.img iso
我最终得到了一个名为 的软盘映像和floppy.img
一个名为myos.iso
.
期望与实际结果
在大多数情况下,此代码有效,但在许多环境中则无效。当它工作时,它只是在显示器上打印:
BMMME
B
我使用具有相对偏移量的典型CALL打印出来,它似乎工作正常。在某些环境中,当我运行代码时,我会得到:
乙
然后它似乎只是停止做任何事情。它似乎B
正确打印出来,但随后发生了一些意想不到的事情。
似乎工作的环境:
- QEMU用软盘和 ISO 启动
- VirtualBox用软盘和 ISO 启动
- VMWare 9用软盘和 ISO 启动
- DosBox用软盘启动
- 使用软盘映像在 Debian Jessie 上正式打包的Bochs (2.6)
- Debian Jessie 上使用软盘映像和ISO的 Bochs 2.6.6(从源代码控制构建)映像
- 90 年代中期使用软盘和ISO的 AST Premmia SMP P90 系统
无法按预期工作的环境:
- 使用ISO映像在 Debian Jessie 上正式打包的Bochs (2.6)
- 基于 486DX 的系统,带有 90 年代初的 AMI BIOS,使用软盘映像。CD 无法在此系统上启动,因此无法测试该 CD。
我发现有趣的是Bochs (2.6 版)在使用ISO的 Debian Jessie 上无法正常工作。当我从具有相同版本的软盘启动时,它按预期工作。
在所有情况下,ISO和软盘映像似乎都已加载并开始运行,因为在所有情况下,它至少能够B
在显示器上打印出来。
我的问题
- 当它失败时,为什么它只打印出 a
B
而仅此而已? - 为什么有些环境有效而有些环境失败?
- 这是我的代码或硬件/BIOS 中的错误吗?
- 如何修复它以便我仍然可以使用近乎间接的跳转和调用表来获得绝对内存偏移量?我知道我可以完全避免这些说明,这似乎解决了我的问题,但我希望能够了解如何以及是否可以在引导加载程序中正确使用它们。