This commit is contained in:
Niklas Gollenstede
2025-04-14 11:20:52 +02:00
commit 5a2e32aaeb
126 changed files with 16742 additions and 0 deletions

146
tools/build.mk Normal file
View File

@@ -0,0 +1,146 @@
# Build the kernel
# Top Level Folder all generated files will be placed in.
ROOTBUILDDIR = build
# Folder the generated files will be placed in.
BUILDDIR ?= $(ROOTBUILDDIR)
# Build folder suffixes
OPTTAG = opt
NOOPTTAG = noopt
DBGTAG = dbg
VERBOSETAG = verbose
# C++
CXX ?= $(PREFIX)clang++
# on macos the default compiler is called c++, switch to clang++
ifeq ($(shell uname),Darwin)
ifeq ($(CXX),c++)
CXX := clang++
endif
endif
CXXFLAGS_ARCH = -m64
CXXFLAGS_DEFAULT = -std=c++23 -ffreestanding -fno-pic -nodefaultlibs -nostdlib -nostdinc -I. -fno-rtti -fno-exceptions -Wno-write-strings -fno-stack-protector -mno-red-zone -g -gdwarf-2 -fno-use-cxa-atexit -no-pie -nostartfiles
# Enable 16-byte compare-and-exchange instruction for debugging purposes (stack alignment)
CXXFLAGS_DEFAULT += -mcx16
CXXFLAGS_OPT = -O3 -fomit-frame-pointer
CXXFLAGS_WARNING = -Wall -Wextra -Werror -Wno-error=unused-parameter -Wno-error=unused-variable -Wno-non-virtual-dtor
CXXFLAGS_CLANG = -Wno-error=unused-private-field -Wno-implicit-exception-spec-mismatch -Wno-error=unused-const-variable -Wno-unused-command-line-argument -Wno-unused-const-variable -fno-strict-aliasing
ifeq ($(shell uname),Darwin)
CXXFLAGS_CLANG += -target x86_64-pc-linux-gnu -fuse-ld=lld
endif
CXXFLAGS_GCC = -fno-tree-loop-distribute-patterns -Wstack-usage=4096 -Wno-error=stack-usage=
CXXFLAGS_NOFPU = -mno-mmx -mno-sse -mgeneral-regs-only
CXXFLAGS = $(CXXFLAGS_ARCH) $(CXXFLAGS_DEFAULT) $(CXXFLAGS_OPT) $(CXXFLAGS_NOFPU) $(CXXFLAGS_WARNING)
# Compiler specific flags
ifneq (,$(findstring clang,$(CXX)))
COMPILER := CLANG
CXXFLAGS += $(CXXFLAGS_CLANG)
else ifneq (,$(findstring g++,$(CXX)))
COMPILER := GCC
# g++ 6 does not support general-regs-only flag
ifeq "$(shell expr `$(CXX) -dumpversion | cut -f1 -d.` \<= 6)" "1"
CXXFLAGS := $(filter-out -mgeneral-regs-only,$(CXXFLAGS))
endif
# Configures -Warray-bounds warning to allow accessing in the NULL page
# Necessary to read the Extended BIOS Data Area at 0x40e
ifeq "$(shell expr `$(CXX) -dumpversion | cut -f1 -d.` \>= 12)" "1"
CXXFLAGS += --param=min-pagesize=0x40e
endif
CXXFLAGS += $(CXXFLAGS_GCC)
else
COMPILER :=
endif
# Assembly
ASM = nasm
ASMFLAGS = -f elf64
# Additional build utilities
# If llvm tools are available, use them
ifneq (,$(shell which llvm-objcopy 2>/dev/null))
PREFIX = llvm-
endif
OBJCOPY = $(PREFIX)objcopy
STRIP = $(PREFIX)strip
AR = $(PREFIX)ar
# Subdirectories with sources
VPATH = $(sort $(dir $(CC_SOURCES) $(ASM_SOURCES)))
# Lists of object files that are generated by compilation:
# Note that the variables containing the input files are to be defined by
# the Makefiles prior to including this common.mk.
CC_OBJECTS = $(addprefix $(BUILDDIR)/,$(CC_SOURCES:.cc=.o))
DEP_FILES = $(addprefix $(BUILDDIR)/,$(CC_SOURCES:.cc=.d) $(addsuffix .d,$(ASM_SOURCES)))
ASM_OBJECTS = $(addprefix $(BUILDDIR)/,$(addsuffix .o,$(ASM_SOURCES)))
# Dependency files
$(BUILDDIR)/%.d : %.cc $(MAKEFILE_LIST)
@echo "DEP $<"
@mkdir -p $(@D)
$(VERBOSE) $(CXX) $(CXXFLAGS) -MM -MT $(BUILDDIR)/$*.o -MF $@ $<
$(BUILDDIR)/%.asm.d : %.asm $(MAKEFILE_LIST)
@echo "DEP $<"
@mkdir -p $(@D)
$(VERBOSE) $(ASM) $(ASMFLAGS) -M -MT $(BUILDDIR)/$*.asm.o -MF $@ $<
# Object files
$(BUILDDIR)/%.o : %.cc $(MAKEFILE_LIST)
@echo "CXX $<"
@mkdir -p $(@D)
$(VERBOSE) $(CXX) -c $(CXXFLAGS) -o $@ $<
$(BUILDDIR)/%.asm.o : %.asm $(MAKEFILE_LIST)
@echo "ASM $<"
@mkdir -p $(@D)
$(VERBOSE) $(ASM) $(ASMFLAGS) -o $@ $<
# The standard target 'clean' removes the whole generated system, the object files, and the dependency files.
clean::
rm -rf "$(BUILDDIR)"
# Target issuing a nested call to make generating a fully optimized systems without assertions.
%-$(OPTTAG):
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)/$(OPTTAG)" ISODIR="$(ISODIR)/$(OPTTAG)" CXXFLAGS_OPT="-Ofast -fomit-frame-pointer -flto -march=westmere -DNDEBUG" $*
# Target issuing a nested call to make generating a non-optimized system.
%-$(NOOPTTAG):
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)/$(NOOPTTAG)" ISODIR="$(ISODIR)/$(NOOPTTAG)" CXXFLAGS_OPT="-O0" $*
# Target issuing a nested call to make generating a system optimized for debugging.
%-$(DBGTAG):
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)/$(DBGTAG)" ISODIR="$(ISODIR)/$(DBGTAG)" CXXFLAGS_OPT="-Og -fno-omit-frame-pointer" $*
# Target issuing a nested call to make generating a system with verbose output.
%-$(VERBOSETAG):
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)/$(VERBOSETAG)" ISODIR="$(ISODIR)/$(VERBOSETAG)" CXXFLAGS_OPT="-DVERBOSE" $*
# Documentation
help::
@$(echo) "" \
"All targets exist in different flavours in addition to \e[2;3m<name>\e[0m:\n" \
"\e[2;3m<name>\e[0;3m-noopt\e[0m, \e[2;3m<name>\e[0;3m-opt\e[0m, \e[2;3m<name>\e[0;3m-dbg\e[0m, and \e[2;3m<name>\e[0;3m-verbose\e[0m.\n" \
"Targets suffixed with \e[3m-noopt\e[0m are compiled without optimizations,\n" \
"\e[3m-opt\e[0m targets produce a highly optimized binary, while\n" \
"\e[3m-dbg\e[0m targets only use optimizations not hindering debugging.\n" \
"Targets suffixed with \e[3m-verbose\e[0m generate binaries including\n" \
"verbose output (via \e[3mDBG_VERBOSE\e[0m), making such targets useful for debugging.\n" \
"To get a verbose make output, clear VERBOSE, e.g. \e[3mmake VERBOSE=\e[0m.\n" \
"The following targets are available (each target can be suffixed by \e[3m-noopt\e[0m\n" \
"and \e[3m-verbose\e[0m):\n\n" \
" \e[3mall\e[0m Builds $(PROJECT), generating an ELF binary\n\n"
# Print warnings, if appropriate
ifeq (,$(COMPILER))
$(warning Unknown (and potentially unsupported) compiler "$(CXX)"!)
endif
# Include dependency files (generated via gcc flag -MM)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEP_FILES)
endif
# Phony targets
.PHONY: clean help

