You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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) 

排查方向

  1. 内存地址不匹配:boot.asm将内核加载到0x1000段(物理地址0x10000开始),但链接器指定内核运行地址为0x100000,导致代码实际位置与链接地址错位,指令或数据访问异常。
  2. 栈未正确设置:kernel_jump.asm未重新设置栈指针,C++函数调用时栈操作出错,影响后续写入逻辑。
  3. 磁盘加载扇区不足:boot.asm仅加载8个扇区(4KB),若内核体积超过4KB,部分代码未被加载,运行时出现异常。
  4. 段权限或基址问题:检查GDT中数据段、VGA段的基址、限长及写权限是否正确覆盖0xB8000地址范围。

内容的提问来源于stack exchange,提问作者Ioane Baidoshvili

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 05:45:56