简体   繁体   English

远跳如何在真实硬件上工作(引导加载程序创建)

[英]how does the far jump work on real hardware (bootloader creation)

I am trying to develop my own OS (for learning purpose), everything is fine in qemu, it work well.我正在尝试开发自己的操作系统(用于学习目的),qemu 中的一切都很好,运行良好。

nasm -f bin os.s -o os
qemu-system-x86_64 -drive file=os,format=raw

So I decided to try it on real hardware and I encounter two issue: The first is that when I boot my pc with my usb key containing my OS (dd bs=4M if=os of=/dev/sdc), it start by printing "Invalid partition table.", But then.所以我决定在真正的硬件上尝试它,我遇到了两个问题:第一个是当我使用包含我的操作系统的 usb 密钥启动我的电脑时(dd bs=4M if=os of=/dev/sdc),它开始于打印“无效的分区表。”,但随后。 if i press any key it execute correctly the code and it start booting my os by printing "VOK" on the screen and it stop there.如果我按任意键,它会正确执行代码,并通过在屏幕上打印“VOK”开始启动我的操作系统,然后停在那里。

Does anyone know from where the "Invalid partition table?"有谁知道“无效分区表”的来源? could come from ?可能来自?

second question: at the end, I try to perform a far jump to exit the 16bits mode and enter 32bits mode.第二个问题:最后,我尝试执行远跳退出16位模式并进入32位模式。 As you can see I currently stop the program just before with a infinity loop (jmp $).如您所见,我目前在使用无限循环 (jmp $) 之前停止程序。 when I pass the loop just after the far jump, in the init_32 label, its working fine with qemu but doesn't work with my laptop (he reboot again and again meaning the code crashed).当我在远跳之后通过循环时,在 init_32 label 中,它在 qemu 上工作正常但在我的笔记本电脑上不起作用(他一次又一次地重新启动意味着代码崩溃)。 I have no idea why this is happening, some help would be helpful!我不知道为什么会这样,一些帮助会有所帮助!

