admin管理员组

文章数量:1402342

I am trying to make my own OS and am currently stuck on one tiny problem.
My welcome messages are not loading in the terminal.

Other than that, everything seems to work fine. (I'm not too sure, so maybe there are other bugs as well.)

Here is what QEMU displays after running:

make clear
make 
make run

Here are my files for reference.

kernel.c

/* kernel.c - Main kernel entry point */

/* Video memory address */
#define VIDEO_MEMORY 0xB8000
/* Color: white on black */
#define COLOR 0x0F

/* Function to write a character to video memory */
void putchar(char c, int x, int y) {
    unsigned char *video_memory = (unsigned char*)VIDEO_MEMORY;
    int offset = (y * 80 + x) * 2;
    video_memory[offset] = c;
    video_memory[offset + 1] = COLOR;
}

/* Function to clear the screen */
void clear_screen() {
    unsigned char *video_memory = (unsigned char*)VIDEO_MEMORY;
    for(int i = 0; i < 80 * 25 * 2; i += 2) {
        video_memory[i] = ' ';
        video_memory[i + 1] = COLOR;
    }
}

/* Function to print a string */
void print_string(const char *str, int x, int y) {
    int i = 0;
    while(str[i] != '\0') {
        putchar(str[i], x + i, y);
        i++;
    }
}

/* Main kernel function */
void kernel_main() {
    // Clear screen
    
    clear_screen();
    // Display a welcome message
    print_string("NOX OS Kernel Loaded Successfully!", 20, 10);
    print_string("Welcome to the dark side...", 25, 12);
    
    // Halt the CPU
    while(1) {
        __asm__ volatile("hlt");
    }
}

linker.ld

ENTRY(_start)

SECTIONS {
    . = 0x1000;    /* Match the address in the bootloader */
    
    .text : {
        *(.text)
    }
    
    .data : {
        *(.data)
    }
    
    .bss : {
        *(.bss)
    }
}

boot.asm

; boot.asm - A simple bootloader
[bits 16]
[ 0x7c00]

; Set up segments
cli
mov ax, 0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
sti

; Display a message
mov si, boot_message
call print_string

; Load the kernel from disk
mov ah, 0x02    ; BIOS read sector function
mov al, 15      ; Number of sectors to read (adjust as needed)
mov ch, 0       ; Cylinder number
mov cl, 2       ; Sector number (sectors start from 1, bootloader is at 1)
mov dh, 0       ; Head number
mov dl, 0       ; Drive number (0 = floppy disk)
mov bx, 0x1000  ; Memory location to load the kernel
int 0x13        ; Call BIOS interrupt
jc disk_error   ; Jump if error (carry flag set)

; Switch to protected mode
cli                    ; Disable interrupts
lgdt [gdt_descriptor]  ; Load GDT

; Set protected mode bit
mov eax, cr0
or eax, 0x1
mov cr0, eax

; Far jump to 32-bit code
jmp CODE_SEG:protected_mode_entry

disk_error:
    mov si, disk_error_msg
    call print_string
    jmp hang

; Infinite loop for when we're done
hang:
    jmp hang

; Print string routine
print_string:
    lodsb
    or al, al
    jz done
    mov ah, 0x0E
    int 0x10
    jmp print_string
done:
    ret

; Global Descriptor Table
gdt_start:
    ; Null descriptor
    dd 0x0
    dd 0x0
    
    ; Code segment descriptor
    dw 0xffff    ; Limit (bits 0-15)
    dw 0x0000    ; Base (bits 0-15)
    db 0x00      ; Base (bits 16-23)
    db 10011010b ; Access byte
    db 11001111b ; Flags and Limit (bits 16-19)
    db 0x0       ; Base (bits 24-31)
    
    ; Data segment descriptor
    dw 0xffff    ; Limit 
    dw 0x0000    ; Base (bits 0-15)
    db 0x00      ; Base (bits 16-23)
    db 10010010b ; Access byte
    db 11001111b ; Flags and Limit
    db 0x0       ; Base (bits 24-31)
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1  ; GDT size
    dd gdt_start                ; GDT address

; Constants
CODE_SEG equ 0x08
DATA_SEG equ 0x10

; Messages
boot_message db 'NOX OS Booting...', 0
disk_error_msg db 'Error loading kernel!', 0

[bits 32]
protected_mode_entry:
    ; Set up segment registers for protected mode
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    
    ; Set up a stack
    mov esp, 0x90000
    
    ; Far jump to the kernel using a code segment selector
    jmp CODE_SEG:0x1000

; Padding and boot signature
times 510-($-$$) db 0
dw 0xAA55

entry.asm

; entry.asm - Assembly entry point that calls our C kernel
[bits 32]
[global _start]
[extern kernel_main]  ; Make sure this matches your C function name

section .text
_start:
    ; We're already in protected mode, skip trying to use BIOS interrupts
    
    ; Show a debug character at position 3
    mov byte [0xB8004], 'E'
    mov byte [0xB8005], 0x07
    
    ; Set up kernel stack
    mov esp, kernel_stack_top
    
    ; Call the C kernel main function
    call kernel_main
    
    ; Kernel should never return, but if it does:
    cli                 ; Disable interrupts
    hlt                 ; Halt the CPU
    jmp $               ; Infinite loop
    
; Reserve space for the kernel stack
section .bss
align 16
kernel_stack_bottom:
    resb 16384  ; 16 KB for kernel stack
kernel_stack_top:

Makefile

# Compiler settings
CC = x86_64-elf-gcc
CFLAGS = -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs -Wall -Wextra -c
ASM = nasm
ASMFLAGS = -f elf32
LD = x86_64-elf-ld
LDFLAGS = -T src/kernel/linker.ld -m elf_i386

# Directories
SRC_DIR = src
BUILD_DIR = build

# Files
BOOT_SRC = $(SRC_DIR)/boot/boot.asm
KERNEL_ENTRY = $(SRC_DIR)/kernel/entry.asm
KERNEL_SRC = $(SRC_DIR)/kernel/kernel.c
BOOT_BIN = $(BUILD_DIR)/boot.bin
KERNEL_OBJ = $(BUILD_DIR)/kernel.o
ENTRY_OBJ = $(BUILD_DIR)/entry.o
KERNEL_BIN = $(BUILD_DIR)/kernel.bin
OS_IMAGE = $(BUILD_DIR)/nox-os.img

# Build rules
all: $(OS_IMAGE)

$(BOOT_BIN): $(BOOT_SRC)
    $(ASM) -f bin $< -o $@

$(ENTRY_OBJ): $(KERNEL_ENTRY)
    $(ASM) $(ASMFLAGS) $< -o $@

$(KERNEL_OBJ): $(KERNEL_SRC)
    $(CC) $(CFLAGS) $< -o $@

$(KERNEL_BIN): $(ENTRY_OBJ) $(KERNEL_OBJ)
    $(LD) $(LDFLAGS) -o $@ $^

$(OS_IMAGE): $(BOOT_BIN) $(KERNEL_BIN)
    dd if=/dev/zero of=$@ bs=512 count=2880
    dd if=$(BOOT_BIN) of=$@ conv=notrunc
    # Ensure the kernel is properly aligned at sector 2
    dd if=$(KERNEL_BIN) of=$(OS_IMAGE) seek=1 conv=notrunc bs=512

run: $(OS_IMAGE)
    qemu-system-i386 -fda $(OS_IMAGE) -boot a -monitor stdio -d int -no-reboot

clean:
    rm -rf $(BUILD_DIR)/*
    mkdir -p $(BUILD_DIR)

I tried increasing the number of sectors read in boot.asm, but that didn't work either.

I am trying to make my own OS and am currently stuck on one tiny problem.
My welcome messages are not loading in the terminal.

Other than that, everything seems to work fine. (I'm not too sure, so maybe there are other bugs as well.)

Here is what QEMU displays after running:

make clear
make 
make run

Here are my files for reference.

kernel.c

/* kernel.c - Main kernel entry point */

/* Video memory address */
#define VIDEO_MEMORY 0xB8000
/* Color: white on black */
#define COLOR 0x0F

/* Function to write a character to video memory */
void putchar(char c, int x, int y) {
    unsigned char *video_memory = (unsigned char*)VIDEO_MEMORY;
    int offset = (y * 80 + x) * 2;
    video_memory[offset] = c;
    video_memory[offset + 1] = COLOR;
}

/* Function to clear the screen */
void clear_screen() {
    unsigned char *video_memory = (unsigned char*)VIDEO_MEMORY;
    for(int i = 0; i < 80 * 25 * 2; i += 2) {
        video_memory[i] = ' ';
        video_memory[i + 1] = COLOR;
    }
}

/* Function to print a string */
void print_string(const char *str, int x, int y) {
    int i = 0;
    while(str[i] != '\0') {
        putchar(str[i], x + i, y);
        i++;
    }
}

/* Main kernel function */
void kernel_main() {
    // Clear screen
    
    clear_screen();
    // Display a welcome message
    print_string("NOX OS Kernel Loaded Successfully!", 20, 10);
    print_string("Welcome to the dark side...", 25, 12);
    
    // Halt the CPU
    while(1) {
        __asm__ volatile("hlt");
    }
}

linker.ld

ENTRY(_start)

SECTIONS {
    . = 0x1000;    /* Match the address in the bootloader */
    
    .text : {
        *(.text)
    }
    
    .data : {
        *(.data)
    }
    
    .bss : {
        *(.bss)
    }
}

boot.asm

; boot.asm - A simple bootloader
[bits 16]
[ 0x7c00]

; Set up segments
cli
mov ax, 0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
sti

; Display a message
mov si, boot_message
call print_string

; Load the kernel from disk
mov ah, 0x02    ; BIOS read sector function
mov al, 15      ; Number of sectors to read (adjust as needed)
mov ch, 0       ; Cylinder number
mov cl, 2       ; Sector number (sectors start from 1, bootloader is at 1)
mov dh, 0       ; Head number
mov dl, 0       ; Drive number (0 = floppy disk)
mov bx, 0x1000  ; Memory location to load the kernel
int 0x13        ; Call BIOS interrupt
jc disk_error   ; Jump if error (carry flag set)

; Switch to protected mode
cli                    ; Disable interrupts
lgdt [gdt_descriptor]  ; Load GDT

; Set protected mode bit
mov eax, cr0
or eax, 0x1
mov cr0, eax

; Far jump to 32-bit code
jmp CODE_SEG:protected_mode_entry

disk_error:
    mov si, disk_error_msg
    call print_string
    jmp hang

; Infinite loop for when we're done
hang:
    jmp hang

; Print string routine
print_string:
    lodsb
    or al, al
    jz done
    mov ah, 0x0E
    int 0x10
    jmp print_string
done:
    ret

; Global Descriptor Table
gdt_start:
    ; Null descriptor
    dd 0x0
    dd 0x0
    
    ; Code segment descriptor
    dw 0xffff    ; Limit (bits 0-15)
    dw 0x0000    ; Base (bits 0-15)
    db 0x00      ; Base (bits 16-23)
    db 10011010b ; Access byte
    db 11001111b ; Flags and Limit (bits 16-19)
    db 0x0       ; Base (bits 24-31)
    
    ; Data segment descriptor
    dw 0xffff    ; Limit 
    dw 0x0000    ; Base (bits 0-15)
    db 0x00      ; Base (bits 16-23)
    db 10010010b ; Access byte
    db 11001111b ; Flags and Limit
    db 0x0       ; Base (bits 24-31)
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1  ; GDT size
    dd gdt_start                ; GDT address

; Constants
CODE_SEG equ 0x08
DATA_SEG equ 0x10

; Messages
boot_message db 'NOX OS Booting...', 0
disk_error_msg db 'Error loading kernel!', 0

[bits 32]
protected_mode_entry:
    ; Set up segment registers for protected mode
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    
    ; Set up a stack
    mov esp, 0x90000
    
    ; Far jump to the kernel using a code segment selector
    jmp CODE_SEG:0x1000

; Padding and boot signature
times 510-($-$$) db 0
dw 0xAA55

entry.asm

; entry.asm - Assembly entry point that calls our C kernel
[bits 32]
[global _start]
[extern kernel_main]  ; Make sure this matches your C function name

section .text
_start:
    ; We're already in protected mode, skip trying to use BIOS interrupts
    
    ; Show a debug character at position 3
    mov byte [0xB8004], 'E'
    mov byte [0xB8005], 0x07
    
    ; Set up kernel stack
    mov esp, kernel_stack_top
    
    ; Call the C kernel main function
    call kernel_main
    
    ; Kernel should never return, but if it does:
    cli                 ; Disable interrupts
    hlt                 ; Halt the CPU
    jmp $               ; Infinite loop
    
; Reserve space for the kernel stack
section .bss
align 16
kernel_stack_bottom:
    resb 16384  ; 16 KB for kernel stack
kernel_stack_top:

Makefile

# Compiler settings
CC = x86_64-elf-gcc
CFLAGS = -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs -Wall -Wextra -c
ASM = nasm
ASMFLAGS = -f elf32
LD = x86_64-elf-ld
LDFLAGS = -T src/kernel/linker.ld -m elf_i386

# Directories
SRC_DIR = src
BUILD_DIR = build

# Files
BOOT_SRC = $(SRC_DIR)/boot/boot.asm
KERNEL_ENTRY = $(SRC_DIR)/kernel/entry.asm
KERNEL_SRC = $(SRC_DIR)/kernel/kernel.c
BOOT_BIN = $(BUILD_DIR)/boot.bin
KERNEL_OBJ = $(BUILD_DIR)/kernel.o
ENTRY_OBJ = $(BUILD_DIR)/entry.o
KERNEL_BIN = $(BUILD_DIR)/kernel.bin
OS_IMAGE = $(BUILD_DIR)/nox-os.img

# Build rules
all: $(OS_IMAGE)

$(BOOT_BIN): $(BOOT_SRC)
    $(ASM) -f bin $< -o $@

$(ENTRY_OBJ): $(KERNEL_ENTRY)
    $(ASM) $(ASMFLAGS) $< -o $@

$(KERNEL_OBJ): $(KERNEL_SRC)
    $(CC) $(CFLAGS) $< -o $@

$(KERNEL_BIN): $(ENTRY_OBJ) $(KERNEL_OBJ)
    $(LD) $(LDFLAGS) -o $@ $^

$(OS_IMAGE): $(BOOT_BIN) $(KERNEL_BIN)
    dd if=/dev/zero of=$@ bs=512 count=2880
    dd if=$(BOOT_BIN) of=$@ conv=notrunc
    # Ensure the kernel is properly aligned at sector 2
    dd if=$(KERNEL_BIN) of=$(OS_IMAGE) seek=1 conv=notrunc bs=512

run: $(OS_IMAGE)
    qemu-system-i386 -fda $(OS_IMAGE) -boot a -monitor stdio -d int -no-reboot

clean:
    rm -rf $(BUILD_DIR)/*
    mkdir -p $(BUILD_DIR)

I tried increasing the number of sectors read in boot.asm, but that didn't work either.

Share Improve this question edited Mar 29 at 20:57 Sep Roland 39.9k10 gold badges48 silver badges88 bronze badges asked Mar 24 at 4:30 James TanJames Tan 575 bronze badges 1
  • Update: It's still not working, but printchar works perfectly. – James Tan Commented Mar 24 at 6:32
Add a comment  | 

1 Answer 1

Reset to default 4

In your Makefile you have:

LDFLAGS = -T src/kernel/linker.ld -m elf_i386
KERNEL_BIN = $(BUILD_DIR)/kernel.bin
[snip]
$(KERNEL_BIN): $(ENTRY_OBJ) $(KERNEL_OBJ)
    $(LD) $(LDFLAGS) -o $@ $^
[snip]

Your linker script doesn't have an OUTPUT_FORMAT directive so the default will be an ELF file, not a binary file. The Makefile is creating an ELF file called kernel.bin. ELF files contain metadata and a header in them and as a result they can't be treated as a raw binary.

The simplest fix is to change linker.ld by adding this directive at the top:

OUTPUT_FORMAT(binary)

The previous method should resolve your problem, but it isn't a method I recommend. An ELF file can be very valuable when debugging code with GDB while connected to QEMU if it contains debug information.

I would generate a file called kernel.elf and then use OBJCOPY to convert that ELF file to a file called kernel.bin. You can change your Makefile to look something like:

# Compiler settings
CC = x86_64-elf-gcc
CFLAGS = -g -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs -Wall -Wextra -c
ASM = nasm
ASMFLAGS = -gdwarf -f elf32
LD = x86_64-elf-ld
LDFLAGS = -T src/kernel/linker.ld -m elf_i386
OBJCOPY = x86_64-elf-objcopy

# Directories
SRC_DIR = src
BUILD_DIR = build

# Files
BOOT_SRC = $(SRC_DIR)/boot/boot.asm
KERNEL_ENTRY = $(SRC_DIR)/kernel/entry.asm
KERNEL_SRC = $(SRC_DIR)/kernel/kernel.c
BOOT_BIN = $(BUILD_DIR)/boot.bin
KERNEL_OBJ = $(BUILD_DIR)/kernel.o
ENTRY_OBJ = $(BUILD_DIR)/entry.o
KERNEL_BIN = $(BUILD_DIR)/kernel.bin
KERNEL_ELF = $(BUILD_DIR)/kernel.elf
OS_IMAGE = $(BUILD_DIR)/nox-os.img

# Build rules
all: $(OS_IMAGE)

$(BOOT_BIN): $(BOOT_SRC)
        $(ASM) -f bin $< -o $@

$(ENTRY_OBJ): $(KERNEL_ENTRY)
        $(ASM) $(ASMFLAGS) $< -o $@

$(KERNEL_OBJ): $(KERNEL_SRC)
        $(CC) $(CFLAGS) $< -o $@

$(KERNEL_ELF): $(ENTRY_OBJ) $(KERNEL_OBJ)
        $(LD) $(LDFLAGS) -o $@ $^

$(KERNEL_BIN): $(KERNEL_ELF)
        $(OBJCOPY) -O binary $^ $@

$(OS_IMAGE): $(BOOT_BIN) $(KERNEL_BIN)
        dd if=/dev/zero of=$@ bs=512 count=2880
        dd if=$(BOOT_BIN) of=$@ conv=notrunc
        # Ensure the kernel is properly aligned at sector 2
        dd if=$(KERNEL_BIN) of=$(OS_IMAGE) seek=1 conv=notrunc bs=512

run: $(OS_IMAGE)
        qemu-system-i386 -fda $(OS_IMAGE) -boot a -monitor stdio -d int -no-reboot

clean:
        rm -rf $(BUILD_DIR)/*
        mkdir -p $(BUILD_DIR)

I've enabled debug info by passing -g to GCC and -gdwarf to NASM. You can use OBJDUMP to output your source code and the generated assembly as well as the section layout with x86_64-elf-objdump -DxS build/kernel.elf >objdump.txt.

With a kernel.elf file you can use GDB's symbolic debugger to connect to QEMU with a script like:

#!/bin/sh

hdd=kernel/build/image.hdd

qemu-system-i386 \
    -fda build/nox-os.img \
    -d int \
    -no-reboot \
    -no-shutdown -S -s &

QEMU_PID=$!

#        -ex 'layout src' \
#        -ex 'layout regs' \

gdb build/kernel.elf \
        -ex 'target remote localhost:1234' \
        -ex 'break kernel_main' \
        -ex 'continue'

ps --pid $QEMU_PID > /dev/null
if [ "$?" -eq 0 ]; then
    kill -9 $QEMU_PID
fi

stty sane

本文标签: cCan39t get welcome messages to load in QEMUStack Overflow