BIOS 中断 13 读取扇区不工作

BIOS Interrupt 13 read sector not working

我已经开始汇编操作系统开发,目前我正在设置一个两阶段引导加载程序。我已经设置了第一阶段,但是当我重置磁盘并读取第二个扇区时,屏幕上出现一堆乱码。当我尝试调用本应加载到 0x1000 的代码时,没有任何反应(据我所知)。我试图找到解决方案,但我找到的 none 似乎有效,但我 运行 运气不好。这是我的完整代码:

    BITS 16

start:
    mov ax, 07C0h
    add ax, 288
    mov ss, ax
    mov sp, 4096
    
    mov ax, 07C0h
    mov ds, ax
    
    call reset
    call read_sector
    
    jmp sector
    
    text_string db "hello, world!", 0
    sector equ 1000h

reset:
    mov ah, 00h
    mov dl, 00h
    int 13h

read_sector:
    ; 1000h (used to be 07E0h, but changed as I realized that I probably needed to accomodate more space for the bootloader)
    mov ax, sector
    mov es, ax
    mov ah, 02h
    mov al, 01h
    mov ch, 00h
    mov cl, 02h
    mov dh, 00h
    mov dl, 00h
    int 13h
    jc reset

printf:
    mov ah, 0Eh
    lodsb
    cmp al, 0
    je done
    int 10h
    jmp printf

jump_print:
    call printf
    ret

done:
    ret
    
times 510-($-$$) db 0
dw 0xAA55
; sector 2

message db 'now there is a different message!', 0
mov si, message
call printf

jmp $

times 1024-($-$$) db 0

任何帮助都会很棒。作为参考,我在 windows 上,我正在使用 nasm 进行编译。我正在通过 VirtualBox 进行测试(我知道这可能不是最好的工具,但它的工作原理与我目前所见的一样)。

一些问题:

a) BIOS“读取磁盘sector/s”函数需要一个段(在ES中)和一个偏移量(在BX中)。您没有提供偏移量(未将 BX 设置为任何值),因此没有合理的方法来猜测 BIOS 可能加载扇区的位置(它可能位于 [ 请求的 64 KiB 段内的任何位置) =10=]).

b) 无论 sector 是如何创建的,它都不能是段(对于 mov ax, sectormov es, ax),也不能是当前代码中的“偏移量”段”(对于 jmp sector),所以其中一个一定是错误的。对于“sector = 0x1000”,它看起来应该是“当前代码段中的偏移量”(并且读取该扇区的代码应将 ES 设置为 0x07C0 以匹配假定的当前代码段并执行 mov bx,sectorsector 视为请求段中的偏移量)。

c) 我强烈建议尽可能将分段设置为零。这有助于避免混淆和错误(尤其是对于初学者),并使以后更容易切换 to/from 保护模式(或长模式)。为此,除了更改段寄存器加载(例如 mov ax, 07C0hmov es,ax 会变成 mov ax, 0, mov es,ax)。这将包括堆栈(我建议在其中使用“SS:SP = 0:0x7C00”)。不要忘记堆栈段限制通常实际上并不限制任何东西——例如pushcall 当堆栈“已满”时只会导致 SP 溢出并从 0x0000 回绕到 0xFFFE。

d) 在第一次尝试读取 sector/s 之前不需要重置磁盘系统(因为你知道 BIOS 成功读取了你的引导加载程序并且磁盘系统正在工作美好的)。它只能用作从磁盘错误中恢复的尝试。请注意,(对于软盘)重置磁盘系统通常意味着重新校准驱动器,这涉及将磁头送回“磁道 0”。

