OS开发求助:主汇编文件外无法写入VGA内存
内核写入VGA内存失效排查
我正在开发操作系统,刚完成引导加载程序编写,现在开始开发内核。首先实现的功能是从kernel_main.cpp向0xB8000地址的VGA内存写入文本,但运行后预期字符未出现。调试发现:
boot.asm中实模式和保护模式都能正常打印- 远跳转到0x100000地址后,
kernel_jump.asm及C++代码的写入操作均失效 - GDB确认已成功进入
kernel_main和kernel_jump.asm的_start入口,已排查一周未找到根源
boot.asm代码
; Starting in real mode [BITS 16] [ORG 0x7C00] ; BIOS will load the bootloader to this address ; Addresses and offsets CODE_SEG_OFFSET equ 0x08 DATA_SEG_OFFSET equ 0x10 VGA_SEG_OFFSET equ 0x18 KERNEL_LOAD_SEG equ 0x1000 KERNEL_START_ADDRESS equ 0x100000 ; 0x7C00 + 1 MiB start: cli ; Dissabling interferences ; Setting up segment registers and stack pointer mov ax, 0x00 mov ds, ax mov es, ax mov ss, ax mov sp, 0x7c00 sti ; Enabling interferences ; Set VGA text mode (80x25) to ensure control over the screen mov ah, 0x00 mov al, 0x03 ; 80x25 text mode int 0x10 ; Loading the Global Descriptor Table lgdt [gdt_descriptor] ; Loading the kernel using CHS mov bx, KERNEL_LOAD_SEG mov dh, 0x00 mov dl, 0x80 mov cl, 0x02 mov ch, 0x00 mov ah, 0x02 mov al, 8 int 0x13 jc disk_read_error ; Managing disk read errors ; Checking the first few bytes of the kernel in memory ; mov bx, KERNEL_LOAD_SEG ; Load segment where kernel is loaded ; mov ds, bx ; Set data segment to point to kernel ; mov eax, [0x1000] ; Load the first word from kernel (segment 0x1000) ; cmp eax, 0xDEADBEE1 ; Magic number ; jne kernel_load_error ; If not matching, jump to the failure handler load_protected_mode: cli ; Dissabling interferences ; Switching to protected mode mov eax, cr0 or eax, 1 mov cr0, eax ; Far jump jmp CODE_SEG_OFFSET:protected_mode_main ; Error labels disk_read_error: mov eax, 0xB8000 ; VGA buffer address mov byte [eax], 'D' ; Display 'D' for disk mov byte [eax + 1], 0x4F ; Red background with white text jmp $ kernel_load_error: mov eax, 0xB8000 ; VGA buffer address mov byte [eax], 'K' ; Display 'K' for kernel mov byte [eax + 1], 0x4F ; Red background with white text jmp $ ; Global Descriptor Table gdt_table: ; Null descriptor dd 0x0 dd 0x0 ; Code segment descriptor dw 0xFFFF ; Limit dw 0x0000 ; Base address low db 0x00 ; Base address middle db 10011010b ; Defining access rights of the memory segment db 11001111b ; Defining flags db 0x00 ; Base high ; Data segment descriptor dw 0xFFFF ; Limit dw 0x0000 ; Base address low db 0x00 ; Base address middle db 10010010b ; Defining access rights of the memory segment db 11001111b ; Defining flags db 0x00 ; Base high ; VGA text mode segment descriptor dw 0x0FFF ; Limit dw 0x8000 ; Base address low db 0x0B ; Base address middle db 10010010b ; Defining access rights of the memory segment db 11001111b ; Defining flags db 0x00 ; Base high gdt_end: ; For calculating GDT size gdt_descriptor: dw gdt_end - gdt_table - 1 dd gdt_table [BITS 32] ; 32 bit protected mode protected_mode_main: ; Setting up segment registers mov ax, DATA_SEG_OFFSET mov ds, ax mov es, ax mov fs, ax mov ss, ax mov gs, ax mov ebp, 0x9C00 mov esp, ebp ; Set VGA segment descriptor for ES to access VGA memory at 0xB8000 mov ax, VGA_SEG_OFFSET mov es, ax mov eax, 0xB8000 mov byte [eax], '+' mov byte [eax + 1], 0x4F ; Far jump to kernel_jump.asm jmp CODE_SEG_OFFSET:KERNEL_START_ADDRESS ; Filling remaining space with 0 times 510 - ($ - $$) db 0 dw 0xAA55
kernel_jump.asm代码
[BITS 32] ; We are in protected mode global _start extern kernel_main ; External symbol of kernel_main.cpp _start: ; Setting up VGA segment mov ax, 0x18 mov es, ax ; Print directly to VGA buffer in protected mode mov eax, 0xB8000 mov byte [eax], 'C' ; Character mov byte [eax + 1], 0x4F ; Attribute byte call kernel_main ; Connecting to kernel_main.cpp (void kernel_main()) jmp $ ; Infinite loop times 512 - ($ - $$) db 0 ; Filling remainder of 512 bytes with 0
链接器脚本代码
ENTRY(_start) OUTPUT_FORMAT(binary) /* .bin */ SECTIONS { . = 0x100000; /* 1 MiB (beyond the real mode) */ .text : ALIGN(4096) { *(.text) } .rodata : ALIGN(4096) { *(.rodata) } .data : ALIGN(4096) { *(.data) } .bss : ALIGN(4096) { *(COMMON) *(.bss) } }
C++打印函数代码
// Adding new line void print_newline() { col = 0; // Only adding new line if possible if(row < NUM_ROWS - 1) { ++row; } else { // Scrolling the screen up for(size_t r = 1; r < NUM_ROWS; ++r) { for(size_t c = 0; c < NUM_COLS; ++c) { // Copying this row to the one above buffer[c + NUM_COLS * (r - 1)] = buffer[c + NUM_COLS * r]; } } clear_row(NUM_ROWS - 1); } }
Makefile代码
# ========================================== # Makefile # For building the project # # Author: Ioane Baidoshvili # Date: 02-Sep-24 # ========================================== # Directories BOOTLOADER_DIR = src/bootloader KERNEL_DIR = src/kernel BIN = $(CURDIR)/bin BUILD = $(CURDIR)/build # HPP include directories INCLUDE = -I src/include # Tools ASM = nasm LD = ld CXX = i686-elf-g++ CXX_FLAGS = $(INCLUDE) -g -Wall -O2 -ffreestanding -fno-exceptions -fno-rtti ELF_LD = i686-elf-ld ELF_LD_FLAGS = -g -relocatable GCC = i686-elf-gcc GCC_FLAGS = -g -ffreestanding -nostdlib -nostartfiles -nodefaultlibs -Wall -O0 -Iinc # Output files OS_BIN = $(BIN)/os.bin # Linked binary file BOOT_BIN = $(BIN)/boot.bin KERNEL_JUMP_BIN = $(BIN)/kernel_jump.bin KERNEL_JUMP_OBJ = $(BUILD)/kernel_jump.o KERNEL_OBJ = $(BUILD)/kernel_main.o LINKED_OBJ = $(BUILD)/linkedKernel.o PRINT_OBJ = $(BUILD)/print.o all: # Compiling ASM files $(ASM) -f bin $(BOOTLOADER_DIR)/boot.asm -o $(BOOT_BIN) $(ASM) -f elf -g $(BOOTLOADER_DIR)/kernel_jump.asm -o $(KERNEL_JUMP_OBJ) # Compiling CPP files $(CXX) -I./src $(CXX_FLAGS) -std=c++23 -c $(KERNEL_DIR)/kernel_main.cpp -o $(KERNEL_OBJ) $(CXX) -I./src $(CXX_FLAGS) -std=c++23 -c $(KERNEL_DIR)/drivers_src/print_src/print.cpp -o $(PRINT_OBJ) # Merging kernel_jump.o with kernel_main.o $(ELF_LD) $(ELF_LD_FLAGS) $(KERNEL_JUMP_OBJ) $(KERNEL_OBJ) $(PRINT_OBJ) -o $(LINKED_OBJ) # Making final product $(GCC) $(GCC_FLAGS) -T tools/linker.ld -o $(KERNEL_JUMP_BIN) -ffreestanding -O0 -nostdlib $(LINKED_OBJ) # Polishing result dd if=$(BOOT_BIN) >> $(OS_BIN) dd if=$(KERNEL_JUMP_BIN) >> $(OS_BIN) dd if=/dev/zero bs=512 count=8 >> $(OS_BIN) # Cleaning the build clean: rm -f $(OS_BIN) rm -f $(BOOT_BIN) rm -f $(KERNEL_JUMP_BIN) rm -f $(KERNEL_JUMP_OBJ) rm -f $(KERNEL_OBJ) rm -f $(LINKED_OBJ) rm -f $(PRINT_OBJ)
排查方向
- 内存地址不匹配:boot.asm将内核加载到0x1000段(物理地址0x10000开始),但链接器指定内核运行地址为0x100000,导致代码实际位置与链接地址错位,指令或数据访问异常。
- 栈未正确设置:
kernel_jump.asm未重新设置栈指针,C++函数调用时栈操作出错,影响后续写入逻辑。 - 磁盘加载扇区不足:boot.asm仅加载8个扇区(4KB),若内核体积超过4KB,部分代码未被加载,运行时出现异常。
- 段权限或基址问题:检查GDT中数据段、VGA段的基址、限长及写权限是否正确覆盖0xB8000地址范围。
内容的提问来源于stack exchange,提问作者Ioane Baidoshvili
相关产品推荐
相关产品推荐