[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

Does anyone know from where the "Invalid partition table?"有谁知道“无效分区表”的来源? could come from ?可能来自?

When USB flash was first introduced there was no standard to determine how booting from USB flash is supposed to work (unlike CD where a proper standard was created).当 USB flash 首次引入时,没有标准来确定从 USB flash 引导应该如何工作(不像 CD,其中创建了适当的标准)。 With no standard of its own, firmware was left with 2 choices: emulate a floppy disk (even though it's large like a hard disk) or emulate a hard disk (even though its removable like a floppy disk).由于没有自己的标准,固件只有两种选择:模拟软盘(即使它像硬盘一样大)或模拟硬盘(即使它像软盘一样可移动)。 Some motherboards put a setting in BIOS configuration to control what happens.一些主板在 BIOS 配置中设置了一个设置来控制发生的事情。 Eventually most/all firmware decided to try to auto-detect what it should do using glorious unspecified non-standard shenanigans.最终,大多数/所有固件决定尝试使用光荣的未指定的非标准恶作剧来自动检测它应该做什么。

Then UEFI got introduced and added the possibility of GPT partitions, and the possibility that there's a FAT partition with a UEFI boot loader (and that legacy BIOS shouldn't be used).然后引入了 UEFI 并添加了 GPT 分区的可能性,以及存在带有 UEFI 引导加载程序的 FAT 分区的可能性(并且不应使用旧版 BIOS)。

The end result is something like:最终结果是这样的:

  • if it seems like there's a BPB and no partition table;如果看起来有 BPB 而没有分区表; then it should emulate a floppy disk with legacy BIOS那么它应该用旧版 BIOS 模拟软盘

  • if it seems like there's a partition table in the MBR;如果 MBR 中似乎有一个分区表; check if it's a protective MBR and if there's a GPT partition table too.检查它是否是保护性 MBR,以及是否也有 GPT 分区表。

  • if there's a partition table (of either type - MBR or GPT), use it to check for a UEFI partition with a FAT file system that contains a UEFI boot loader;如果有分区表(MBR 或 GPT 类型),请使用它来检查具有包含 UEFI 引导加载程序的 FAT 文件系统的 UEFI 分区; and if there is then do UEFI boot (no emulation, no legacy stuff)如果有,则执行 UEFI 引导(无仿真,无遗留内容)

  • if there's a partition table (of either type) but no UEFI boot loader;如果有分区表(任一类型)但没有 UEFI 引导加载程序; then emulate hard disk for legacy BIOS然后为传统 BIOS 模拟硬盘

  • if none of the above worked, make a unspecified default choice (either emulate floppy with legacy BIOS, or emulate hard drive with legacy BIOS, or assume that the device can't be booted from at all)如果以上都不起作用,请做出未指定的默认选择(使用旧版 BIOS 模拟软盘,或使用旧版 BIOS 模拟硬盘驱动器,或者假设设备根本无法启动)

Of course "glorious unspecified non-standard shenanigans" can include anything, and can include displaying a warning message when nothing makes sense (eg when there's nothing that seems like it might be a BPB and also nothing that seems like it might be a partition table).当然,“光荣的未指定的非标准恶作剧”可以包括任何东西,并且可以包括在没有任何意义时显示警告消息(例如,当没有任何东西看起来像是 BPB,也没有任何东西看起来像是分区表时).

Note that "glorious unspecified non-standard shenanigans" can also include unexpected dodgy nonsense;请注意,“光荣的未指明的非标准恶作剧”还可以包括意想不到的狡猾的胡说八道; like if there's MBR partitions, assuming code in the MBR merely chain-loads a boot loader from the active partition and firmware booting the 1st sector of the active partition directly without executing any code in the MBR;就像有 MBR 分区一样,假设 MBR 中的代码只是从活动分区和固件链式加载引导加载程序,直接引导活动分区的第一个扇区而不执行 MBR 中的任何代码; and like "correcting" (corrupting) fields in an assumed BPB that doesn't look like a BPB at all.就像在一个看起来根本不像 BPB 的假定 BPB 中“更正”(损坏)字段一样。

Also be warned that (in an abundance of pure arrogance) Microsoft decided that it's fine if their OS writes a 32-bit "disk signature" at offset 0x01B8 in the MBR of disks that belong to other people's operating systems (that they have no right to touch in any way whatsoever), and that this can also corrupt an operating system's boot code, and (if an OS uses it) it can ruin TPM (where code used during boot, including MBR, is "measured" by firmware before its used, for security purposes - for things like remote attestation and establishing disk encryption keys).还要注意的是(出于纯粹的傲慢)微软决定如果他们的操作系统在属于其他人操作系统的磁盘的 MBR 偏移量 0x01B8 处写入 32 位“磁盘签名”就可以了(他们没有权利以任何方式触摸),并且这也可能破坏操作系统的引导代码,并且(如果操作系统使用它)它可能破坏 TPM(其中引导期间使用的代码,包括 MBR,在其之前由固件“测量”使用,出于安全目的 - 用于远程证明和建立磁盘加密密钥之类的事情)。

To guard against all of the above, you'll want to pick a clearly defined case ("floppy emulation, legacy BIOS", "hard disk emulation, legacy BIOS" or "UEFI boot");为了防止上述所有情况,您需要选择一个明确定义的情况(“软盘仿真、旧版 BIOS”、“硬盘仿真、旧版 BIOS”或“UEFI 启动”); and do everything you can to steer the firmware's "glorious unspecified non-standard shenanigans" towards the correct choice (eg if you chose "hard disk emulation, legacy BIOS"; then fill the area where a BPB would be with zeros to reduce the chance of firmware thinking it might be a valid BPB and choosing "floppy emulation");并尽你所能引导固件的“光荣的未指定的非标准恶作剧”走向正确的选择(例如,如果你选择“硬盘仿真,传统 BIOS”;然后用零填充 BPB 的区域以减少机会固件认为它可能是一个有效的 BPB 并选择“软盘仿真”); and then assume anything in the area where the BPB would be (from offset 0x000B to at least 0x003F inclusive) and the 4 bytes at offset 0x01B8 may be corrupted and not use these bytes.然后假设 BPB 所在区域中的任何内容(从偏移量 0x000B 到至少 0x003F,包括在内)并且偏移量 0x01B8 处的 4 个字节可能已损坏并且不使用这些字节。

Of course you'll also want the dw 0xaa55 in the last 2 bytes, and a jmp in the first few bytes;当然,您还需要最后 2 个字节中的dw 0xaa55和前几个字节中的jmp as these may be used (and might not be used) by firmware to decide if the disk is bootable.因为这些可能被固件使用(也可能不使用)来决定磁盘是否可引导。

when I pass the loop just after the far jump, in the init_32 label, its working fine with qemu but doesn't work with my laptop (he reboot again and again meaning the code crashed)当我在远跳之后通过循环时,在 init_32 label 中,它与 qemu 一起工作正常但不适用于我的笔记本电脑(他一次又一次地重新启动意味着代码崩溃)

Possible causes include:可能的原因包括:

  • Firmware corrupting your code (described above)固件损坏您的代码(如上所述)

  • Windows corrupting your code (described above) Windows 损坏您的代码(如上所述)

  • Firmware skipping MBR code and booting 1st sector of a partition that doesn't exist (mentioned above)固件跳过 MBR 代码并引导不存在的分区的第一个扇区(如上所述)

  • Your code making assumptions about the undefined value BIOS felt like leaving in ES , loading a sector at this undefined address (that depends on the value left in ES ), then either corrupting itself (because the undefined address is where your code or stack is) or doing a far jump to random garbage at a defined address that isn't where the sector was loaded.您的代码对 BIOS 的未定义值做出假设感觉就像留在ES,在这个未定义的地址加载一个扇区(这取决于ES中留下的值),然后破坏自身(因为未定义的地址是您的代码或堆栈所在的位置)或者在一个定义的地址处远跳到随机垃圾,该地址不是加载扇区的地方。

  • Your code making assumptions about the undefined value BIOS felt like leaving in SS and SP , loading a sector at a "possibly accidentally correct" address that happens to be where the stack is, then crashing (eg BIOS int 0x13 code returning to a corrupted address because the stack was trashed).您的代码假设 BIOS 的未定义值感觉就像留在SSSP中,在“可能意外正确”的地址加载一个扇区,该地址恰好是堆栈所在的位置,然后崩溃(例如 BIOS int 0x13代码返回到损坏的地址因为堆栈被丢弃了)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM