Chapter 28 Exercises: Bare Metal Programming

Section A: Boot Mechanics

1. Build and Boot the MinOS Bootloader Set up the QEMU environment and build the MinOS bootloader from the chapter:

# Install dependencies
sudo apt-get install nasm qemu-system-x86 gdb

# Build
nasm -f bin minOS_boot.asm -o boot.bin

# Create a minimal "kernel" that halts
echo -e '\xF4' > kernel.bin  # HLT instruction

# Combine and test
cat boot.bin kernel.bin > minOS.img
truncate -s 32768 minOS.img
qemu-system-x86_64 -drive format=raw,file=minOS.img

You should see "Booting MinOS..." followed by "Kernel loaded." on the QEMU screen.

2. Boot Signature Verification Write a Python or bash script that verifies a boot sector binary: - File must be at least 512 bytes - Bytes at offset 510–511 must be 0x55, 0xAA - Print the first 16 bytes as hex for inspection

If the signature is wrong, the BIOS will refuse to boot it. Make your script print a warning if the signature is missing, then fix the bootloader to include it.

3. Real Mode String Output In the real-mode portion of the bootloader, implement a function print_hex_byte that prints a single byte value as two hex digits using BIOS INT 0x10. Test it by printing the boot drive number (DL value at entry):

print_hex_byte:
    ; AL = byte to print
    ; Upper nibble
    mov ah, al
    shr ah, 4
    add ah, '0'
    cmp ah, '9'+1
    jl .ok1
    add ah, 'A'-'9'-1
.ok1:
    push ax
    mov al, ah
    mov ah, 0x0E
    int 0x10
    pop ax
    ; Lower nibble
    and al, 0x0F
    ; ... (complete this)

4. Read Multiple Disk Sectors Modify the bootloader's DAP to read 64 sectors (32KB) instead of 32. Verify by placing a recognizable pattern (e.g., the string "KERNEL LOADED" repeated) at sector 33 in the image and confirming the bootloader reads it into memory.

Test with GDB:

(gdb) x/s 0x8000 + 32*512   # check sector 33 content in memory

Section B: Mode Transitions

5. GDT Verification After entering 32-bit protected mode, add code that reads back the segment descriptor loaded for DS and verifies its DPL, type, and limit fields. Use SGDT to read the GDTR, then manually decode the descriptor: - Selector 0x10 (data) → index 2 in GDT - Read the 8-byte entry and decode: base, limit, G bit, D bit, P bit

Print the decoded values to VGA text mode to confirm the GDT was set up correctly.

6. Page Table Inspection After entering long mode but before calling the kernel, print (to VGA or serial) the contents of your minimal page tables: - PML4[0]: value in hex (should be ~0x2003) - PDP[0]: value (should be ~0x3003) - PD[0]: value (should be ~0x83)

Verify that the physical address encoded in PD[0] is 0 (identity map) and that the PS bit (bit 7) is set (2MB huge page).

7. Expanding the Identity Map The minimal bootloader maps only the first 2MB. Extend the page tables to map the first 4GB: - In the PD (at 0x3000), fill entries PD[0] through PD[511] — wait, that's 1GB - For 4GB: fill PDP[0] through PDP[3], each pointing to a different PD

Actually map a PDP[0..3], each with a PD, each PD covering 1GB with 2MB pages.

This allows the kernel to access memory above 2MB without page faults.

8. Handling Boot Errors Gracefully Improve the bootloader's error handling: - If INT 0x13 disk read fails, print the error code (AH = error, displayed as hex) - If A20 line fails to enable (check by writing to address 0x100000 and reading 0x000000 + 0x10 offset — if they aliased, A20 is off), print "A20 FAILED" - After any fatal error: print the error, try 3 retries if applicable, then print "System Halted" and loop on HLT


Section C: VGA Text Mode

9. VGA Driver Functions Implement a complete VGA text-mode driver for MinOS in long mode:

; vga_clear()              — clear screen, reset cursor
; vga_putchar(al)          — print character at cursor, advance cursor
; vga_puts(rdi)            — print null-terminated string
; vga_puthex64(rdi)        — print 64-bit value as hex (16 digits)
; vga_putuint(rdi)         — print 64-bit unsigned decimal
; vga_newline()            — move cursor to next line
; vga_scroll()             — scroll screen up by one line

The cursor position should be tracked in a global variable (row, column) and the vga_putchar function should call vga_scroll when it reaches line 25.

