# AdrOS Makefile
# Usage: make ARCH=x86 (default) | arm | riscv | mips
ARCH ?= x86
KERNEL_NAME := adros-$(ARCH).bin
# Directories
SRC_DIR := src
BUILD_DIR := build/$(ARCH)
# Minimal kernel sources shared across all architectures
KERNEL_COMMON := main.c console.c utils.c cmdline.c driver.c cpu_features.c
C_SOURCES := $(addprefix $(SRC_DIR)/kernel/,$(KERNEL_COMMON))
# HAL sources (architecture-specific)
C_SOURCES += $(wildcard $(SRC_DIR)/hal/$(ARCH)/*.c)
# --- x86 Configuration ---
ifeq ($(ARCH),x86)
# x86 gets ALL kernel sources, drivers, and mm
C_SOURCES := $(wildcard $(SRC_DIR)/kernel/*.c)
C_SOURCES += $(wildcard $(SRC_DIR)/drivers/*.c)
C_SOURCES += $(wildcard $(SRC_DIR)/mm/*.c)
C_SOURCES += $(wildcard $(SRC_DIR)/hal/$(ARCH)/*.c)
# Default Toolchain Prefix (can be overridden)
ifdef CROSS
TOOLPREFIX ?= i686-elf-
endif
# Toolchain tools (Allow user override via make CC=...)
CC ?= $(TOOLPREFIX)gcc
AS ?= $(TOOLPREFIX)as
LD ?= $(TOOLPREFIX)ld
# lwIP sources (NO_SYS=0, IPv4, threaded API + sockets)
LWIPDIR := third_party/lwip/src
LWIP_CORE := $(LWIPDIR)/core/init.c $(LWIPDIR)/core/def.c $(LWIPDIR)/core/inet_chksum.c \
$(LWIPDIR)/core/ip.c $(LWIPDIR)/core/mem.c $(LWIPDIR)/core/memp.c \
$(LWIPDIR)/core/netif.c $(LWIPDIR)/core/pbuf.c $(LWIPDIR)/core/raw.c \
$(LWIPDIR)/core/stats.c $(LWIPDIR)/core/sys.c $(LWIPDIR)/core/tcp.c \
$(LWIPDIR)/core/tcp_in.c $(LWIPDIR)/core/tcp_out.c $(LWIPDIR)/core/timeouts.c \
$(LWIPDIR)/core/udp.c $(LWIPDIR)/core/dns.c
LWIP_IPV4 := $(LWIPDIR)/core/ipv4/etharp.c $(LWIPDIR)/core/ipv4/icmp.c \
$(LWIPDIR)/core/ipv4/ip4.c $(LWIPDIR)/core/ipv4/ip4_addr.c \
$(LWIPDIR)/core/ipv4/ip4_frag.c $(LWIPDIR)/core/ipv4/dhcp.c \
$(LWIPDIR)/core/ipv4/acd.c
LWIP_IPV6 := $(LWIPDIR)/core/ipv6/ethip6.c $(LWIPDIR)/core/ipv6/icmp6.c \
$(LWIPDIR)/core/ipv6/inet6.c $(LWIPDIR)/core/ipv6/ip6.c \
$(LWIPDIR)/core/ipv6/ip6_addr.c $(LWIPDIR)/core/ipv6/ip6_frag.c \
$(LWIPDIR)/core/ipv6/mld6.c $(LWIPDIR)/core/ipv6/nd6.c
LWIP_NETIF := $(LWIPDIR)/netif/ethernet.c
LWIP_API := $(LWIPDIR)/api/api_lib.c $(LWIPDIR)/api/api_msg.c \
$(LWIPDIR)/api/err.c $(LWIPDIR)/api/if_api.c $(LWIPDIR)/api/netbuf.c \
$(LWIPDIR)/api/netifapi.c $(LWIPDIR)/api/tcpip.c
LWIP_SOURCES := $(LWIP_CORE) $(LWIP_IPV4) $(LWIP_IPV6) $(LWIP_NETIF) $(LWIP_API)
NET_SOURCES := $(wildcard $(SRC_DIR)/net/*.c) $(wildcard $(SRC_DIR)/net/lwip_port/*.c)
C_SOURCES += $(NET_SOURCES)
# Mandatory Architecture Flags
ARCH_CFLAGS := -m32 -ffreestanding -fno-builtin -U_FORTIFY_SOURCE -mno-sse -mno-mmx -Iinclude -Iinclude/net -Ithird_party/lwip/src/include
ARCH_LDFLAGS := -m elf_i386 -T $(SRC_DIR)/arch/x86/linker.ld
ARCH_ASFLAGS := --32
# Default User Flags (Allow override via make CFLAGS=...)
CFLAGS ?= -O2 -Wall -Wextra -Werror -Wno-error=cpp
# Merge Flags
CFLAGS := $(ARCH_CFLAGS) $(CFLAGS)
LDFLAGS := $(ARCH_LDFLAGS) $(LDFLAGS)
ASFLAGS := $(ARCH_ASFLAGS) $(ASFLAGS)
ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/x86/*.S)
C_SOURCES += $(wildcard $(SRC_DIR)/arch/x86/*.c)
# Userspace cross-compiler (always i686-elf, even when kernel CC differs)
USER_CC ?= i686-elf-gcc
USER_LD ?= i686-elf-ld
USER_AR ?= i686-elf-ar
# User build output directory (under build/$ARCH/ to keep arch-separated)
USER_BUILD := build/$(ARCH)/user
# List of dynamically-linked user commands (built via user/cmds/<name>/Makefile)
USER_CMD_NAMES := echo sh cat ls mkdir rm cp mv touch ln \
head tail wc sort uniq cut \
chmod chown chgrp \
date hostname uptime \
mount umount env kill sleep \
clear ps df free tee \
basename dirname rmdir \
grep id uname dmesg \
printenv tr dd pwd stat \
sed awk who top du find which \
init
# ELF paths for dynamically-linked commands
USER_CMD_ELFS := $(foreach cmd,$(USER_CMD_NAMES),$(USER_BUILD)/cmds/$(cmd)/$(cmd).elf)
# Special builds (not dynamically-linked via common.mk)
FULLTEST_ELF := $(USER_BUILD)/cmds/fulltest/fulltest.elf
LDSO_ELF := $(USER_BUILD)/cmds/ldso/ld.so
PIE_SO := $(USER_BUILD)/cmds/pie_test/libpietest.so
PIE_ELF := $(USER_BUILD)/cmds/pie_test/pie_test.elf
# ulibc
ULIBC_DIR := user/ulibc
ULIBC_SO := $(USER_BUILD)/ulibc/libc.so
ULIBC_LIB := $(USER_BUILD)/ulibc/libulibc.a
# doom
DOOM_ELF := user/doom/doom.elf
INITRD_IMG := initrd.img
MKINITRD := tools/mkinitrd
endif
# --- ARM64 Configuration ---
ifeq ($(ARCH),arm)
CC := aarch64-linux-gnu-gcc
AS := aarch64-linux-gnu-as
LD := aarch64-linux-gnu-ld
OBJCOPY := aarch64-linux-gnu-objcopy
CFLAGS := -ffreestanding -O2 -Wall -Wextra -Werror -Wno-error=cpp -mno-outline-atomics -Iinclude
LDFLAGS := -T $(SRC_DIR)/arch/arm/linker.ld
ASFLAGS :=
ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/arm/*.S)
C_SOURCES += $(wildcard $(SRC_DIR)/arch/arm/*.c)
endif
# --- RISC-V 64 Configuration ---
ifeq ($(ARCH),riscv)
CC := riscv64-linux-gnu-gcc
AS := riscv64-linux-gnu-as
LD := riscv64-linux-gnu-ld
OBJCOPY := riscv64-linux-gnu-objcopy
CFLAGS := -ffreestanding -O2 -Wall -Wextra -Werror -Wno-error=cpp -Iinclude -mcmodel=medany
LDFLAGS := -T $(SRC_DIR)/arch/riscv/linker.ld
ASFLAGS :=
ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/riscv/*.S)
C_SOURCES += $(wildcard $(SRC_DIR)/arch/riscv/*.c)
endif
# --- MIPS 32 Configuration ---
ifeq ($(ARCH),mips)
CC := mipsel-linux-gnu-gcc
AS := mipsel-linux-gnu-as
LD := mipsel-linux-gnu-ld
CFLAGS := -ffreestanding -O2 -Wall -Wextra -Werror -Wno-error=cpp -Iinclude -mabi=32 -march=mips32r2 -mno-abicalls -fno-pic -G0
LDFLAGS := -T $(SRC_DIR)/arch/mips/linker.ld
ASFLAGS := -march=mips32r2
ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/mips/*.S)
C_SOURCES += $(wildcard $(SRC_DIR)/arch/mips/*.c)
endif
# lwIP object files (compiled from third_party, separate pattern)
LWIP_OBJ := $(patsubst %.c, $(BUILD_DIR)/lwip/%.o, $(LWIP_SOURCES))
# Object generation
OBJ := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(C_SOURCES))
OBJ += $(patsubst $(SRC_DIR)/%.S, $(BUILD_DIR)/%.o, $(ASM_SOURCES))
OBJ += $(LWIP_OBJ)
QEMU_DFLAGS :=
ifneq ($(QEMU_DEBUG),)
QEMU_DFLAGS := -d guest_errors,cpu_reset -D qemu.log
endif
ifneq ($(QEMU_INT),)
QEMU_DFLAGS := $(QEMU_DFLAGS) -d int
endif
BOOT_OBJ := $(BUILD_DIR)/arch/$(ARCH)/boot.o
KERNEL_OBJ := $(filter-out $(BOOT_OBJ), $(OBJ))
all: $(KERNEL_NAME)
$(KERNEL_NAME): $(OBJ)
@echo " LD $@"
@$(LD) $(LDFLAGS) -n -o $@ $(BOOT_OBJ) $(KERNEL_OBJ) $(shell $(CC) $(ARCH_CFLAGS) -print-libgcc-file-name)
iso: $(KERNEL_NAME) $(INITRD_IMG)
@mkdir -p iso/boot
@cp -f $(KERNEL_NAME) iso/boot/$(KERNEL_NAME)
@cp -f $(INITRD_IMG) iso/boot/$(INITRD_IMG)
@echo " GRUB-MKRESCUE adros-$(ARCH).iso"
@grub-mkrescue -o adros-$(ARCH).iso iso > /dev/null
$(MKINITRD): tools/mkinitrd.c include/xxhash32.h
@gcc -Iinclude tools/mkinitrd.c -o $(MKINITRD)
# --- ulibc build (output into build/$ARCH/user/ulibc/) ---
ULIBC_BUILDDIR := $(CURDIR)/$(USER_BUILD)/ulibc
$(ULIBC_LIB) $(ULIBC_SO): FORCE
@$(MAKE) -C $(ULIBC_DIR) CC="$(USER_CC)" AS="$(USER_CC:gcc=as)" AR="$(USER_AR)" LD="$(USER_LD)" \
BUILDDIR="$(ULIBC_BUILDDIR)" \
$(ULIBC_BUILDDIR)/libulibc.a $(ULIBC_BUILDDIR)/libc.so --no-print-directory
FORCE:
# --- Special builds (fulltest, ldso, pie_test) ---
$(FULLTEST_ELF): user/cmds/fulltest/fulltest.c user/cmds/fulltest/errno.c user/linker.ld
@$(MAKE) --no-print-directory -C user/cmds/fulltest TOPDIR=$(CURDIR) BUILDDIR=$(CURDIR)/$(USER_BUILD)/cmds/fulltest USER_CC="$(USER_CC)"
$(LDSO_ELF): user/cmds/ldso/ldso.c user/ldso_linker.ld
@$(MAKE) --no-print-directory -C user/cmds/ldso TOPDIR=$(CURDIR) BUILDDIR=$(CURDIR)/$(USER_BUILD)/cmds/ldso USER_CC="$(USER_CC)"
$(PIE_SO) $(PIE_ELF): user/cmds/pie_test/pie_main.c user/cmds/pie_test/pie_func.c user/pie_linker.ld
@$(MAKE) --no-print-directory -C user/cmds/pie_test TOPDIR=$(CURDIR) BUILDDIR=$(CURDIR)/$(USER_BUILD)/cmds/pie_test USER_CC="$(USER_CC)" USER_LD="$(USER_LD)"
# --- Dynamically-linked user commands (generic rule via sub-Makefiles) ---
# Use absolute paths so they work from sub-Makefile directories
ABS_ULIBC := $(CURDIR)/$(ULIBC_DIR)
ABS_DYN_CC := $(USER_CC) -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -fPIC -fno-plt -I$(ABS_ULIBC)/include
ABS_DYN_LD := $(USER_LD) -m elf_i386 --dynamic-linker=/lib/ld.so -T $(CURDIR)/user/dyn_linker.ld -L$(ULIBC_BUILDDIR) -rpath /lib --unresolved-symbols=ignore-in-shared-libs -z noexecstack
# Generate build rules for each dynamically-linked command
define USER_CMD_RULE
$(USER_BUILD)/cmds/$(1)/$(1).elf: user/cmds/$(1)/$(1).c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
@$$(MAKE) --no-print-directory -C user/cmds/$(1) TOPDIR=$$(CURDIR) \
BUILDDIR=$$(CURDIR)/$(USER_BUILD)/cmds/$(1) \
DYN_CC="$$(ABS_DYN_CC)" DYN_LD="$$(ABS_DYN_LD)" CRT0="$(ULIBC_BUILDDIR)/crt0.o"
endef
$(foreach cmd,$(USER_CMD_NAMES),$(eval $(call USER_CMD_RULE,$(cmd))))
# Commands that go to /bin/ in rootfs (all except init)
USER_BIN_NAMES := $(filter-out init,$(USER_CMD_NAMES))
# Build INITRD_FILES list: <elf>:<rootfs-path>
FSTAB := rootfs/etc/fstab
INITRD_FILES := $(FULLTEST_ELF):sbin/fulltest \
$(USER_BUILD)/cmds/init/init.elf:sbin/init \
$(foreach cmd,$(USER_BIN_NAMES),$(USER_BUILD)/cmds/$(cmd)/$(cmd).elf:bin/$(cmd)) \
$(LDSO_ELF):lib/ld.so $(ULIBC_SO):lib/libc.so \
$(PIE_SO):lib/libpietest.so $(PIE_ELF):bin/pie_test \
$(FSTAB):etc/fstab
INITRD_DEPS := $(MKINITRD) $(FULLTEST_ELF) $(USER_CMD_ELFS) $(LDSO_ELF) $(ULIBC_SO) $(PIE_SO) $(PIE_ELF) $(FSTAB)
# doom (build via 'make doom', included in initrd if present)
doom: $(ULIBC_LIB) $(ULIBC_SO)
@$(MAKE) --no-print-directory -C user/doom TOPDIR=$(CURDIR) ULIBC_BUILDDIR="$(ULIBC_BUILDDIR)"
# Include doom.elf if it has been built
ifneq ($(wildcard $(DOOM_ELF)),)
INITRD_FILES += $(DOOM_ELF):usr/games/doom
INITRD_DEPS += $(DOOM_ELF)
endif
$(INITRD_IMG): $(INITRD_DEPS)
@./$(MKINITRD) $(INITRD_IMG) $(INITRD_FILES)
run: iso
@rm -f serial.log qemu.log
@test -f disk.img || dd if=/dev/zero of=disk.img bs=1M count=4 2>/dev/null
@qemu-system-i386 -boot d -cdrom adros-$(ARCH).iso -m 128M -display none \
-drive file=disk.img,if=ide,format=raw \
-nic user,model=e1000 \
-serial file:serial.log -monitor none -no-reboot -no-shutdown \
$(QEMU_DFLAGS)
run-arm: adros-arm.bin
@rm -f serial-arm.log
@qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -nographic \
-kernel adros-arm.bin -serial mon:stdio $(QEMU_DFLAGS)
run-riscv: adros-riscv.bin
@rm -f serial-riscv.log
@qemu-system-riscv64 -M virt -m 128M -nographic -bios none \
-kernel adros-riscv.bin -serial mon:stdio $(QEMU_DFLAGS)
run-mips: adros-mips.bin
@rm -f serial-mips.log
@qemu-system-mipsel -M malta -m 128M -nographic \
-kernel adros-mips.bin -serial mon:stdio $(QEMU_DFLAGS)
# ---- Static Analysis ----
cppcheck:
@cppcheck --version >/dev/null
@cppcheck --quiet --enable=warning,performance,portability --error-exitcode=1 \
-I include $(SRC_DIR)
# Sparse: kernel-oriented semantic checker (type safety, NULL, bitwise vs logical)
SPARSE_FLAGS := -m32 -D__i386__ -D__linux__ -Iinclude
SPARSE_SRCS := $(filter-out $(wildcard $(SRC_DIR)/arch/arm/*.c) \
$(wildcard $(SRC_DIR)/arch/riscv/*.c) \
$(wildcard $(SRC_DIR)/arch/mips/*.c) \
$(wildcard $(SRC_DIR)/hal/arm/*.c) \
$(wildcard $(SRC_DIR)/hal/riscv/*.c) \
$(wildcard $(SRC_DIR)/hal/mips/*.c), $(C_SOURCES))
sparse:
@echo "[SPARSE] Running sparse on $(words $(SPARSE_SRCS)) files..."
@fail=0; \
for f in $(SPARSE_SRCS); do \
sparse $(SPARSE_FLAGS) $$f 2>&1; \
done
@echo "[SPARSE] Done."
# GCC -fanalyzer: interprocedural static analysis (use-after-free, NULL deref, etc)
ANALYZER_FLAGS := -m32 -ffreestanding -fanalyzer -fsyntax-only -Iinclude -O2 -Wno-cpp
analyzer:
@echo "[ANALYZER] Running gcc -fanalyzer on $(words $(SPARSE_SRCS)) files..."
@fail=0; \
for f in $(SPARSE_SRCS); do \
$(CC) $(ANALYZER_FLAGS) $$f 2>&1 | grep -v "^$$" || true; \
done
@echo "[ANALYZER] Done."
# Combined static analysis: cppcheck + sparse
check: cppcheck sparse
@echo "[CHECK] All static analysis passed."
# ---- Automated Smoke Test (QEMU + expect) ----
SMOKE_SMP ?= 4
SMOKE_TIMEOUT ?= 120
test: iso
@echo "[TEST] Running smoke test (SMP=$(SMOKE_SMP), timeout=$(SMOKE_TIMEOUT)s)..."
@expect tests/smoke_test.exp $(SMOKE_SMP) $(SMOKE_TIMEOUT)
test-1cpu: iso
@echo "[TEST] Running smoke test (SMP=1, timeout=50s)..."
@expect tests/smoke_test.exp 1 50
test-battery: iso
@echo "[TEST] Running full test battery (multi-disk, ping, VFS)..."
@expect tests/test_battery.exp $(SMOKE_TIMEOUT)
# ---- Host-Side Unit Tests ----
test-host:
@mkdir -p build/host
@echo "[TEST-HOST] Compiling tests/test_utils.c..."
@gcc -m32 -Wall -Wextra -Werror -Iinclude -o build/host/test_utils tests/test_utils.c
@./build/host/test_utils
@echo "[TEST-HOST] Compiling tests/test_security.c..."
@gcc -m32 -Wall -Wextra -Werror -Iinclude -o build/host/test_security tests/test_security.c
@./build/host/test_security
@echo "[TEST-HOST] Running userspace utility tests..."
@bash tests/test_host_utils.sh
# ---- GDB Scripted Checks (requires QEMU + GDB) ----
test-gdb: $(KERNEL_NAME) iso
@echo "[TEST-GDB] Starting QEMU with GDB stub..."
@rm -f serial.log
@test -f disk.img || dd if=/dev/zero of=disk.img bs=1M count=4 2>/dev/null
@qemu-system-i386 -smp 4 -boot d -cdrom adros-$(ARCH).iso -m 128M -display none \
-drive file=disk.img,if=ide,format=raw \
-serial file:serial.log -monitor none -no-reboot -no-shutdown \
-s -S &
@sleep 1
@gdb -batch -nx -x tests/gdb_checks.py adros-$(ARCH).bin || true
@-pkill -f "qemu-system-i386.*-s -S" 2>/dev/null || true
# ---- All Tests ----
test-all: check test-host test
@echo "[TEST-ALL] All tests passed."
scan-build:
@command -v scan-build >/dev/null
@scan-build --status-bugs $(MAKE) ARCH=$(ARCH) $(if $(CROSS),CROSS=$(CROSS),) all
mkinitrd-asan: $(FULLTEST_ELF)
@mkdir -p build/host
@gcc -g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined tools/mkinitrd.c -o build/host/mkinitrd-asan
@./build/host/mkinitrd-asan build/host/$(INITRD_IMG).asan $(FULLTEST_ELF):sbin/fulltest
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(dir $@)
@echo " CC $<"
@$(CC) $(CFLAGS) -c $< -o $@
# lwIP sources (compiled with relaxed warnings)
$(BUILD_DIR)/lwip/%.o: %.c
@mkdir -p $(dir $@)
@echo " CC $< (lwIP)"
@$(CC) $(CFLAGS) -Wno-address -w -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.S
@mkdir -p $(dir $@)
@echo " AS $<"
@$(AS) $(ASFLAGS) $< -o $@
clean:
rm -rf build $(KERNEL_NAME) $(INITRD_IMG) adros-*.iso
@$(MAKE) -C user/ulibc clean --no-print-directory 2>/dev/null || true
@if [ -f user/doom/Makefile ]; then $(MAKE) -C user/doom clean --no-print-directory 2>/dev/null || true; fi
.PHONY: all clean iso run run-arm run-riscv run-mips doom cppcheck sparse analyzer check test test-1cpu test-battery test-host test-gdb test-all scan-build mkinitrd-asan