目前,在通过将寄存器 cr0 中的 lsb 设置为 1 进入保护模式后,我的引导加载程序遇到了困难。我执行了远跳转到由
jmp 0x8:start_kernel
定义的内核代码段。我使用 0x8
是因为我的内核代码从 GDT 中的第 8 个字节偏移量开始,重置代码段(寄存器 cs)。然后我将其他段寄存器设置为 0x10(第 16 个字节偏移量 ~ 由 GDT 定义的内核数据段选择器)。我重新启用中断 (sti) 并调用 kernel_main(内核入口点)。进入保护模式后,Qemu 模拟器会不受控制地闪烁,并且不会产生内核的输出。任何帮助将不胜感激,谢谢!
boot.asm:
[bits 16]
extern kernel_main
global start_kernel
xor ax, ax
mov es, ax
mov ds, ax
mov bp, 0x8000
mov sp, bp
mov dl, 0x80 ; first hard disk
mov ah, 0x02 ; int 0x13 function number
mov al, 0x05 ; kernel is only 512 bytes (# sector's we have to read)
mov ch, 0x00 ; cylinder number
mov cl, 0x02 ; #starting sector (starts from 1 not 0) first 512 bytes for boot loader
mov dh, 0x00 ; head number
mov bx, 0x7e00 ; code for bootloader starts at 7c00 ~ 31744 bytes + 512 bytes (for bootlaoder) = 7E00
int 0x13
lgdt [gdtr]
mov ah, 0x0 ; service for setting the video mode
mov al, 0x03 ; setting video mode to be text mode w/ 16 colors
int 0x10
cli
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:start_kernel
[bits 32]
start_kernel:
mov eax, 0x10
mov ss, eax
mov ds, eax
mov fs, eax
mov gs, eax
mov es, eax
sti
call kernel_main
gdt:
null_descriptor: db 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
kernel_code: db 0x0, 0xCF, 0x9A, 0x0, 0x0, 0x0, 0xFF, 0xFF
kernel_data: db 0x0, 0xCF, 0x92, 0x0, 0x0, 0x0, 0xFF, 0xFF
user_code: db 0x0, 0xCF, 0xFC, 0x0, 0x0, 0x0, 0xFF, 0xFF
user_data: db 0x0, 0xCF, 0xF2, 0x0, 0x0, 0x0, 0xFF, 0xFF
gdt_end:
gdtr:
size: dw gdt_end - gdt - 1
base: dd gdt
times 510-($-$$) db 0
db 0x55, 0xaa
生成文件:
ASM=nasm
CC=x86_64-elf-gcc
BOOTLOADER=boot.asm
BOOTLOADER_BIN=boot.o
KERNEL=kernel.c
KERNEL_FLAGS= -Wall -m32 -ffreestanding -fno-asynchronous-unwind-tables -fno-pie -c
KERNEL_OBJECT= kernel.o
KERNEL_IMG=kernel.img
LINKER_FILE=linker.ld
OUTPUT_BIN=kernel.bin
build: $(BOOTLOADER) $(KERNEL)
$(ASM) -f elf32 $(BOOTLOADER) -o $(BOOTLOADER_BIN)
$(CC) $(KERNEL_FLAGS) $(KERNEL) -o $(KERNEL_OBJECT)
x86_64-elf-ld -melf_i386 -T$(LINKER_FILE) $(BOOTLOADER_BIN) $(KERNEL_OBJECT) -o linked_kernel.elf
x86_64-elf-objcopy -O binary linked_kernel.elf $(OUTPUT_BIN)
dd if=$(OUTPUT_BIN) of=$(KERNEL_IMG) conv=notrunc
qemu-system-x86_64 -s kernel.img
clean:
rm -f $(BOOTLOADER_BIN) $(KERNEL_OBJECT) $(OUTPUT_ELF) $(OUTPUT_BIN) $(KERNEL_IMG)
内核.c:
volatile unsigned char* video = (volatile unsigned char*)0xB8000;
void kernel_main() {
// Write 'H' and 'i' at the top-left of the screen.
video[0] = 'H'; // Character 'H'
video[1] = 0x04; // Attribute byte (light gray on black)
video[2] = 'i'; // Character 'i'
video[3] = 0x04; // Attribute byte (light gray on black)
// Infinite loop to keep the kernel running
while (1);
}
有关 GDT 表的信息:
gdt = '''
[
{ "name": "null_descriptor", "type": "null" },
{ "name": "kernel_code", "base_address": "0",
"limit": "fffff", "granularity": "4kb",
"system_segment": false, "type": "code",
"accessed": false, "read_enabled": true, "conforming": false,
"privilege_level": 0, "present": true, "operation_size": "32bit", "64bit": false },
{ "name": "kernel_data", "base_address": "0",
"limit": "fffff", "granularity": "4kb",
"system_segment": false, "type": "data",
"accessed": false, "expands": "up", "write_enabled": true,
"privilege_level": 0, "present": true, "upper_bound": "4gb", "64bit": false },
{ "name": "userspace_code", "base_address": "0",
"limit": "fffff", "granularity": "4kb",
"system_segment": false, "type": "code",
"accessed": false, "read_enabled": true, "conforming": false,
"privilege_level": 3, "present": true, "operation_size": "32bit", "64bit": false },
{ "name": "userspace_data", "base_address": "0",
"limit": "fffff", "granularity": "4kb",
"system_segment": false, "type": "data",
"accessed": false, "expands": "up", "write_enabled": true,
"privilege_level": 3, "present": true, "upper_bound": "4gb", "64bit": false }
]
''';
jmp 0x08:start_kernel
这个指令是完全错误的。第一个问题是您需要在 jmp 指令上使用 32 位覆盖前缀。 (最简单的方法是通过 db 0x66 指令发出字节)。第二个问题是您的 start_kernel 标签不在您认为的位置,因为您缺少 org 前缀,因此它没有从 7C00 开始组装。
我很确定闪烁是由于您在 800 和 7C00 之间跌落至零内存并再次运行引导加载程序所致。尽管处于 32 位模式,但您仍在使用部分 16 位指令解码,直到您执行前面带有 32 位前缀的远跳转;我预计在真正的 CPU 上,这会在第一个
int
指令上失败。
您将面临的下一个问题是您从未在16位模式下初始化过ss;但是一旦你修复了你的内核读取将覆盖你的堆栈并且你的代码将无法运行。尝试将 sp 和 bp 初始化为 7C00 而不是 8000。