33
tools/common.mk Normal file
View File

@@ -0,0 +1,33 @@
# Common include Makefile
# Hide commands
VERBOSE = @
# Prefix for toolchain binaries
PREFIX ?=
# Project name
PROJECT ?= "MPStuBS"
help::
@$(echo) "\n" \
"\e[1mMAKEFILE for the teaching operating system $(PROJECT)\e[0m\n" \
"--------------------------------------------------\n\n" \
"Executing '\e[4mmake\e[0m' will compile the operating system from source.\n"
# Get current directory path
CURRENT_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
# Include Makefile scripts
include $(CURRENT_DIR)/build.mk
include $(CURRENT_DIR)/qemu.mk
include $(CURRENT_DIR)/image.mk
include $(CURRENT_DIR)/linter.mk
include $(CURRENT_DIR)/remote.mk
# Disable buitlin rules
MAKEFLAGS += --no-builtin-rules
# Disable buitlin rules
MAKEFLAGS += --no-print-directory
# Disable buitlin suffixes
.SUFFIXES:

6771
tools/cpplint.py vendored Normal file

File diff suppressed because it is too large Load Diff

96
tools/image.mk Normal file
View File

@@ -0,0 +1,96 @@
# Generates a bootable ISO image that can be transferred to external media, such as CDs or USB sticks.
# This will install, in addition to your kernel, the bootloader GRUB (https://www.gnu.org/software/grub/).
#
# The target 'gemu-iso' is used to test the image generated with 'iso'.
#
# Assuming that a USB mass-storage devices is connected as, for instance /dev/sdc, the target 'usb-sdc'
# can be used to make your device bootable (requires root access, substitute sdc with the matching device).
# Alternatively, you can burn the .iso file directly to CD.
DD = dd
XORRISO = xorriso
MKISO = grub-mkrescue
ISODIR = $(BUILDDIR)-iso
ISOGRUBCFG = boot/grub/grub.cfg
ISOKERNEL = boot/kernel
ISOINITRD = initrd
GRUBTITLE = $(shell id -un)s $(PROJECT)
GRUBTIMEOUT = 2
GRUBBIN = /usr/lib/grub/i386-pc
# Default ISO target
iso: $(ISOFILE)
# Create Grub config
$(ISODIR)/$(ISOGRUBCFG):
@echo "GEN $@"
@mkdir -p $(dir $@)
@$(echo) "set timeout=$(GRUBTIMEOUT)\nset default=0\n\nmenuentry \"$(GRUBTITLE)\" {\n\tmultiboot /$(ISOKERNEL)\n\tmodule /$(ISOINITRD)\n\tboot\n}" > $@
# Strip debug symbols from kernel binary
$(ISODIR)/$(ISOKERNEL): all
@echo "STRIP $@"
@mkdir -p $(dir $@)
$(VERBOSE) $(STRIP) --strip-debug --strip-unneeded -p -o $@ $(KERNEL)
# copy inital ramdisk
$(ISODIR)/$(ISOINITRD): all
@echo "CPY $@"
@mkdir -p $(dir $@)
@if [ -s $(INITRD) ] ; then cp -a $(INITRD) $@ ; else touch $@ ; fi
# Pack to ISO
$(ISOFILE): $(ISODIR)/$(ISOKERNEL) $(ISODIR)/$(ISOINITRD) $(ISODIR)/$(ISOGRUBCFG)
@echo "ISO $@"
@which $(XORRISO) >/dev/null || echo "Xorriso cannot be found - if building the ISO fails, this may be the reason!" >&2
$(VERBOSE) $(MKISO) -d $(GRUBBIN) -o $@ $(ISODIR)
# Run ISO in QEMU/KVM
%-iso: $(ISOFILE)
@${MAKE} -s QEMUKERNEL="-cdrom $<" $*
# Copy ISO to USB device
usb: $(ISOFILE)
ifeq (,$(USBDEV))
@echo "The environment variable USBDEV must contain the path to the USB mass-storage device:" >&2
@lsblk -o TYPE,KNAME,SIZE,MODEL -a -p | grep "^disk" | cut -b 6-
@exit 1
else
$(VERBOSE) $(DD) if=$< of=$(USBDEV) bs=4M status=progress && sync
endif
# Shorthand to copy ISO to a specific USB device
usb-%:
@$(MAKE) USBDEV=/dev/$* usb
# Burn ISO to CD
cd: $(ISOFILE)
ifeq (,$(CDRWDEV))
@echo "The environment variable CDRWDEV must contain the path to the CD/DVD writer" >&2
@exit 1
else
$(VERBOSE) $(XORRISO) -as cdrecord -v dev=$(CDRWDEV) -dao $<
endif
# Shorthand to nurn ISO to specific CD device
cd-%:
@$(MAKE) CDRWDEV=/dev/$* cd
# The standard target 'clean' removes the whole generated system, the object files, and the dependency files.
clean::
@echo "RM $(ISODIR)"
$(VERBOSE) rm -rf "$(ISODIR)" "$(ISODIR)$(OPTTAG)" "$(ISODIR)$(NOOPTTAG)" "$(ISODIR)$(DBGTAG)" "$(ISODIR)$(VERBOSETAG)"
# Documentation
help::
@$(echo) "Bootable Images\n" \
" \e[3miso\e[0m Generates a bootable system image (File: $(ISOFILE))\n\n" \
" \e[3m*-iso\e[0m Simulate the system by booting from the virtual CD drive. (e.g. qemu-iso)\n\n" \
" \e[3musb\e[0m Generates a bootable USB mass-storage device; the environment\n" \
" variable \e[4mUSBDEV\e[0m should point to the USB device\n\n" \
" \e[3mcd\e[0m Generates a bootable CD; the environment variable \e[4mCDRWDEV\e[0m\n" \
" should point to the CD writer\n\n"
# Phony targets
.PHONY: iso cd usb help

