A bootloader is a small program that runs when a computer starts up, responsible for loading the operating system kernel into memory and transferring control to it. In the x86 architecture with traditional BIOS:
- BIOS (Basic Input/Output System): The firmware that initializes hardware and loads the bootloader
- Real Mode: The x86 processor starts in 16-bit real mode with access to only 1MB of memory
- Boot Sector: The first 512 bytes of a storage device that contains the bootloader
- Memory Layout: At boot, the BIOS loads the boot sector to memory address
0x7C00
- Power On → BIOS initializes hardware
- BIOS → Searches for bootable devices (checks for
0xAA55signature) - Load Boot Sector → BIOS loads 512 bytes to
0x7C00and jumps there - Bootloader → Sets up environment, loads kernel, transfers control
TrpLoader is a custom x86 BIOS bootloader written in assembly language. This project demonstrates low-level system programming and the boot process fundamentals.
TrpLoader/
├── Makefile # Build configuration
├── boot/
│ ├── boot.asm # Main bootloader assembly code
│ └── linker.ld # Linker script (currently empty)
├── build/
│ └── trpLoader.bin # Compiled bootloader binary
├── include/
│ └── boot.h # C headers for future stages
└── src/
└── main.c # C code for future stages
- Origin Setup:
[ORG 0x7C00]- Tells assembler our code starts at BIOS load address - Segment Registers: Initialized DS, ES, SS to 0 for consistent memory access
- Stack Setup: Positioned stack pointer at
0x8000(safe location below bootloader)
- BIOS Interrupts: Implemented text output using INT 10h (BIOS video services)
- String Printing: Created
print_stringfunction for displaying messages - Welcome Message: Displays "Welcome to TrpLoader!" on boot
- Boot Signature: Added mandatory
0xAA55at bytes 510-511 - Padding: Ensured exactly 512-byte boot sector size
- Infinite Loop: Prevents processor from executing garbage after bootloader
[ORG 0x7C00] ; Load address specified by BIOS
[BITS 16] ; 16-bit real mode
cli ; Disable interrupts during setup
xor ax, ax ; Zero out AX register
mov ds, ax ; Data segment = 0
mov es, ax ; Extra segment = 0
mov ss, ax ; Stack segment = 0
mov sp, 0x8000 ; Stack grows downward from 0x8000
sti ; Re-enable interrupts- NASM Assembler: Assembles
.asmfiles to raw binary - QEMU Integration:
make runlaunches bootloader in emulator - Clean Builds: Proper dependency management
Stage 2 will be implemented in C without standard library (libc) for the following advantages:
- Higher-level logic: Easier to implement complex algorithms (partition parsing, file systems)
- Type safety: Reduced bugs compared to assembly
- Maintainability: More readable and modular code
- No dependencies: Bare metal C with custom runtime
- Size control: No bloated standard library overhead
Stage 1 (ASM, 512 bytes) Stage 2 (C, ~32KB) Kernel
┌─────────────────┐ ┌─────────────────┐ ┌──────────┐
│ • Setup segments│ → │ • Parse MBR │ → │ OS Kernel│
│ • Load Stage 2 │ │ • Load kernel │ │ │
│ • Switch to C │ │ • Setup memory │ │ │
└─────────────────┘ └─────────────────┘ └──────────┘
BIOS loads at 0x7C00 Loads at 0x8000 Loads at 0x100000
0x00000 - 0x003FF Interrupt Vector Table
0x00400 - 0x004FF BIOS Data Area
0x00500 - 0x07BFF Free conventional memory
0x07C00 - 0x07DFF Stage 1 Bootloader (512 bytes)
0x08000 - 0x0FFFF Stage 2 Bootloader (32KB C code)
0x10000 - 0x9FFFF Free memory for kernel loading
0xA0000 - 0xFFFFF Video memory and BIOS ROM
- Assembly bridge: Transition from real mode ASM to C
- Custom runtime: Minimal C runtime without libc
- Memory management: Basic heap and stack management
- Build system: GCC cross-compilation setup
- BIOS interfaces: C wrappers for INT 13h, INT 10h
- Disk I/O library: LBA/CHS sector reading functions
- VGA text mode: Screen output and formatting
- Error handling: Robust error reporting system
- MBR parser: Read and analyze partition table
- FAT16/32 support: Basic file system reading
- Kernel loader: ELF or binary kernel loading
- Memory mapping: Set up kernel memory layout
- Protected mode: 32-bit mode transition
- A20 line: Enable >1MB memory access
- Kernel parameters: Pass system information
- Control transfer: Jump to kernel entry point
The MBR contains the partition table starting at offset 0x01BE:
Offset | Size | Description
-------|------|------------
0x01BE | 16 | Partition Entry 1
0x01CE | 16 | Partition Entry 2
0x01DE | 16 | Partition Entry 3
0x01EE | 16 | Partition Entry 4
0x01FE | 2 | Boot Signature (0xAA55)
Partition Entry Structure (16 bytes):
Offset | Size | Field
-------|------|------
0x00 | 1 | Boot Flag (0x80 = bootable)
0x01 | 3 | CHS Start Address
0x04 | 1 | Partition Type
0x05 | 3 | CHS End Address
0x08 | 4 | LBA Start Sector
0x0C | 4 | Number of Sectors
- Memory Constraints: Only 512 bytes for stage 1 bootloader
- Real Mode Limitations: 16-bit addressing, 1MB memory limit
- Disk Geometry: Handle different disk formats (CHS vs LBA)
- Error Recovery: Robust error handling for disk operations
- C Runtime: Building custom runtime without libc dependencies
- Cross-compilation: Targeting 16/32-bit x86 from modern systems
Update Makefile to support cross-compilation:
# Toolchain for 32-bit x86 target
CC = gcc
CFLAGS = -m32 -ffreestanding -nostdlib -nostartfiles -nodefaultlibs
CFLAGS += -fno-builtin -fno-stack-protector -fno-pic
CFLAGS += -Wall -Wextra -Werror -std=c99
LDFLAGS = -m32 -nostdlib -Ttext=0x8000
# Stage 2 C files
STAGE2_C = src/main.c src/disk.c src/mbr.c src/vga.c
STAGE2_OBJ = $(STAGE2_C:.c=.o)Enhanced boot.asm:
; Load stage 2 from disk
mov dl, 0x80 ; Drive number
mov ax, 1 ; Start from sector 1 (after MBR)
mov cx, 64 ; Load 64 sectors (32KB)
mov bx, 0x8000 ; Load address for stage 2
call load_sectors
; Jump to C code entry point
jmp 0x8000Create src/boot_c.asm for C entry:
[BITS 16]
global _start
extern main
_start:
; Set up stack for C code
mov sp, 0x7C00 ; Stack below bootloader
; Call C main function
call main
; Infinite loop if main returns
jmp $File Organization:
src/
├── main.c # Main bootloader logic
├── types.h # Custom type definitions
├── bios.h/.c # BIOS interrupt wrappers
├── disk.h/.c # Disk I/O operations
├── mbr.h/.c # MBR parsing functions
├── vga.h/.c # VGA text output
├── memory.h/.c # Memory management
└── kernel.h/.c # Kernel loading
types.h - Custom type system:
#ifndef TYPES_H
#define TYPES_H
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef signed char int8_t;
typedef signed short int16_t;
typedef signed int int32_t;
typedef uint8_t bool;
#define true 1
#define false 0
#define NULL ((void*)0)
// Size definitions
typedef uint32_t size_t;
#endifbios.h - Hardware abstraction:
#ifndef BIOS_H
#define BIOS_H
#include "types.h"
// BIOS interrupt wrappers
void bios_putchar(char c);
void bios_puts(const char* str);
bool bios_read_sectors(uint8_t drive, uint32_t lba,
uint16_t count, void* buffer);
#endifmbr.h - Partition table structures:
#ifndef MBR_H
#define MBR_H
#include "types.h"
typedef struct {
uint8_t boot_flag; // 0x80 = bootable
uint8_t chs_start[3]; // CHS start address
uint8_t type; // Partition type
uint8_t chs_end[3]; // CHS end address
uint32_t lba_start; // LBA start sector
uint32_t sector_count; // Number of sectors
} __attribute__((packed)) partition_entry_t;
typedef struct {
uint8_t boot_code[446]; // Boot code
partition_entry_t partitions[4]; // 4 partition entries
uint16_t signature; // 0xAA55
} __attribute__((packed)) mbr_t;
// Function prototypes
bool parse_mbr(void);
partition_entry_t* find_active_partition(void);
#endifBuild Process:
# Compile C source files
gcc $(CFLAGS) -c src/main.c -o src/main.o
gcc $(CFLAGS) -c src/disk.c -o src/disk.o
gcc $(CFLAGS) -c src/mbr.c -o src/mbr.o
# Link stage 2 binary
ld $(LDFLAGS) src/*.o -o build/stage2.bin
# Combine stage 1 + stage 2
cat build/boot.bin build/stage2.bin > build/trpLoader.imgTesting Strategy:
# Create test disk image
dd if=/dev/zero of=test_disk.img bs=1M count=10
dd if=build/trpLoader.img of=test_disk.img conv=notrunc
# Test in QEMU
qemu-system-i386 -drive format=raw,file=test_disk.img -nographic- A20 Gate: Enable access to >1MB memory
- GDT Setup: Global Descriptor Table for segments
- Mode Switch: 16-bit real mode → 32-bit protected mode
- FAT16/32: Read files from DOS partitions
- EXT2: Basic Linux file system support
- Custom: Simple bootloader-specific format
typedef enum {
BOOT_SUCCESS = 0,
BOOT_ERROR_DISK_READ,
BOOT_ERROR_NO_PARTITION,
BOOT_ERROR_INVALID_KERNEL,
BOOT_ERROR_MEMORY
} boot_error_t;make # Build bootloader
make run # Run in QEMU emulator
make clean # Clean build files- QEMU Monitor: Access with Ctrl+Alt+2 for debugging commands
- Memory Dumps: Use QEMU monitor to inspect memory contents
- Single Stepping: GDB integration for assembly debugging
- Bochs Emulator: Alternative with better debugging features
- Update Makefile for C cross-compilation support
- Create minimal C runtime without libc dependencies
- Implement BIOS wrappers for hardware access in C
- Build MBR parser in C for partition detection
- Add kernel loading with ELF support
- Test with real disk images and partitions
- NASM: Netwide Assembler for x86
- QEMU: System emulator for testing
- GDB: GNU Debugger for low-level debugging
- hexdump: For examining binary files
Current Status: Stage 1 Complete ✅
Next Target: C-based Stage 2 with MBR Parsing 🎯
Ready to continue the bootloader journey with C programming!