Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@ GAS = as
GAS_FLAGS = --32

LD = ld
LD_FLAGS = --oformat binary -m elf_i386 -s -nostdlib
LD_FLAGS = -m i386pe -s -nostdlib --image-base 0

OBJCOPY = objcopy

CC = gcc
CC_FLAGS = -m32 -march=i386 -fno-pic -static -fno-asynchronous-unwind-tables \
-fno-stack-protector -ffreestanding -nostdlib -O2

DD = dd
PS = powershell -NoProfile -Command
QEMU ?= C:/Program Files/qemu/qemu-system-i386.exe

STAGE2_SECTORS = 127
IMAGE_SIZE_MB = 64

BUILD_DIR = ./build
SRC_DIR = ./src


# not files
.PHONY: all clean run
.PHONY: all clean run install check-stage2-size


# default option: make .img file
Expand All @@ -25,22 +30,22 @@ all: $(BUILD_DIR)/bootloader.img

# get .img from stages
$(BUILD_DIR)/bootloader.img: $(BUILD_DIR)/fstage.bin $(BUILD_DIR)/sstage.bin
# create disk img
$(DD) if=/dev/zero of=$@ bs=1M count=64
# write Stage 1
$(DD) if=$(BUILD_DIR)/fstage.bin of=$@ bs=1 count=446 conv=notrunc
# write Stage 2 + 2.5
$(DD) if=$(BUILD_DIR)/sstage.bin of=$@ bs=512 seek=1 conv=notrunc
$(PS) "$$fs=[IO.File]::Open('$@',[IO.FileMode]::Create,[IO.FileAccess]::ReadWrite); $$fs.SetLength($(IMAGE_SIZE_MB)MB); $$fs.Close()"
$(PS) "$$b=[IO.File]::ReadAllBytes('$(BUILD_DIR)/fstage.bin'); $$fs=[IO.File]::Open('$@',[IO.FileMode]::Open,[IO.FileAccess]::ReadWrite); $$fs.Write($$b,0,446); $$fs.Seek(510,[IO.SeekOrigin]::Begin) > $$null; $$fs.Write($$b,510,2); $$fs.Close()"
$(PS) "$$b=[IO.File]::ReadAllBytes('$(BUILD_DIR)/sstage.bin'); $$fs=[IO.File]::Open('$@',[IO.FileMode]::Open,[IO.FileAccess]::ReadWrite); $$fs.Seek(512,[IO.SeekOrigin]::Begin) > $$null; $$fs.Write($$b,0,$$b.Length); $$fs.Close()"


# link asm code to .bin
$(BUILD_DIR)/fstage.bin: $(BUILD_DIR)/fstage.o
$(LD) $(LD_FLAGS) -Ttext 0x7c00 -o $@ $<
$(LD) $(LD_FLAGS) -Ttext 0x7c00 -e _start -o $(BUILD_DIR)/fstage.pe $<
$(OBJCOPY) -O binary -j .text $(BUILD_DIR)/fstage.pe $@


# link stage 2 asm and C code to .bin
$(BUILD_DIR)/sstage.bin: $(BUILD_DIR)/sstage.o $(BUILD_DIR)/sstagec.o
$(LD) $(LD_FLAGS) -Ttext 0x8000 -o $@ $^
$(LD) $(LD_FLAGS) -Ttext 0x7e00 -e stage2_start -o $(BUILD_DIR)/sstage.pe $^
$(OBJCOPY) -O binary $(BUILD_DIR)/sstage.pe $@
powershell -NoProfile -Command "if ((Get-Item '$@').Length -gt ($(STAGE2_SECTORS) * 512)) { throw 'Stage 2 is larger than $(STAGE2_SECTORS) sectors' }"


