The*_*e04 1 assembly operating-system bootloader x86-16
我已经进入了汇编操作系统开发,目前我正在设置一个两阶段引导加载程序。我已经设置了第一阶段,但是当我重置磁盘并读取第二个扇区时,我在屏幕上看到了一堆乱码。当我尝试调用应该在 加载的代码时0x1000,没有任何反应(据我所知)。我试图找到解决方案,但我发现的解决方案似乎都不起作用,而且我的运气越来越差。这是我的完整代码:
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
Run Code Online (Sandbox Code Playgroud)
任何帮助都会很棒。作为参考,我在 Windows 上并且正在使用 nasm 进行编译。我正在通过 VirtualBox 进行测试(我知道这可能不是最好的工具,但它的工作原理与我目前所见的一样)。
一些问题:
a) BIOS“读取磁盘扇区/秒”功能需要一个段(in ES)和一个偏移量(in BX)。您没有提供偏移量(未设置BX为任何内容),因此无法猜测 BIOS 可能加载扇区的位置(它可能位于 请求的 64 KiB 段内的任何位置ES)。
b) 不管是如何sector创建的,它不能是一个段(对于mov ax, sector, mov es, ax),也不能是“当前代码段中的偏移量”(对于jmp sector),所以其中一个肯定是错误的。对于“部门= 0×1000”它看起来像它应该是一个“在当前代码段偏移”(和代码读取部门应设置ES以0x07C0匹配假定当前的代码段,做mov bx,sector治疗sector作为偏移在请求的段中)。
c) 我强烈建议尽可能将段设置为零。这有助于避免混淆和错误(特别是对于初学者),并且以后更容易切换到/从保护模式(或长模式)。要做到这一点org 0x7C00,除了更改段寄存器加载(例如mov ax, 07C0h,mov es,ax将变为mov ax, 0, mov es,ax)之外,您还需要使用(而不是“隐含的 org 0,因为它没有明确设置” )。这将包括堆栈(我建议使用“SS:SP = 0:0x7C00”)。不要忘记堆栈段限制通常实际上并没有限制任何东西——例如push,call当堆栈“满”时,只会导致 SP 溢出并从 0x0000 回绕到 0xFFFE。
d) 在第一次尝试读取扇区之前不需要重置磁盘系统(因为您知道 BIOS 成功读取了您的引导加载程序并且磁盘系统工作正常)。它应该仅用于尝试从磁盘错误中恢复。请注意(对于软盘)重置磁盘系统通常意味着重新校准驱动器,这涉及将磁头发送回“磁道 0”。
e) 当从磁盘读取错误时,有时它们是暂时的(例如由于软驱电机速度变化),有时它们不是(例如“参数错误”、“软盘弹出”等)。建议重试几次(并在两次尝试之间偶尔重置磁盘系统)以解决临时问题(尤其是软盘)。永远地来回撞击磁盘磁头(使用“重置/重新校准,搜索/读取,重置/重新校准,搜索/读取,...)是处理持久性问题的极其糟糕的方法。您需要一个计数器来跟踪有多少你已经重试了几次,这样你就可以在有限的尝试次数后停止并显示错误消息。不要忘记 BIOS 返回一个错误代码,
f) 你有控制流问题。具体来说,您call reset但该代码没有ret并落入read_sector代码中;所以call reset除非 BIOS 成功读取扇区,否则不会返回,并且call read_sector在扇区已成功读取后执行此操作毫无意义。
g) 如果read_sector代码成功,它将通过代码打印一个字符串。您打印字符串(printf例程)的代码将从内存中包含的地址处获取字符DS:SI,但SI从未在第一个扇区中设置任何字符(在执行之前),因此它可能只会打印随机/未定义的混乱(最可能的情况是未知地址处的字节恰好为零并且没有打印任何内容)。还; 读取一个字节后,lodsb SI将根据“方向标志”的设置方式递增或递减。您的代码没有将方向标志设置为任何东西(例如没有cld任何地方的指导);这意味着您的代码向后打印随机字符串的可能性极小(“!dlroW olleH”)。我不知道您是否打算ret在代码之后读取扇区,或者您是否打算设置DS:SI为“加载第二个扇区!”的地址。字符串(我假设是前者,因为第一个扇区中没有字符串)。
h) 第二个扇区的内容以字符串 (" now there is a different message!")开头。如果没有其他错误,call sector(在第 1 个扇区中)将导致执行此字符串而不是有效代码(可能导致某种壮观的崩溃)。您想将字符串移到其他地方(在第二个扇区中的代码之后)或直接跳转到代码(例如jmp sector2_code,第一个扇区中的 a 和指令sector2_code:前的标签mov si, message)。
i) 您用来读取扇区的 BIOS 功能大多不适用于硬盘(现代硬盘实在是太大了 - 您想改用“扩展磁盘读取”功能)。对于软盘,您不能使用“扩展磁盘读取”功能(必须使用您正在使用的 BIOS 功能)。对于其他情况(从 USB 闪存启动,从配置为模拟某些内容的 CD 启动),您将需要一个“BIOS 参数块”(请参阅https://en.wikipedia.org/wiki/BIOS_parameter_block) 用于软盘和软盘仿真,或分区表(用于硬盘或硬盘仿真)。对于实际的软盘,“BIOS 参数块”在技术上是可选的(最初是 FAT 文件系统的一部分,与 BIOS 完全无关),但其他操作系统 (Windows) 抱怨磁盘不是如果没有则有效/格式化(这确实让普通用户感到困惑)。
j) Windows 是一种恶意软件,它可以/将破坏磁盘第一个扇区(在引导扇区代码的中间)中的 4 个字节,即使 Windows 无权篡改竞争操作系统的磁盘或引导加载程序。具体来说,微软决定 Windows 应该使用唯一的磁盘签名来识别磁盘,所以当 Windows 第一次看到一个磁盘时,它会尝试获取这个唯一的签名,如果该值不唯一,它将在偏移处写入一个新的“唯一”值第一个扇区中的 0x01B8。请注意,此行为在 UEFI 规范中与 GPT 分区方案(其不适用于未分区的磁盘、MBR 分区的磁盘或使用BIOS 而不是 UEFI);在 UEFI 规范中它' s 被认为是可选的(字面意思是“这可能被操作系统使用......”)并且没有明确的指导说明哪个操作系统(你的或微软的)可以决定签名是否存在(或签名应该如何以允许操作系统就“唯一”值达成一致,并防止每次不同的操作系统看到磁盘时对其进行修改)。任何状况之下; 您希望避免使用引导扇区中偏移量 0x01B8 处的 4 个字节来保护自己免受 Windows 的侵害。值并防止每次不同的操作系统看到磁盘时修改它)。任何状况之下; 您希望避免使用引导扇区中偏移量 0x01B8 处的 4 个字节来保护自己免受 Windows 的侵害。值并防止每次不同的操作系统看到磁盘时修改它)。任何状况之下; 您希望避免使用引导扇区中偏移量 0x01B8 处的 4 个字节来保护自己免受 Windows 的侵害。