10. Color Text and Attributes Implement vga_set_color(foreground, background) that sets the current text attribute byte. Print the following color demonstration:

MinOS v0.1                    ← white on black
Initializing...               ← light gray on black
[OK] Memory map loaded        ← green on black for [OK]
[!!] No FPU detected          ← red on black for [!!]

Use VGA attributes: 0x07 = light gray, 0x0A = bright green, 0x0C = bright red, 0x0F = bright white.

11. Scrolling Implementation Implement VGA scrolling: when the cursor reaches line 25, move lines 1–24 up to lines 0–23, clear line 24, and reset the cursor to the beginning of line 24. Use MOVQ or REP MOVSB to copy the VGA buffer efficiently.

Benchmark: how many bytes must be copied for one scroll operation? How many cycles does it take? (The VGA buffer is at 0xB8000, 80×25×2 = 4000 bytes.)


Section D: MinOS Kernel Entry

12. Complete Kernel Entry Function Write the MinOS kernel entry point (kernel_main) in NASM that: 1. Calls vga_clear() 2. Prints "MinOS 0.1" at the top of the screen 3. Calls idt_init() (from Chapter 26) 4. Calls pmm_init() (from Chapter 27) with the E820 map 5. Calls sti to enable interrupts 6. Prints "System ready." and enters the main loop (HLT in a loop) 7. In the main loop: check the keyboard buffer (from Chapter 26 driver) and echo characters to VGA

13. E820 Memory Map Collection Add E820 memory map collection to the bootloader (in real mode, before mode switch):

; Collect E820 memory map to address 0x7E00 (just after boot sector)
; Store count at 0x7E00, entries starting at 0x7E04

e820_collect:
    mov di, 0x7E04      ; buffer for entries
    xor ebx, ebx        ; continuation value (0 = start)
    xor bp, bp          ; entry count

.next_entry:
    mov eax, 0xE820
    mov ecx, 20         ; entry size
    mov edx, 0x534D4150 ; 'SMAP' magic
    int 0x15
    jc .done            ; CF set = done or error
    cmp eax, 0x534D4150 ; verify signature
    jne .done

    ; Skip entries with length 0
    mov ecx, [di + 8]   ; length low 32 bits
    or  ecx, [di + 12]  ; length high 32 bits
    jz .skip_entry

    inc bp              ; increment count
    add di, 20          ; advance buffer pointer

.skip_entry:
    test ebx, ebx       ; EBX=0 means last entry
    jz .done
    jmp .next_entry

.done:
    mov [0x7E00], bp    ; store entry count
    ret

Pass the map address (0x7E04) and count (at 0x7E00) to pmm_init in the kernel.

14. Serial Port Debug Output Add early serial port output to the bootloader (in real mode) for debugging without a VGA display. BIOS INT 0x14 provides serial port access:

; Send character in AL to COM1 (port 0)
serial_putchar:
    push ax
    mov ah, 0x01    ; INT 14h / AH=1: Send character
    mov dx, 0       ; port 0 = COM1
    int 0x14        ; AH = status, bit 7 = error
    pop ax
    ret

Print all bootloader status messages to both VGA and serial. In QEMU, redirect serial output:

qemu-system-x86_64 -drive format=raw,file=minOS.img -serial stdio

Section E: UEFI Comparison

15. UEFI Boot Discovery On a UEFI-booting Linux system, examine the UEFI environment:

# List UEFI variables
ls /sys/firmware/efi/efivars/ | head -20

# View UEFI boot entries
efibootmgr -v

# Examine the EFI system partition
mount | grep efi
ls /boot/efi/EFI/

# View the actual UEFI application binary format
file /boot/efi/EFI/ubuntu/grubx64.efi
# Output: PE32+ executable (EFI application)  x86-64

16. Compare Boot Sequences Write a comparison table documenting the differences between BIOS/MBR booting and UEFI booting:

Aspect BIOS/MBR UEFI
Boot binary format 512-byte flat binary PE32+ executable
Code size limit 510 bytes (stage 1) Up to partition size
CPU mode at handoff 16-bit real mode 32 or 64-bit
Disk partitioning MBR (4 primary partitions) GPT (128 partitions)
Secure Boot support No Yes
Boot config storage MBR partition table NVRAM variables
File system access Must implement from scratch FAT32 driver built-in

Verify each entry in your table against the UEFI specification or OVMF UEFI implementation in QEMU.