进入自定义引导加载程序的保护模式时,Qemu 模拟器会闪烁

问题描述 投票:0回答:1

目前,在通过将寄存器 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  }
]
''';
assembly x86 bootloader osdev low-level
1个回答
0
投票
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。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.