30
tools/linter.mk Normal file
View File

@@ -0,0 +1,30 @@
# Perform static code checks
TIDY ?= clang-tidy
CPPLINT ?= /usr/bin/env python "$(CURRENT_DIR)/cpplint.py"
# Check sources with Clang Tidy
tidy::
ifeq (,$(CC_SOURCES))
@echo "(nothing to tidy)"
else
$(VERBOSE) $(TIDY) --format-style=google -header-filter=.* -warnings-as-errors="readability*" -checks="readability*,google-readability-casting,google-explicit-constructor,bugprone*,-bugprone-narrowing-conversions,-bugprone-reserved-identifier,-readability-else-after-return,-readability-magic-numbers" $(filter-out utils/png.cc,$(CC_SOURCES)) -- $(CXXFLAGS_ARCH) $(CXXFLAGS_DEFAULT) $(CXXFLAGS_OPT)
endif
# Check sources with cpplint
lint::
@if $(CPPLINT) --quiet --recursive . ; then \
echo "Congratuations, coding style obeyed!" ; \
else \
echo "Coding style violated -- see CPPLINT.cfg for details" ; \
exit 1 ; \
fi
# Documentation
help::
@$(echo) "Static Analysis and Linter\n" \
" \e[3mlint\e[0m Checks the coding style using \e[4mCPPLINT\e[0m\n\n" \
" \e[3mtidy\e[0m Uses \e[4mClang Tidy\e[0m for a static code analysis\n\n"
# Phony targets
.PHONY: tidy lint help

