CROSS := i686-elf-
CC := $(CROSS)gcc
LD := $(CROSS)ld
QEMU := qemu-system-i386
MACHINE := pc-i440fx-7.2
BIOS := /usr/share/seabios/bios-256k.bin
include layout.mk
QEMU_COMMON := -machine $(MACHINE) -accel tcg -cpu qemu32 -smp 1 -m 64M -bios $(BIOS) -boot order=c,strict=on -nic none -no-reboot
QEMU_FLAGS := $(QEMU_COMMON) -drive file=build/mbr.img,format=raw,if=ide
QEMU_BLANK_FLAGS := $(QEMU_COMMON) -drive file=build/blank.img,format=raw,if=ide
QEMU_BADSIG_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-badsig.img,format=raw,if=ide
QEMU_TRUNCATED_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-truncated.img,format=raw,if=ide
QEMU_BADMAGIC_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-badmagic.img,format=raw,if=ide
QEMU_CORRUPT_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-corrupt.img,format=raw,if=ide
QEMU_OVERSIZED_HEADER_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-oversized-header.img,format=raw,if=ide
CFLAGS := -std=c11 -ffreestanding -O0 -g -Wall -Wextra -Werror -fno-pie -fno-stack-protector -fno-asynchronous-unwind-tables

.PHONY: build run debug screenshot check check-layout help
build: build/sample.elf build/blank.img build/day02-mbr.img build/mbr.img build/mbr-badsig.img build/mbr-truncated.img build/mbr-badmagic.img build/mbr-oversized-header.img build/mbr-corrupt.img build/payload-layout.txt

build/.dir:
	mkdir -p build
	touch $@

build/layout.inc: layout.mk | build/.dir
	{ printf '%%define STAGE2_LBA %s\n' '$(STAGE2_LBA)'; printf '%%define STAGE2_SECTORS %s\n' '$(STAGE2_SECTORS)'; printf '%%define HEADER_LBA %s\n' '$(HEADER_LBA)'; printf '%%define PAYLOAD_LBA %s\n' '$(PAYLOAD_LBA)'; printf '%%define PAYLOAD_MAX_BYTES %s\n' '$(PAYLOAD_MAX_BYTES)'; printf '%%define PAYLOAD_SEGMENT %s\n' '$(PAYLOAD_SEGMENT)'; printf '%%define READ_CHUNK_SECTORS %s\n' '$(READ_CHUNK_SECTORS)'; printf '%%define HEADER_MAGIC %s\n' '$(HEADER_MAGIC)'; printf '%%define HEADER_VERSION %s\n' '$(HEADER_VERSION)'; } > $@

build/demo.o: sample/demo.c | build/.dir
	$(CC) $(CFLAGS) -c $< -o $@

build/entry.o: sample/entry.asm | build/.dir
	nasm -f elf32 -g -F dwarf $< -o $@

build/sample.elf: build/entry.o build/demo.o sample/linker.ld
	$(LD) -z noexecstack -T sample/linker.ld build/entry.o build/demo.o -o $@

build/mbr.bin: boot/mbr.asm build/layout.inc | build/.dir
	nasm -f bin $< -o $@
	test "$$(wc -c < $@)" -eq 512

build/day02-mbr.bin: boot/mbr.asm build/layout.inc | build/.dir
	nasm -D DAY02_ONLY -f bin $< -o $@
	test "$$(wc -c < $@)" -eq 512

build/stage2.bin: boot/stage2.asm build/layout.inc | build/.dir
	nasm -f bin $< -o $@
	test "$$(wc -c < $@)" -eq 4096

check-layout:
	test $(STAGE2_LBA) -eq 1
	test $(STAGE2_SECTORS) -eq 8
	test $(HEADER_VERSION) -eq 1
	test $(PAYLOAD_MAX_BYTES) -eq 65536
	test $(PAYLOAD_RESERVED_SECTORS) -eq 128
	test $$(( $(PAYLOAD_SEGMENT) * 16 )) -eq $$(( $(PAYLOAD_LOAD) ))
	test $$(( $(PAYLOAD_LOAD) )) -eq 65536
	test $(FAT16_START_LBA) -lt $(IMAGE_SECTORS)
	test $(READ_CHUNK_SECTORS) -gt 0
	test $$(( 128 % $(READ_CHUNK_SECTORS) )) -eq 0
	test "$(HEADER_LBA)" -eq "$$(( $(STAGE2_LBA) + $(STAGE2_SECTORS) ))"
	test "$(PAYLOAD_LBA)" -eq "$$(( $(HEADER_LBA) + 1 ))"
	test "$$(( $(PAYLOAD_LBA) + $(PAYLOAD_RESERVED_SECTORS) ))" -le "$(FAT16_START_LBA)"
	@if [ "$(PAYLOAD_BYTES)" -le 0 ]; then echo 'payload size must be positive' >&2; exit 1; fi
	@if [ "$(PAYLOAD_BYTES)" -gt "$(PAYLOAD_MAX_BYTES)" ]; then echo 'payload size $(PAYLOAD_BYTES) exceeds max $(PAYLOAD_MAX_BYTES)' >&2; exit 1; fi
	test "$(READ_CHUNK_SECTORS)" -le 127
	test "$$(( $(READ_CHUNK_SECTORS) * 512 ))" -le 65536

build/mkpayload: tools/mkpayload.c | build/.dir
	cc -std=c11 -O2 -Wall -Wextra -Werror $< -o $@

