[英]how does the far jump work on real hardware (bootloader creation)
我正在尝试开发自己的操作系统(用于学习目的),qemu 中的一切都很好,运行良好。
nasm -f bin os.s -o os
qemu-system-x86_64 -drive file=os,format=raw
所以我决定在真正的硬件上尝试它,我遇到了两个问题:第一个是当我使用包含我的操作系统的 usb 密钥启动我的电脑时(dd bs=4M if=os of=/dev/sdc),它开始于打印“无效的分区表。”,但随后。 如果我按任意键,它会正确执行代码,并通过在屏幕上打印“VOK”开始启动我的操作系统,然后停在那里。
有谁知道“无效分区表”的来源? 可能来自?
第二个问题:最后,我尝试执行远跳退出16位模式并进入32位模式。 如您所见,我目前在使用无限循环 (jmp $) 之前停止程序。 当我在远跳之后通过循环时,在 init_32 label 中,它在 qemu 上工作正常但在我的笔记本电脑上不起作用(他一次又一次地重新启动意味着代码崩溃)。 我不知道为什么会这样,一些帮助会有所帮助!
[org 0x7c00]
SECTOR_READ equ 0x08
; memory offset where our kernel is located
KERNEL_OFFSET equ 0x7e00
; this is the boot loader which will be present on the MBR
; sector 1, 512 bytes
mov ah, 0x0e ; tty mode
mov bp, 0x9000 ; this is an address far away from 0x7c00 so that we don't get overwritten
mov sp, bp ; if the stack is empty then sp points to bp
mov al, 'V'
int 0x10
; let's load our OS loader
; load 'dh' sectors from drive 'dl' into ES:BX
mov bx, KERNEL_OFFSET;
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, SECTOR_READ ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; DONT TOUCH
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
int 0x13 ; BIOS interrupt
jc error ; if error (stored in the carry bit)
cmp al, SECTOR_READ ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne error
mov ah, 0x0e ; tty mode
mov al, 'O'
int 0x10
mov al, 'K'
int 0x10
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
jmp switch_64bits
error:
mov ah, 0x0e ; tty mode
mov al, 'K'
int 0x10
mov al, 'O'
int 0x10
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
jmp $
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
[bits 16]
switch_64bits: ; load 32 bit first, then 64 bits
; clear all interrupts
cli
; load our Global Descriptor Table
lgdt [gdt_descriptor]
; switch to protected mode
; set PE (Protection Enable) bit in CR0
; CR0 is a Control Register 0
mov eax, cr0
or al, 0x1
mov cr0, eax
; far jump to 32 bit instructions
; so we can be sure processor has done
; all other operations before switch
; at this moment we can say bye to 16-bit Real Mode
jmp $
jmp CODE_SEG:init_32
[bits 32]
init_32:
; padding and magic number
times 510 - ($-$$) db 0
; Magic number
dw 0xaa55
times 256 dw 0x0202 ; sector 2 = 512 bytes 0x7e00
times 256 dw 0x0303 ; sector 3 = 512 bytes 0x8000
times 256 dw 0x0404 ; sector 4 = 512 bytes 0x8200
times 256 dw 0x0505 ; sector 5 = 512 bytes 0x8400
times 256 dw 0x0606 ; sector 6 = 512 bytes 0x8600
times 256 dw 0x0707 ; sector 7 = 512 bytes 0x8800
times 256 dw 0x0808 ; sector 8 = 512 bytes 0x8a00
times 256 dw 0x0909 ; sector 9 = 512 bytes 0x8c00
有谁知道“无效分区表”的来源? 可能来自?
当 USB flash 首次引入时,没有标准来确定从 USB flash 引导应该如何工作(不像 CD,其中创建了适当的标准)。 由于没有自己的标准,固件只有两种选择:模拟软盘(即使它像硬盘一样大)或模拟硬盘(即使它像软盘一样可移动)。 一些主板在 BIOS 配置中设置了一个设置来控制发生的事情。 最终,大多数/所有固件决定尝试使用光荣的未指定的非标准恶作剧来自动检测它应该做什么。
然后引入了 UEFI 并添加了 GPT 分区的可能性,以及存在带有 UEFI 引导加载程序的 FAT 分区的可能性(并且不应使用旧版 BIOS)。
最终结果是这样的:
如果看起来有 BPB 而没有分区表; 那么它应该用旧版 BIOS 模拟软盘
如果 MBR 中似乎有一个分区表; 检查它是否是保护性 MBR,以及是否也有 GPT 分区表。
如果有分区表(MBR 或 GPT 类型),请使用它来检查具有包含 UEFI 引导加载程序的 FAT 文件系统的 UEFI 分区; 如果有,则执行 UEFI 引导(无仿真,无遗留内容)
如果有分区表(任一类型)但没有 UEFI 引导加载程序; 然后为传统 BIOS 模拟硬盘
如果以上都不起作用,请做出未指定的默认选择(使用旧版 BIOS 模拟软盘,或使用旧版 BIOS 模拟硬盘驱动器,或者假设设备根本无法启动)
当然,“光荣的未指定的非标准恶作剧”可以包括任何东西,并且可以包括在没有任何意义时显示警告消息(例如,当没有任何东西看起来像是 BPB,也没有任何东西看起来像是分区表时).
请注意,“光荣的未指明的非标准恶作剧”还可以包括意想不到的狡猾的胡说八道; 就像有 MBR 分区一样,假设 MBR 中的代码只是从活动分区和固件链式加载引导加载程序,直接引导活动分区的第一个扇区而不执行 MBR 中的任何代码; 就像在一个看起来根本不像 BPB 的假定 BPB 中“更正”(损坏)字段一样。
还要注意的是(出于纯粹的傲慢)微软决定如果他们的操作系统在属于其他人操作系统的磁盘的 MBR 偏移量 0x01B8 处写入 32 位“磁盘签名”就可以了(他们没有权利以任何方式触摸),并且这也可能破坏操作系统的引导代码,并且(如果操作系统使用它)它可能破坏 TPM(其中引导期间使用的代码,包括 MBR,在其之前由固件“测量”使用,出于安全目的 - 用于远程证明和建立磁盘加密密钥之类的事情)。
为了防止上述所有情况,您需要选择一个明确定义的情况(“软盘仿真、旧版 BIOS”、“硬盘仿真、旧版 BIOS”或“UEFI 启动”); 并尽你所能引导固件的“光荣的未指定的非标准恶作剧”走向正确的选择(例如,如果你选择“硬盘仿真,传统 BIOS”;然后用零填充 BPB 的区域以减少机会固件认为它可能是一个有效的 BPB 并选择“软盘仿真”); 然后假设 BPB 所在区域中的任何内容(从偏移量 0x000B 到至少 0x003F,包括在内)并且偏移量 0x01B8 处的 4 个字节可能已损坏并且不使用这些字节。
当然,您还需要最后 2 个字节中的dw 0xaa55
和前几个字节中的jmp
; 因为这些可能被固件使用(也可能不使用)来决定磁盘是否可引导。
当我在远跳之后通过循环时,在 init_32 label 中,它与 qemu 一起工作正常但不适用于我的笔记本电脑(他一次又一次地重新启动意味着代码崩溃)
可能的原因包括:
固件损坏您的代码(如上所述)
Windows 损坏您的代码(如上所述)
固件跳过 MBR 代码并引导不存在的分区的第一个扇区(如上所述)
您的代码对 BIOS 的未定义值做出假设感觉就像留在ES
中,在这个未定义的地址加载一个扇区(这取决于ES
中留下的值),然后破坏自身(因为未定义的地址是您的代码或堆栈所在的位置)或者在一个定义的地址处远跳到随机垃圾,该地址不是加载扇区的地方。
您的代码假设 BIOS 的未定义值感觉就像留在SS
和SP
中,在“可能意外正确”的地址加载一个扇区,该地址恰好是堆栈所在的位置,然后崩溃(例如 BIOS int 0x13
代码返回到损坏的地址因为堆栈被丢弃了)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.