From 62fe09d80d3979105406ba25a33a52799a4387cd Mon Sep 17 00:00:00 2001 From: vmttmv Date: Thu, 13 Nov 2025 23:35:54 +0200 Subject: [PATCH] multiple changes: BL1/BL2/kernel separation (build system, etc.) BL2 implementation. BL documentation --- Makefile | 82 ++++++------ bootloader/README.md | 27 ++++ bootloader/boot1.asm | 194 ---------------------------- bootloader/{boot.asm => stage1.asm} | 93 ++++++------- bootloader/stage2.asm | 98 ++++++++++++++ bootloader/stage2.ld | 14 ++ bootloader/stage2_load.c | 118 +++++++++++++++++ gdb.txt | 6 + 8 files changed, 348 insertions(+), 284 deletions(-) create mode 100644 bootloader/README.md delete mode 100644 bootloader/boot1.asm rename bootloader/{boot.asm => stage1.asm} (74%) create mode 100644 bootloader/stage2.asm create mode 100644 bootloader/stage2.ld create mode 100644 bootloader/stage2_load.c create mode 100644 gdb.txt diff --git a/Makefile b/Makefile index 597a9f1..ba67947 100644 --- a/Makefile +++ b/Makefile @@ -1,64 +1,58 @@ AS = nasm +ASFLAGS = -f elf32 -g -F dwarf CC = gcc -CFLAGS = -std=c11 -m32 -ffreestanding -c -fno-stack-protector -fno-pie LD = ld -QEMU = qemu-system-i386 -IMG_SIZE = 1440k +QEMU= qemu-system-i386 BUILD_DIR = build - -BOOT_SRC = bootloader/boot.asm -BOOT_OBJ = $(BUILD_DIR)/boot.o -BOOT_ELF = $(BUILD_DIR)/boot.elf -BOOT_IMG = $(BUILD_DIR)/boot.img +DISK_IMG = $(BUILD_DIR)/disk.img +STAGE2_SIZE = 2048 KERNEL_C_SRC = $(wildcard kernel/*.c) KERNEL_ASM_SRC = $(wildcard kernel/*.asm) KERNEL_OBJ = $(patsubst kernel/%.c, $(BUILD_DIR)/%.o, $(KERNEL_C_SRC)) KERNEL_OBJ += $(patsubst kernel/%.asm, $(BUILD_DIR)/asm_%.o, $(KERNEL_ASM_SRC)) -KERNEL_OBJ += $(BUILD_DIR)/boot1.o -KERNEL_ELF = $(BUILD_DIR)/kernel.elf -KERNEL_BIN = $(BUILD_DIR)/kernel.bin -DISK_IMG = $(BUILD_DIR)/disk.img +all: $(DISK_IMG) -all: $(BOOT_IMG) $(KERNEL_BIN) $(DISK_IMG) +.PHONY: stage1 stage2 kernel run gdb clean +stage1: $(BUILD_DIR) + $(AS) $(ASFLAGS) -o $(BUILD_DIR)/$@.o bootloader/$@.asm + $(LD) -Ttext=0x7c00 -melf_i386 -o $(BUILD_DIR)/$@.elf $(BUILD_DIR)/$@.o + objcopy -O binary $(BUILD_DIR)/$@.elf $(BUILD_DIR)/$@.bin + +# NOTE: Stage2 final size should be checked against `$(STAGE2_SIZE)` by the build system to avoid an overflow. +# Alternatively, convey the final stage2 size through other means to stage1. +stage2: $(BUILD_DIR) + $(AS) $(ASFLAGS) -o $(BUILD_DIR)/stage2.o bootloader/stage2.asm + $(CC) -std=c11 -ffreestanding -nostdlib -fno-stack-protector -m32 -g -c -o $(BUILD_DIR)/stage2_load.o bootloader/stage2_load.c + $(LD) -Tbootloader/stage2.ld -melf_i386 -o $(BUILD_DIR)/$@.elf $(BUILD_DIR)/stage2.o $(BUILD_DIR)/stage2_load.o + objcopy -O binary $(BUILD_DIR)/$@.elf $(BUILD_DIR)/$@.bin + truncate -s $(STAGE2_SIZE) $(BUILD_DIR)/$@.bin + +$(BUILD_DIR)/asm_%.o: kernel/%.asm + $(AS) $(ASFLAGS) -o $@ $< + +$(BUILD_DIR)/%.o: kernel/%.c + $(CC) -std=c11 -ffreestanding -nostdlib -fno-stack-protector -m32 -g -c -o $@ $< + +kernel: $(KERNEL_OBJ) | $(BUILD_DIR) + $(LD) -melf_i386 -Tbootloader/linker.ld -o $(BUILD_DIR)/kernel.elf $(KERNEL_OBJ) + +$(DISK_IMG): stage1 stage2 kernel + dd if=$(BUILD_DIR)/stage1.bin of=$@ + dd if=$(BUILD_DIR)/stage2.bin of=$@ oflag=append conv=notrunc + dd if=$(BUILD_DIR)/kernel.elf of=$@ oflag=append conv=notrunc + truncate -s 1M $@ $(BUILD_DIR): mkdir -p $@ -$(BOOT_OBJ): $(BOOT_SRC) | $(BUILD_DIR) - $(AS) -f elf32 -g -F dwarf -o $@ $< +run: + qemu-system-i386 -s -S $(DISK_IMG) -$(BOOT_ELF): $(BOOT_OBJ) - $(LD) -Ttext=0x7c00 -melf_i386 -o $@ $< - -$(BOOT_IMG): $(BOOT_ELF) - objcopy -O binary $< $@ - truncate -s $(IMG_SIZE) $@ - -$(BUILD_DIR)/boot1.o: bootloader/boot1.asm - $(AS) -f elf32 -o $@ $< - -$(BUILD_DIR)/asm_%.o: kernel/%.asm - $(AS) -f elf32 -o $@ $< - -$(BUILD_DIR)/%.o: kernel/%.c - $(CC) $(CFLAGS) $< -o $@ - -$(KERNEL_BIN): $(KERNEL_OBJ) | $(BUILD_DIR) - $(LD) -melf_i386 --oformat binary -T bootloader/linker.ld -o $@ $(KERNEL_OBJ) - -$(DISK_IMG): $(BOOT_IMG) $(KERNEL_BIN) - dd if=$(BOOT_IMG) of=$@ bs=512 seek=4 - dd if=$(KERNEL_BIN) of=$@ bs=512 seek=200 - -run: $(DISK_IMG) - $(QEMU) -drive file=$<,format=raw,if=floppy - -.PHONY: stage1 clean - -stage1: $(BOOT_IMG) +gdb: + gdb -x gdb.txt clean: rm -rf $(BUILD_DIR) diff --git a/bootloader/README.md b/bootloader/README.md new file mode 100644 index 0000000..cf41b9b --- /dev/null +++ b/bootloader/README.md @@ -0,0 +1,27 @@ +# ClassicOS 2-stage bootloader + +Bootloader documentation for ClassicOS + +## Disk image organization: + +``` +[ 512 B ] [ 2048 B ] [ Unspecified ] + Stage 1 Stage 2 Kernel +``` + +## Stage 1 (`stage1.asm`) + +Responsible for loading the second stage using BIOS routines, and switching to protected mode. + +- Queries CHS parameters from BIOS +- Loads the second stage bootloader (2048 B) to `07c00h` +- Sets up a GDT with descriptor entries for code and data both covering the whole 32-bit address space +- Enables A20 +- Set CR0.PE (enable protected mode) and jump to stage 2 + +## Stage 2 (`stage2.asm, stage2_load.c`) + +- Set up segment registers +- Load the kernel ELF header +- Parse the program headers, and load all `PT_LOAD` segments from disk +- Jump to the kernel entry diff --git a/bootloader/boot1.asm b/bootloader/boot1.asm deleted file mode 100644 index 46f62f6..0000000 --- a/bootloader/boot1.asm +++ /dev/null @@ -1,194 +0,0 @@ -; ============================================================================== -; boot1.asm - Second Stage Bootloader (Fixed Real Mode Transition) -; ============================================================================== - -[BITS 32] -global _start -extern kmain - -_start: - ; Set up segments - mov ax, 0x10 - mov ds, ax - mov es, ax - mov fs, ax - mov gs, ax - mov ss, ax - - ; Stack (must be identity-mapped) - mov esp, 0x90000 - - ; CPU Feature Detection: check CPUID support - pushfd ; Save flags - pop eax - mov ecx, eax - xor eax, 1 << 21 ; Flip ID bit - push eax - popfd - pushfd - pop eax - xor eax, ecx - jz .no_cpuid ; CPUID unsupported if no change - - ; CPUID supported, verify features - mov eax, 1 - cpuid - ; Check for paging support (bit 31 of edx) - test edx, 1 << 31 - jz .no_paging_support - - ; Additional CPU feature checks could be added here - - jmp .cpuid_check_done - -.no_cpuid: - mov si, no_cpuid_msg - call print_string_16 - jmp halt - -.no_paging_support: - mov si, no_paging_msg - call print_string_16 - jmp halt - -.cpuid_check_done: - - ; Temporarily switch back to real mode - cli - mov eax, cr0 - and eax, 0x7FFFFFFE ; Clear PE & PG bits - mov cr0, eax - jmp 0x18:real_mode_entry - -; ---------------------------------------------------------------- -[BITS 16] -real_mode_entry: - ; Real mode for BIOS access (E820, VESA) - xor ax, ax - mov es, ax - - ; VESA call - mov di, VbeControllerInfo - mov ax, 0x4F00 - int 0x10 - jc vesa_error - - ; E820 memory map - xor ebx, ebx - mov edx, 0x534D4150 - mov di, MemoryMapBuffer - mov [MemoryMapEntries], dword 0 - -.e820_loop: - mov eax, 0xE820 - mov ecx, 24 - int 0x15 - jc e820_error - add di, 24 - inc dword [MemoryMapEntries] - test ebx, ebx - jnz .e820_loop - jmp e820_done - -e820_error: - mov si, e820_error_msg - call print_string_16 - jmp halt - -vesa_error: - mov si, vesa_error_msg - call print_string_16 - - ; Fallback: set VGA text mode 3 and continue - mov ah, 0x00 ; BIOS Set Video Mode function - mov al, 0x03 ; VGA 80x25 text mode - int 0x10 - - ; Clear screen - mov ah, 0x06 ; Scroll up function - mov al, 0 ; Clear entire screen - mov bh, 0x07 ; Text attribute (gray on black) - mov cx, 0 ; Upper-left corner - mov dx, 0x184F ; Lower-right corner - int 0x10 - - jmp e820_done ; Continue booting without VESA graphics - -e820_done: - ; Back to protected mode - cli - mov eax, cr0 - or eax, 1 - mov cr0, eax - jmp 0x08:protected_entry - -; ---------------------------------------------------------------- -; removed duplicate print from here -; suggested calling it from boot.asm - -e820_error_msg db "E820 Failed!", 0 -vesa_error_msg db "VESA Failed!", 0 -no_cpuid_msg db "No CPUID support detected!", 0 -no_paging_msg db "CPU lacks paging support!", 0 - -; ---------------------------------------------------------------- -[BITS 32] -protected_entry: - ; Paging setup - xor eax, eax - mov edi, page_directory - mov ecx, 1024 - rep stosd - mov edi, page_table - rep stosd - mov eax, page_table - or eax, 0x3 - mov [page_directory], eax - mov ecx, 1024 - mov edi, page_table - mov eax, 0x00000003 -.fill_pages: - mov [edi], eax - add eax, 0x1000 - add edi, 4 - loop .fill_pages - - mov eax, page_directory - mov cr3, eax - mov eax, cr0 - or eax, 0x80000000 - mov cr0, eax - - jmp kmain - -halt: - cli - -.hang: - hlt - jmp .hang - -; ---------------------------------------------------------------- -; Data buffers and variables must be appropriately defined in your data section -MemoryMapBuffer times 128 db 0 ; 128*24 bytes reserved for E820 memory map (adjust size as needed) -MemoryMapEntries dd 0 -VbeControllerInfo times 512 db 0 ; Buffer for VESA controller info (adjust size as needed) - -; Define page directory and page table aligned as needed (in your data section) -align 4096 -page_directory times 1024 dd 0 -align 4096 -page_table times 1024 dd 0 - -%assign pad_size 4096 -%ifdef __SIZE__ - %define size_current __SIZE__ -%else - %define size_current ($ - $$) -%endif - -%if size_current < pad_size - times pad_size - size_current db 0 -%endif - -checksum_byte db 0 diff --git a/bootloader/boot.asm b/bootloader/stage1.asm similarity index 74% rename from bootloader/boot.asm rename to bootloader/stage1.asm index af7ec11..949d30b 100644 --- a/bootloader/boot.asm +++ b/bootloader/stage1.asm @@ -2,10 +2,16 @@ ; boot.asm - First Stage Bootloader (CHS Based) ; ============================================================================== -[BITS 16] -; [ORG 0x7C00] +; Params for stage2 +%define s2_addr 1 ; stage2 disk offset, in sectors +%define s2_laddr 0x7e00 ; stage2 load address +%define s2_size 2048 ; stage2 size +%define s2_nsect s2_size / 512 ; stage2 size in sectors -start: +[BITS 16] +global _start + +_start: cli ; Disable interrupts mov [bootdev], dl ; Save boot device number (from BIOS in DL) @@ -20,29 +26,16 @@ start: mov ds, ax mov es, ax + ; Query bios for disk parameters + call get_disk_params + ; Load second-stage bootloader (boot1.asm) to 0x7E00 mov ax, 1 ; LBA of boot1.asm (starts at sector 1) call lba_to_chs - mov al, 4 ; Number of sectors to read + mov al, s2_nsect ; Number of sectors to read mov bx, 0x7E00 ; Destination address offset ES = 0 (0x0000:0x7E00) call read_chs - ; Load kernel to 0x100000 (1 MB) - mov ax, 0xffff - mov es, ax ; Set ES for destination address (0xffff << 4 == 0xffff0) - mov ax, 5 ; LBA of kernel start (boot1 is 4 sectors: LBA 1–4 → kernel at LBA 5) - call lba_to_chs - mov al, 16 ; Number of sectors for kernel - mov bx, 0x10 ; Destination address offset (0xffff:0x10 = 0xffff << 4 + 0x10 = 0x100000) - call read_chs - jc disk_error - - ; Memory Validation: Verify checksum of second stage bootloader - mov si, 0x7E00 ; Start of second stage - mov cx, 512 * 4 ; Size in bytes (adjust if more sectors loaded) - call verify_checksum - jc disk_error ; Jump if checksum fails - ; Enable A20 line call enable_a20 jc a20_error ; Jump if A20 enable fails @@ -95,17 +88,32 @@ verify_checksum: pop ax ret +get_disk_params: + mov ah, 08h ; BIOS: Get Drive Parameters + int 13h + ; TODO: error checking + + ; CL bits 0–5 contain sectors per track + mov al, cl + and al, 3Fh ; mask bits 0–5 + mov ah, 0 + mov [sectors_per_track], ax + + ; DH = maximum head number (0-based) + mov al, dh + inc ax ; convert to count (heads = maxhead + 1) + mov [heads_per_cylinder], ax + ret + ; ---------------------------------------------------------------- ; CHS Disk Read Routine ; AL = number of sectors ; CL = starting sector (1-based) -; SI = destination offset (Segment:ES already set) ; Inputs: ; AL = sector count ; CH = cylinder ; DH = head ; CL = sector (1–63, with top 2 bits as high cylinder bits) -; SI = destination offset (segment ES must be set) ; ---------------------------------------------------------------- ; Convert LBA to CHS @@ -117,36 +125,29 @@ verify_checksum: ; CL = sector (1-63, top 2 bits are upper cylinder bits) lba_to_chs: - pusha - + ; Sector xor dx, dx - mov bx, [sectors_per_track] - div bx ; AX = LBA / sectors_per_track, DX = remainder (sector number) - mov si, ax ; SI = temp quotient (track index) - mov cx, [heads_per_cylinder] - xor dx, dx - div cx ; AX = cylinder, DX = head - mov ch, al ; CH = cylinder low byte - mov dh, dl ; DH = head + mov bx, ax + div word [sectors_per_track] ; divide lba with max sectors + add dl, 1 ; take the remainder, sectors start at 1 + mov cl, dl ; sector is in cl - ; Now take sector number from earlier remainder - mov cx, si ; Copy track index to CX to access CL - and cl, 0x3F ; Mask to 6 bits (sector number) - inc cl ; Sector numbers are 1-based + ; Head + mov ax, bx + mov dx, 0 + div word [sectors_per_track] ; divide lba with max sectors + mov dx, 0 + div word [heads_per_cylinder] ; divide quotient with heads + mov dh, dl ; take the remainder, head is in dh - ; Insert upper 2 bits of cylinder into CL - mov ah, al ; AH = cylinder again - and ah, 0xC0 ; Get top 2 bits of cylinder - or cl, ah ; OR them into sector byte - - popa + ; Cylinder + mov ch, al ; take the quotient, cylinder is in ch ret read_chs: pusha push dx - mov cx, 5 .retry: mov ah, 0x02 ; BIOS: Read sectors mov dl, [bootdev] ; Boot device @@ -262,7 +263,7 @@ switch_to_pm: mov eax, cr0 or eax, 1 mov cr0, eax - jmp 0x08:0x7E00 + jmp 0x08:0x7E00 ; jump to S2 ; ---------------------------------------------------------------- print_string_16: @@ -284,8 +285,8 @@ halt: hlt bootdev db 0 -sectors_per_track dw 63 -heads_per_cylinder dw 255 +sectors_per_track dw 0 +heads_per_cylinder dw 0 times 510 - ($ - $$) db 0 dw 0xAA55 diff --git a/bootloader/stage2.asm b/bootloader/stage2.asm new file mode 100644 index 0000000..02d9077 --- /dev/null +++ b/bootloader/stage2.asm @@ -0,0 +1,98 @@ +[BITS 32] +global _start +global ata_lba_read + +extern load_kernel + +_start: + ; Set up segments + ; Data segments + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ; Code segment + mov ax, 0x08 + mov cs, ax + + ; Stack (must be identity-mapped) + mov esp, 0x90000 + + call load_kernel + + jmp eax + +; ---------------------------------------------------------------------------- +; ATA read sectors (LBA mode) +; +; sysv32 abi signature: +; void ata_lba_read(uint32_t lba, uint8_t nsect, void *addr); +; ---------------------------------------------------------------------------- +ata_lba_read: + push ebp + mov ebp, esp + + push ebx + push ecx + push edx + push edi + + mov eax, [ebp+8] ; arg #1 = LBA + mov cl, [ebp+12] ; arg #2 = # of sectors + mov edi, [ebp+16] ; arg #3 = buffer address + and eax, 0x0FFFFFFF + + mov ebx, eax ; Save LBA in RBX + + mov edx, 0x01F6 ; Port to send drive and bit 24 - 27 of LBA + shr eax, 24 ; Get bit 24 - 27 in al + or al, 11100000b ; Set bit 6 in al for LBA mode + out dx, al + + mov edx, 0x01F2 ; Port to send number of sectors + mov al, cl ; Get number of sectors from CL + out dx, al + + mov edx, 0x1F3 ; Port to send bit 0 - 7 of LBA + mov eax, ebx ; Get LBA from EBX + out dx, al + + mov edx, 0x1F4 ; Port to send bit 8 - 15 of LBA + mov eax, ebx ; Get LBA from EBX + shr eax, 8 ; Get bit 8 - 15 in AL + out dx, al + + mov edx, 0x1F5 ; Port to send bit 16 - 23 of LBA + mov eax, ebx ; Get LBA from EBX + shr eax, 16 ; Get bit 16 - 23 in AL + out dx, al + + mov edx, 0x1F7 ; Command port + mov al, 0x20 ; Read with retry. + out dx, al + + mov bl, cl ; Save # of sectors in BL + +.wait_drq: + mov edx, 0x1F7 +.do_wait_drq: + in al, dx + test al, 8 ; the sector buffer requires servicing. + jz .do_wait_drq ; keep polling until the sector buffer is ready. + + mov edx, 0x1F0 ; Data port, in and out + mov ecx, 256 + rep insw ; in to [RDI] + + dec bl ; are we... + jnz .wait_drq ; ...done? + + pop edi + pop edx + pop ecx + pop ebx + pop ebp + ret diff --git a/bootloader/stage2.ld b/bootloader/stage2.ld new file mode 100644 index 0000000..48b7953 --- /dev/null +++ b/bootloader/stage2.ld @@ -0,0 +1,14 @@ +SECTIONS { + . = 0x7e00; + + .text : { *(.text*) } + .rodata : { *(.rodata*) } + .data : { *(.data*) } + + .bss : { + *(.bss*) + *(COMMON) + } + + read_buf = .; +} diff --git a/bootloader/stage2_load.c b/bootloader/stage2_load.c new file mode 100644 index 0000000..83dd973 --- /dev/null +++ b/bootloader/stage2_load.c @@ -0,0 +1,118 @@ +#include + +// ELF Ident indexes +#define EI_NIDENT 16 + +// Program header types +#define PT_NULL 0 +#define PT_LOAD 1 + +// ELF Header (32-bit) +typedef struct { + uint8_t e_ident[EI_NIDENT]; + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint32_t e_entry; // Entry point + uint32_t e_phoff; // Program header table offset + uint32_t e_shoff; // Section header table offset + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +} __attribute__((packed)) Elf32_Ehdr; + +// Program Header (32-bit) +typedef struct { + uint32_t p_type; + uint32_t p_offset; + uint32_t p_vaddr; + uint32_t p_paddr; + uint32_t p_filesz; + uint32_t p_memsz; + uint32_t p_flags; + uint32_t p_align; +} __attribute__((packed)) Elf32_Phdr; + +// Load an ELF executable into memory. +static int elf_load(const void* data, void (*load_segment)(uint8_t *vaddr, uint32_t src, uint32_t size)) { + const Elf32_Ehdr* header = (const Elf32_Ehdr*)data; + const Elf32_Phdr* ph = (const Elf32_Phdr*)((uint8_t*)data + header->e_phoff); + + for (int i = 0; i < header->e_phnum; i++) { + if (ph[i].p_type != PT_LOAD) + continue; + + uint32_t offset = ph[i].p_offset; + uint32_t vaddr = ph[i].p_vaddr; + uint32_t filesz = ph[i].p_filesz; + uint32_t memsz = ph[i].p_memsz; + + // Copy data segment + //load_segment((uint8_t *)vaddr, offset, filesz); + load_segment((uint8_t *)vaddr, offset, filesz); + + // Zero remaining BSS (if any) + if (memsz > filesz) { + uint8_t* bss_start = (uint8_t*)(vaddr + filesz); + for (uint32_t j = 0; j < memsz - filesz; j++) { + bss_start[j] = 0; + } + } + } + + return header->e_entry; +} + +#define KERN_START_SECT 5 +#define MAX(a, b) ((a)>(b) ? (a) : (b)) + +extern void ata_lba_read(uint32_t lba, uint8_t nsect, void *addr); +extern uint8_t read_buf[]; + +static uint32_t +total_header_size(const Elf32_Ehdr *header) { + uint32_t phend = header->e_phoff + header->e_phentsize*header->e_phnum; + + // Align to 512 + return (phend + 511) & ~511; +} + +static void read_sectors(uint8_t *vaddr, uint32_t offset, uint32_t size) { + // # of sectors to read + uint32_t rem_nsect = ((size + 511) & ~511) / 512; + + // Current lba address, offset by the first sector already read + uint32_t lba = KERN_START_SECT + offset / 512; + + // Max 255 sectors at a time + while (rem_nsect) { + uint8_t nsect = rem_nsect > 255 ? 255 : rem_nsect; + ata_lba_read(lba, nsect, vaddr); + + vaddr += nsect * 512; + rem_nsect -= nsect; + lba += nsect; + } +} + +void *load_kernel(void) { + // Read the first sector + ata_lba_read(KERN_START_SECT, 1, read_buf); + + const Elf32_Ehdr* header = (const Elf32_Ehdr*)read_buf; + + // Remaining data size, subtract the first 512B already read + uint32_t rem = total_header_size(header) - 512; + + // Read the rest if necessary + if (rem) + read_sectors(read_buf+512, 512, rem); + + elf_load(read_buf, read_sectors); + + return (void *)header->e_entry; +} diff --git a/gdb.txt b/gdb.txt new file mode 100644 index 0000000..8adab56 --- /dev/null +++ b/gdb.txt @@ -0,0 +1,6 @@ +target remote :1234 +add-symbol-file build/stage1.elf +add-symbol-file build/stage2.elf +add-symbol-file build/kernel.elf +hbreak *0x7c00 +c