e) 当从磁盘读取错误时,有时它们是暂时的(例如由于软盘驱动器电机速度变化),有时它们不是(例如“错误参数”、“软盘弹出”等) .建议重试几次(并在两次尝试之间偶尔重置磁盘系统)以解决临时问题(尤其是软盘)。永远来回撞击磁盘磁头(使用“reset/recalibrate、seek/read、reset/recalibrate、seek/read、...)是处理持续性问题的极其糟糕的方法。您需要一个计数器来跟踪您重试了多少次,以便您可以在有限的尝试次数后停止并显示错误消息。不要忘记 BIOS return 是一个错误代码,并使用该错误代码来确定要显示的错误消息可以帮助您找到错误,还可以帮助普通用户了解在出现问题时该怎么做(例如,如果它是 hardware/dodgy 软盘故障或软件错误)。

f) 你有控制流问题。具体来说,您 call reset 但该代码没有 ret 并落入 read_sector 代码;所以 call reset 不会 return 除非 BIOS 成功读取该扇区,并且在该扇区已经成功读取后执行 call read_sector 没有意义。

g) 如果 read_sector 代码成功,它将跳转到打印字符串的代码。您打印字符串的代码(printf 例程)将从内存中包含在 DS:SI 中的地址处获取字符,但 SI 从未设置为第一个扇区中的任何内容(在其之前已执行),因此它可能只会打印出 random/undefined 混乱(最有可能的情况是未知地址处的字节恰好为零,因此不会打印任何内容)。还;使用 lodsb 读取一个字节后,SI 将根据“方向标志”的设置方式递增或递减。您的代码没有将方向标志设置为任何东西(例如,任何地方都没有 cld 指令);这意味着您的代码可能会向后打印随机字符串(“!dlroW olleH”)的可能性极小。我不知道您是否打算在读取扇区的代码后添加 ret,或者您是否打算将 DS:SI 设置为“加载第二个扇区!”的地址。字符串(我假设是前者,因为第一个扇区中没有字符串)。

h) 第二个扇区的内容以字符串 ("now there is a different message!") 开头。如果没有其他错误,call sector(在第 1 个扇区)将导致执行此字符串而不是有效代码(可能导致某种壮观的崩溃)。您想要将字符串移动到其他地方(在第二个扇区的代码之后)或直接跳转到代码(例如第一个扇区中的 jmp sector2_code 和 [=45= 之前的 sector2_code: 标签] 指令)。

i) 您用来读取扇区的 BIOS 功能大多不适用于硬盘(现代硬盘实在是太大了——有一个“扩展磁盘读取”功能想改用)。对于软盘,您不能使用“扩展磁盘读取”功能(并且必须使用您正在使用的 BIOS 功能)。对于其他情况(从 USB 闪存引导,从配置为模拟某些东西的 CD 引导),您将需要一个“BIOS 参数块”(参见 https://en.wikipedia.org/wiki/BIOS_parameter_block )用于软盘和软盘模拟,或者一个分区table(用于硬盘或硬盘仿真)。对于真正的软盘,“BIOS 参数块”在技术上是可选的(最初是 FAT 文件系统的一部分,与 BIOS 完全无关),但它对于其他操作是常见的系统 (Windows) 抱怨磁盘不是 valid/formatted 如果有 none (这真的让普通用户感到困惑)。

j) Windows 是 can/will 损坏磁盘第一个扇区(在引导扇区代码的中间)中的 4 个字节的恶意软件,即使 Windows 没有篡改竞争操作系统的磁盘或引导加载程序的权利。具体来说,Microsoft 决定 Windows 应该使用唯一的磁盘签名来识别磁盘,因此当 Windows 第一次看到磁盘时,它将尝试获取这个唯一的签名,如果该值不唯一,它将写入第一个扇区偏移量 0x01B8 处的新“唯一”值。请注意,此行为“追溯主动”包含在与 GPT 分区方案(其中不适用于未分区磁盘、MBR 分区磁盘或使用BIOS 而不是 UEFI);在 UEFI 规范中,它被认为是可选的(字面意思是“这可能被 OS...签名是否存在(或者应该如何生成签名以允许操作系统就“唯一”值达成一致并防止每次不同的 OS 看到磁盘时修改它)。任何状况之下;你想避免在你的引导扇区中使用偏移量 0x01B8 处的 4 个字节来保护自己免受 Windows.