build/kernel.bin build/header.bin build/header-badmagic.bin build/header-oversized.bin &: build/mkpayload layout.mk
	$(MAKE) --no-print-directory check-layout
	build/mkpayload build/kernel.bin build/header.bin build/header-badmagic.bin build/header-oversized.bin $(PAYLOAD_BYTES) $(PAYLOAD_MAX_BYTES) $(HEADER_MAGIC)

build/kernel.pad: build/kernel.bin build/header.bin
	dd if=/dev/zero of=$@ bs=512 count=$(PAYLOAD_RESERVED_SECTORS) status=none
	dd if=$< of=$@ bs=512 conv=notrunc status=none
	test "$$(wc -c < $@)" -eq $$(( $(PAYLOAD_RESERVED_SECTORS) * 512 ))

build/payload-layout.txt: build/kernel.bin
	{ printf 'mbr_lba=0\n'; printf 'stage2_lba=$(STAGE2_LBA)\n'; printf 'stage2_sectors=$(STAGE2_SECTORS)\n'; printf 'header_lba=$(HEADER_LBA)\n'; printf 'payload_lba=$(PAYLOAD_LBA)\n'; printf 'payload_bytes=%s\n' "$$(wc -c < build/kernel.bin)"; printf 'payload_sectors=%s\n' "$$(( ($$(wc -c < build/kernel.bin) + 511) / 512 ))"; printf 'payload_reserved_sectors=$(PAYLOAD_RESERVED_SECTORS)\n'; printf 'payload_load=$(PAYLOAD_LOAD)\n'; printf 'fat16_start_lba=$(FAT16_START_LBA)\n'; printf 'read_chunk_sectors=$(READ_CHUNK_SECTORS)\n'; } > $@

build/day02-mbr.img: build/day02-mbr.bin
	cp $< $@
	truncate -s $$(( $(IMAGE_SECTORS) * 512 )) $@

build/mbr.img: build/mbr.bin build/stage2.bin build/header.bin build/kernel.pad
	cp $< $@
	truncate -s $$(( $(IMAGE_SECTORS) * 512 )) $@
	dd if=build/stage2.bin of=$@ bs=512 seek=$(STAGE2_LBA) conv=notrunc status=none
	dd if=build/header.bin of=$@ bs=512 seek=$(HEADER_LBA) conv=notrunc status=none
	dd if=build/kernel.pad of=$@ bs=512 seek=$(PAYLOAD_LBA) conv=notrunc status=none

build/mbr-badsig.img: build/mbr.img
	cp $< $@
	printf '\0\0' | dd of=$@ bs=1 seek=510 conv=notrunc status=none

build/mbr-truncated.img: build/mbr.img
	cp $< $@
	truncate -s $$(( ($(PAYLOAD_LBA) + 8) * 512 )) $@

build/mbr-badmagic.img: build/mbr.img build/header-badmagic.bin
	cp build/mbr.img $@
	dd if=build/header-badmagic.bin of=$@ bs=512 seek=$(HEADER_LBA) conv=notrunc status=none

build/mbr-oversized-header.img: build/mbr.img build/header-oversized.bin
	cp build/mbr.img $@
	dd if=build/header-oversized.bin of=$@ bs=512 seek=$(HEADER_LBA) conv=notrunc status=none

build/mbr-corrupt.img: build/mbr.img
	cp $< $@
	printf '\0' | dd of=$@ bs=1 seek=$$(( $(PAYLOAD_LBA) * 512 + 100 )) conv=notrunc status=none

build/blank.img: | build/.dir
	dd if=/dev/zero of=$@ bs=512 count=2048 status=none

run: build
	LC_ALL=C.UTF-8 $(QEMU) $(QEMU_FLAGS) -display curses -serial none

debug: build
	$(QEMU) $(QEMU_FLAGS) -display none -serial none -monitor none -S -gdb tcp:127.0.0.1:1234

screenshot: build
	{ sleep 2; printf 'screendump build/day03-screen.png -f png\nquit\n'; } | $(QEMU) $(QEMU_FLAGS) -display none -serial none -monitor stdio >/dev/null

check: build
	QEMU='$(QEMU)' QEMU_FLAGS='$(QEMU_FLAGS)' QEMU_BLANK_FLAGS='$(QEMU_BLANK_FLAGS)' QEMU_BADSIG_FLAGS='$(QEMU_BADSIG_FLAGS)' QEMU_TRUNCATED_FLAGS='$(QEMU_TRUNCATED_FLAGS)' QEMU_BADMAGIC_FLAGS='$(QEMU_BADMAGIC_FLAGS)' QEMU_CORRUPT_FLAGS='$(QEMU_CORRUPT_FLAGS)' QEMU_OVERSIZED_HEADER_FLAGS='$(QEMU_OVERSIZED_HEADER_FLAGS)' PAYLOAD_MAX_BYTES='$(PAYLOAD_MAX_BYTES)' PAYLOAD_BYTES='$(PAYLOAD_BYTES)' sh tools/check.sh

help:
	@printf '%s\n' 'make build: ELF32 sample, blank disk, day-02 MBR variant and day-03 boot disk' 'make run: boot day-03 image in curses display' 'make debug: CPU paused, GDB on container loopback port 1234' 'make screenshot: save build/day03-screen.png with QEMU monitor' 'make check: ELF, missing-symbol, boot-sector, EDD header/payload read, checksum and failure-path checks'
