16-bit real-mode target for bare-metal boot sectors and multi-stage images.
x86_16-boot
Not a Rust target — built using Forge’s internal 16-bit assembler.
# Boot sector: 512-byte flat binary with 0x55AA signature
./target/release/forgec examples/bootloader.dev -o boot.bin --target x86_16-boot
# Bare stage image (no boot signature), e.g. a stage-2 loader or kernel
./target/release/forgec src/kernel/kernel.dev -o kernel.raw --linker examples/os/os.fld
FORMAT |
Produces | Use case |
|---|---|---|
flat |
512-byte boot sector, padded, 0x55AA at offset 510 |
Stage 1, loaded by the BIOS at 0x7C00 |
raw |
The image as-is, no padding or signature | Stages 2+, loaded by a previous stage |
The FORMAT raw form is selected with a .fld linker descriptor
(FORMAT raw). Because a raw stage can be loaded anywhere, its in-memory
load address is declared with the LOAD directive (default 0x7C00); the
compiler fixes up absolute string addresses against this base, so a stage
loaded elsewhere (e.g. a loader at 0x9000) must set it.
| Property | Value |
|---|---|
| Architecture | 8086/8088 (real mode) |
| Format | Flat / raw binary (raw bytes) |
| Endianness | Little |
| Pointer Size | 16-bit (segmented) |
| Default Load Address | 0x7C00 |
| Boot Signature | 0x55AA at offset 510 (flat only) |
Physical Address = Segment * 16 + Offset
ForgeOS sets flat segments: ds/es/ss = 0, stack top at 0x7C00.
0x00000 - 0x003FF IVT (256 vectors × 4 bytes)
0x00400 - 0x004FF BIOS Data Area
0x00500 - 0x07BFF Free (conventional)
0x07C00 - 0x07DFF Boot Sector (512 bytes)
0x07E00 - 0x9FFFF Free
0xA0000 - 0xBFFFF Video RAM
0xC0000 - 0xFFFFF BIOS ROM
No standard ABI. Forge uses a stack-based convention:
4 + (n-1-i)*2 on the frame
(the first argument ends up deepest on the stack)_dev_*)Declared extern and emitted only when referenced. Available on x86_16:
| Helper | Implementation |
|---|---|
_dev_bios_teletype(c: char) |
INT 10h AH=0Eh teletype output |
_dev_serial_putc(c: char) |
Debug port output (out 0xE9, al — Bochs/QEMU) |
_dev_load_char(p: ptr[char]) -> char |
Load a byte from memory |
_dev_bios_key() -> char |
INT 16h AH=00h blocking keystroke |
_dev_bios_disk_read(drive, cyl, head, sector, count, buf) -> u16 |
INT 13h AH=02h disk read (ES = addr » 4, BX = addr & 0xF) |
_dev_bios_disk_reset(drive: u16) |
INT 13h AH=00h disk reset |
_dev_bios_reboot() |
INT 19h bootstrap reboot |
_dev_bios_clear() |
INT 10h AH=00h AL=03h — 80x25 text mode (clears the screen) |
_dev_jump(addr: u16) |
Far jump to a flat address |
_dev_halt() |
cli; hlt |
0x7C00, jumps to itFORMAT flat) sets segments/stack, reads the next stage
with _dev_bios_disk_read, and far-jumps to it via _dev_jumpFORMAT raw, its own LOAD address) does the same
until the kernel is in placeForgeOS (examples/os/) is a three-stage reference implementation of this
process — see the ForgeOS example.
| Module | Support |
|---|---|
std.io |
✅ (BIOS INT 10h/16h, debug port) |
std.mem |
✅ |
std.string |
✅ |
std.math |
✅ (int only) |
std.alloc |
❌ |
std.fmt |
✅ |
std.volatile |
✅ |
std.gc |
❌ |
std.runtime |
❌ |
asm!() templates are accepted by the parser and the x86_16 backend
assembles the verbatim template text (other targets error at codegen):
asm!("cli") # Clear interrupts
asm!("sti") # Set interrupts
asm!("hlt") # Halt CPU
asm!("int 0x10") # BIOS call
ForgeOS itself uses no inline assembly — everything is built from Forge
source and the _dev_* helpers.
| Constraint | Limit |
|---|---|
flat image size |
≤ 510 bytes of code + 0x55AA |
raw image size |
Whatever the loading stage can read |
| Stack | Limited (conventional memory) |
| Heap | None |
| Protection | None (real mode) |
| Paging | None |
| Multitasking | None |
@freestanding
pub def _start() -> void:
puts("Hello, Forge bootloader!")
_dev_halt()
def puts(msg: ptr[char]) -> void:
unsafe:
var p = msg
var c = _dev_load_char(p)
while c != 0:
_dev_bios_teletype(c)
p = p + 1
c = _dev_load_char(p)
extern def _dev_bios_teletype(c: char) -> void
extern def _dev_load_char(p: ptr[char]) -> char
extern def _dev_halt() -> void
# Build
forgec examples/bootloader.dev -o boot.bin --target x86_16-boot
# Verify
ls -la boot.bin # 512 bytes
hexdump -C boot.bin # Check 0x55AA at end
# Run in QEMU
qemu-system-x86_64 -fda boot.bin -nographic
# Expected:
# SeaBIOS...
# Booting from Floppy...
# Hello, Forge bootloader!
For multi-stage images under QEMU, prefer the IDE disk with multi-thread TCG (the floppy controller can hang single-thread TCG):
qemu-system-x86_64 -accel tcg,thread=multi \
-drive file=build/os.img,format=raw,if=ide -nographic
# QEMU with GDB
qemu-system-x86_64 -fda boot.bin -nographic -s -S
# In another terminal
gdb -ex "target remote localhost:1234" -ex "set architecture i8086"
# Bochs (better 16-bit debugging)
bochs -q -f bochsrc.txt
LOADLOAD: Any stage not loaded at 0x7C00 must set LOAD in its
.fld, or its string references will point at the wrong addresses