111
tools/qemu.mk Normal file
View File

@@ -0,0 +1,111 @@
# Targets for running and debugging in Qemu/KVM
QEMUCPUS ?= 4
INITRD ?= /dev/null
# Switch to curses if no graphical output is available
ifeq ($(DISPLAY),)
export QEMUDISPLAY ?= curses
else
# Macos display
ifeq ($(shell uname),Darwin)
export QEMUDISPLAY ?= cocoa
else
export QEMUDISPLAY ?= gtk
endif
endif
export QEMUEXTRAFLAGS ?=
# Architecture Specific flags
QEMUFLAGS += -k en-us -machine pcspk-audiodev=pa -d guest_errors -m 2048
# Macos sound
ifeq ($(shell uname),Darwin)
QEMUFLAGS += -audiodev coreaudio,id=pa
else
QEMUFLAGS += -audiodev pa,id=pa
endif
DBGKERNEL ?= $(KERNEL64)
DBGARCH ?= i386:x86-64
QEMU ?= qemu-system-x86_64
QEMUKERNEL ?= -kernel $(KERNEL) -initrd $(INITRD)
KVMFLAGS = -enable-kvm -cpu host
QEMUDBGFLAGS = -no-shutdown -no-reboot
GDB = $(PREFIX)gdb
ifneq ($(XDG_RUNTIME_DIR),)
# We should prefer using domain sockets in a private directory
GDBPORT ?= $(XDG_RUNTIME_DIR)/stubs-gdb.sock
else
# but fall back to PID + 1024, which should be an unprivileged unique port on single- and multiuser systems
GDBPORT ?= :$(shell echo $$(($$(id -u) + 1024)))
endif
ifneq ($(findstring /,$(GDBPORT)),)
QEMUGDB = -chardev socket,path=${GDBPORT},server=on,wait=off,id=gdb0 -gdb chardev:gdb0
else
QEMUGDB = -gdb tcp:${GDBPORT}
endif
# (gdb itself supports either :port or a path as target)
qemu: all
@echo "QEMU ${KERNEL}"
${VERBOSE} $(QEMU) $(QEMUKERNEL) $(QEMUGDB) -display ${QEMUDISPLAY} -smp $(QEMUCPUS) $(QEMUFLAGS) ${QEMUEXTRAFLAGS}
# Runs StuBS in Qemu with with hardware accelerations (KVM support) enabled
# The started emulator provides several virtual CPUs that execute in parallel.
kvm: all
@echo "KVM ${KERNEL}"
${VERBOSE} ${QEMU} $(QEMUKERNEL) $(QEMUGDB) -display ${QEMUDISPLAY} -smp $(QEMUCPUS) $(QEMUFLAGS) ${QEMUEXTRAFLAGS} $(KVMFLAGS)
# Execute Qemu with activated GDB stub and directly connect GDB to the spawned Qemu.
gdb: all
${VERBOSE} $(GDB) "$(DBGKERNEL)" \
-ex "set arch $(DBGARCH)" \
-ex "target remote | exec $(QEMU) -gdb stdio $(QEMUKERNEL) -smp $(QEMUCPUS) -S $(QEMUFLAGS) $(DBGFLAGS)"
################################################################
# Rekursive Targets: Setzen einzelner QEMU Paramter
################################################################
################################################################
# Debugging mit GDB
# Sinnvoll anwendbar mit den Targets: qemu, kvm, *-curses, *-serial
%-gdb:
${VERBOSE} ${MAKE} QEMUEXTRAFLAGS="${QEMUEXTRAFLAGS} -S" $*
# um sich mit GDB auf dem Standardport zu verbinden
connect-gdb:
${VERBOSE} gdb -ex "target remote ${GDBPORT}" $(DBGKERNEL)
################################################################
# Eine -display Variante erzwingen
%-curses:
${VERBOSE} ${MAKE} QEMUDISPLAY="curses" QEMUSERIAL="vc:80Cx24C" $*
%-x11:
${VERBOSE} ${MAKE} QEMUDISPLAY=gtk $*
################################################################
# Serial-Ausgabe
QEMUSERIAL ?= vc:80Cx24C
QEMUFLAGS += -serial $(QEMUSERIAL)
%-serial:
@echo "HINT: C-a x: Terminate QEMU; C-a c: Open QEMU console"
${VERBOSE} ${MAKE} QEMUSERIAL=mon:stdio $*
# Help for Qemu targets
help::
@$(echo) "System Emulation\n" \
" \e[3mqemu\e[0m Starts $(PROJECT) in QEMU\n" \
" Due to the internal design of QEMU, some things (especially\n" \
" race conditions) might behave different compared to hardware!\n\n" \
" \e[3mkvm\e[0m Starts $(PROJECT) in KVM, a hardware-accelerated virtual machine\n\n" \
" \e[3m*-serial\e[0m Redirect the serial console to stdout (e.g., qemu-serial, kvm-serial)\n\n" \
" \e[3m*-curses\e[0m Use QEMU's curses interface (e.g., qemu-curses).\n"\
" \e[3m*-x11\e[0m Use QEMU's GTK interface (e.g., qemu-x11).\n\n"\
" \e[3m*-gdb\e[0m Start Simulator with internal GDB stub and wait for a GDB\n" \
" to attach. (e.g., qemu-gdb)\n" \
" \e[3mconnect-gdb\e[0m Connect to waiting GDB. Execute in a second terminal!\n\n"\
# Phony targets
.PHONY: qemu kvm help