# compile C code to .o
Expand All @@ -55,14 +60,22 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/*/%.asm | $(BUILD_DIR)

# create ./build folder if not found
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
if not exist "$(BUILD_DIR)" mkdir "$(BUILD_DIR)"


# install loader into an existing raw disk image with an ext2 Linux partition.
# Usage: make install DISK=debian-ext2.img
install: $(BUILD_DIR)/fstage.bin $(BUILD_DIR)/sstage.bin
@if "$(DISK)" == "" (echo DISK is not set. Usage: make install DISK=debian-ext2.img & exit /b 1)
$(PS) "$$b=[IO.File]::ReadAllBytes('$(BUILD_DIR)/fstage.bin'); $$fs=[IO.File]::Open('$(DISK)',[IO.FileMode]::Open,[IO.FileAccess]::ReadWrite); $$fs.Write($$b,0,446); $$fs.Seek(510,[IO.SeekOrigin]::Begin) > $$null; $$fs.Write($$b,510,2); $$fs.Close()"
$(PS) "$$b=[IO.File]::ReadAllBytes('$(BUILD_DIR)/sstage.bin'); $$fs=[IO.File]::Open('$(DISK)',[IO.FileMode]::Open,[IO.FileAccess]::ReadWrite); $$fs.Seek(512,[IO.SeekOrigin]::Begin) > $$null; $$fs.Write($$b,0,$$b.Length); $$fs.Close()"


# run bootloader.img via qemu
run: $(BUILD_DIR)/bootloader.img
qemu-system-i386 -drive format=raw,file=$<
"$(QEMU)" -m 256M -drive format=raw,file=$< -serial stdio


# cleanup build artifacts
clean:
rm -rf $(BUILD_DIR)
if exist "$(BUILD_DIR)" rmdir /s /q "$(BUILD_DIR)"
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
# PSLBoot
Polytech Simple Linux Bootloader **(WIP)**
___
# Notes
- **TODO**: Update Makefile when sources are complete.
# What works

PSLBoot can load a Linux bzImage and initrd from the first Linux/ext2
partition of a raw QEMU IDE disk. Stage 2 is stored in the post-MBR gap
starting at LBA 1, so create the first partition at the usual 1 MiB offset
or later.

# Build

```sh
make
```

This creates `build/bootloader.img`, mostly useful as a build artifact sanity
check. For a real Debian disk image, install the loader into an existing raw
image:

```sh
make install DISK=debian-ext2.img
```

The install target preserves the partition table, writes MBR boot code, writes
the boot signature, and writes Stage 2 at LBA 1.

# Debian/ext2 disk expectations

- QEMU disk must be attached as the primary IDE disk.
- The first Linux partition must contain an ext2 filesystem, not ext4-with-extents.
- Kernel/initrd are searched as `/boot/vmlinuz`, `/boot/initrd.img`, then by
`vmlinuz*` and `initrd.img*` inside `/boot`.
- Kernel command line is currently built in as:
`root=/dev/sda1 ro console=tty0 console=ttyS0,115200n8`.

Example run after installing into a prepared disk:

```sh
make run
```

or directly:

```sh
"C:/Program Files/qemu/qemu-system-i386.exe" -m 256M -drive format=raw,file=debian-ext2.img -serial stdio
```
211 changes: 129 additions & 82 deletions src/stage-1/fstage.asm
Original file line number Diff line number Diff line change
@@ -1,82 +1,129 @@
################################################################################
#
# fstage.asm
#
# OS CW26-1. Polytech Simple Linux Bootloader. Stage 1.
#
#
# Recommended build command:
# $ as --32 -o fstage.o fstage.asm
# $ ld -Ttext 0x7c00 --oformat binary -m elf_i386 -o fstage.bin fstage.o
#
################################################################################


.code16
.att_syntax

.section .text
.globl _start


_start:
# ==== init
cli
xor %ax, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov $0x7C00, %sp
sti

mov %dl, boot_drive # saving boot drive number

# ==== check extensions present
mov $0x41, %ah
mov boot_drive, %dl
mov $0x55AA, %bx
int $0x13
jc disk_error

# ==== loading Stage 2 from boot drive
lea dap, %si
mov $0x42, %ah
mov boot_drive, %dl
int $0x13
jc disk_error

ljmp $0x0000, $0x7E00 # passing control to Stage 2


disk_error:
mov $err_msg, %si
.loop:
lodsb
or %al, %al
jz hang
xor %bh, %bh
mov $0x0E, %ah
int $0x10
jmp .loop
hang:
hlt
jmp hang


dap: # Disk Address Packet for int 0x13 (ah = 0x42)
.byte 0x10 # struct size
.byte 0x00 # reserved
.word 16 # sector amt
.word 0x7E00 # offset
.word 0x0000 # segment
.quad 1 # LBA-address

boot_drive: .byte 0
err_msg: .asciz "Disk error!"


.zero (446 - (. - _start)) # padding before partition table

.zero 64 # partition table (manual filling)

.word 0xAA55 # bootsect signature
################################################################################
#
# fstage.asm
#
# OS CW26-1. Polytech Simple Linux Bootloader. Stage 1.
#
#
# Recommended build command:
# $ as --32 -o fstage.o fstage.asm
# $ ld -Ttext 0x7c00 --oformat binary -m elf_i386 -o fstage.bin fstage.o
#
################################################################################


.code16
.att_syntax

.section .text
.globl _start

# Where we stash the BIOS E820 memory map for Stage 2:
# 0x4000 : dword, number of entries
# 0x4004 + n*20 : entries (each: u64 addr, u64 size, u32 type)
E820_COUNT = 0x4000
E820_ENTRIES = 0x4004


_start:
# ==== init
cli
xor %ax, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov $0x7C00, %sp
sti

mov %dl, boot_drive # saving boot drive number

# ==== collect BIOS E820 memory map (int 0x15, eax=0xE820)
call do_e820

# ==== check extensions present
mov $0x41, %ah
mov boot_drive, %dl
mov $0x55AA, %bx
int $0x13
jc disk_error

# ==== loading Stage 2 from boot drive
lea dap, %si
mov $0x42, %ah
mov boot_drive, %dl
int $0x13
jc disk_error

ljmp $0x0000, $0x7E00 # passing control to Stage 2


# ==== Build the E820 map at E820_ENTRIES, count at E820_COUNT.
do_e820:
push %es
pusha
xor %ax, %ax
mov %ax, %es
mov $E820_ENTRIES, %di # es:di -> destination buffer
xor %ebx, %ebx # continuation = 0
xor %bp, %bp # entry counter
mov $0x534D4150, %edx # 'SMAP'
mov $0xE820, %eax
mov $24, %ecx
int $0x15
jc .e820_done # carry on first call => unsupported
cmp $0x534D4150, %eax # BIOS must return 'SMAP' in eax
jne .e820_done
jmp .e820_check
.e820_loop:
mov $0xE820, %eax
mov $24, %ecx
int $0x15
jc .e820_done # carry => end of list
.e820_check:
jcxz .e820_skip # length-0 entry => skip
inc %bp
add $20, %di # advance to next 20-byte slot
.e820_skip:
test %ebx, %ebx # ebx == 0 => that was the last entry
jz .e820_done
jmp .e820_loop
.e820_done:
movzwl %bp, %eax
mov %eax, (E820_COUNT)
popa
pop %es
ret


disk_error:
mov $err_msg, %si
.loop:
lodsb
or %al, %al
jz hang
xor %bh, %bh
mov $0x0E, %ah
int $0x10
jmp .loop
hang:
hlt
jmp hang


dap: # Disk Address Packet for int 0x13 (ah = 0x42)
.byte 0x10 # struct size
.byte 0x00 # reserved
.word 127 # sector amt
.word 0x7E00 # offset
.word 0x0000 # segment
.quad 1 # LBA-address

boot_drive: .byte 0
err_msg: .asciz "Disk error!"


.zero (446 - (. - _start)) # padding before partition table

.zero 64 # partition table (manual filling)

.word 0xAA55 # bootsect signature
Loading
Loading