38
tools/remote.mk Normal file
View File

@@ -0,0 +1,38 @@
# Test your system on real hardware
NETBOOT_LOCAL="/ibr/adm/user-boot/"
NETBOOT_HOST="x1.ibr.cs.tu-bs.de"
# The boot menu shows pairs of `vmlinuz-*` + `initrd-*.img` with owning user and timestamp.
# We just need to choose a name that doesn't overlap with another user's.
netboot: all
$(VERBOSE) \
if [ ! "$$DEPLOY_LOCAL" ] && [ -e "${NETBOOT_LOCAL}" ] ; then DEPLOY_LOCAL=1 ; fi ; \
if [ "$$DEPLOY_LOCAL" = 1 ] ; then \
user=$$USER ; \
else \
user=$$( ssh -G ${NETBOOT_HOST} | grep -oPe '^user \K.*' ) ; \
fi ; \
\
mkdir -p $(BUILDDIR)/netboot ; \
ln -fT ${KERNEL} $(BUILDDIR)/netboot/vmlinuz-$$user ; \
initrd="$(INITRD)" ; if [ -s "$$initrd" ] ; then \
ln -fT $$initrd $(BUILDDIR)/netboot/initrd-$$user.img ; \
else \
echo "(none)" > $(BUILDDIR)/netboot/initrd-$$user.img ; \
fi ; \
\
if [ "$$DEPLOY_LOCAL" = 1 ] ; then \
echo "CP to locally as $$user" ; \
cp $(BUILDDIR)/netboot/vmlinuz-$$user $(BUILDDIR)/netboot/initrd-$$user.img ${NETBOOT_LOCAL} ; \
else \
echo "SCP to ${NETBOOT_HOST} as $$user" ; \
scp $(BUILDDIR)/netboot/vmlinuz-$$user $(BUILDDIR)/netboot/initrd-$$user.img ${NETBOOT_HOST}:${NETBOOT_LOCAL} ; \
fi
# Documentation
help::
@$(echo) "Netboot\n" \
" \e[3mnetboot\e[0m Copies $(PROJECT) to the network share, allowing the test infrastructure\n" \
" to boot your system"