From f89a4592c973a798ec47eb07b3701844d9ede54e Mon Sep 17 00:00:00 2001 From: Freya Murphy <freya@freyacat.org> Date: Tue, 25 Mar 2025 17:41:18 -0400 Subject: [PATCH] convert build system to zig --- .gitignore | 2 + Makefile | 328 +++-------------------------------------------- boot/boot.ld | 9 ++ build.zig | 351 +++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/sio.c | 12 +- 5 files changed, 388 insertions(+), 314 deletions(-) create mode 100644 .gitignore create mode 100644 boot/boot.ld create mode 100644 build.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db16bde --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.zig-cache +bin diff --git a/Makefile b/Makefile index 67353ce..27f2133 100644 --- a/Makefile +++ b/Makefile @@ -1,319 +1,31 @@ -# -# Makefile for the 20245 operating system. -# -######################################## -# Compilation/assembly definable options -######################################## +.PHONY: build clean qemu +.SILENT: -# -# General options: -# CLEAR_BSS include code to clear all BSS space -# GET_MMAP get BIOS memory map via int 0x15 0xE820 -# OS_CONFIG OS-related (vs. just standalone) variations -# FORCE_INLINING force "inline" functions to be inlined even if -# we aren't compiling with at least -O2 -# MAKE_IDENTITY_MAP Compile vmtables.c with an "identity" page -# map table for the first 4MB of the address space. -# +QEMU = qemu-system-i386 +QEMUOPTS = -drive file=bin/disk.img,index=0,media=disk,format=raw -GEN_OPTIONS = -DCLEAR_BSS -DGET_MMAP -DOS_CONFIG - -# -# Debugging options: -# ANNOUNCE_ENTRY announce entry and exit from kernel functions -# RPT_INT_UNEXP report any 'unexpected' interrupts -# RPT_INT_MYSTERY report interrupt 0x27 specifically -# TRACE_CX context restore tracing -# SANITY=n enable "sanity check" level 'n' (0/1/2/3/4) -# T_* tracing options (see below) -# -# Some modules have their own internal debugging options, described -# in their introductory comments. -# -# Define SANITY as 0 for minimal runtime checking (critical errors only). -# If not defined, SANITY defaults to 9999. -# - -DBG_OPTIONS = -DRPT_INT_UNEXP -# DBG_OPTIONS += -DTRACE_CX - -# -# T_ options are used to define bits in a "tracing" bitmask, to allow -# checking of individual conditions. The following are defined: -# -# T_PCB PCB alloc/dealloc -# T_VM VM-related tasks -# T_QUE PCB queue manipulation -# T_SCH, T_DSP Scheduler and dispatcher -# T_SCALL, T_SRET System call entry and exit -# T_EXIT Process exit actions -# T_FORK, T_EXEC Fork and exec actions -# T_INIT Module init function tracing -# T_KM, T_KMFR, T_KMIN Kmem module tracing -# T_SIO, T_SIOR, T_SIOW General SIO module checks -# T_USER, T_ELF User module operations -# -# You can add compilation options "on the fly" by using EXTRAS=thing -# on the command line. For example, to compile with -H (to show the -# hierarchy of #includes): -# -# make EXTRAS=-H -# - -TRACE_OPTIONS = -DT_INIT -DT_USER -DT_ELF -DT_KMIN -DT_VM - -USER_OPTIONS = $(GEN_OPTIONS) $(DBG_OPTIONS) $(TRACE_OPTIONS) $(EXTRAS) - -############################################################## -# YOU SHOULD NOT NEED TO CHANGE ANYTHING BELOW THIS POINT!!! # -############################################################## - -# -# Compilation/assembly control -# - -# -# We only want to include from the common header directory -# -INCLUDES = -I./include - -# -# All our object code will live here -# -BUILDDIR = build -LIBDIR = $(BUILDDIR)/lib - -# -# Things we need to convert to object form -# -SUBDIRS := - -# -# Compilation/assembly/linking commands and options -# -CPP = cpp -CPPFLAGS = $(USER_OPTIONS) -nostdinc $(INCLUDES) - -# -# Compiler/assembler/etc. settings for 32-bit binaries -# -CC = gcc -pipe -CFLAGS = -m32 -fno-pie -std=c99 -fno-stack-protector -fno-builtin -Wall -Wstrict-prototypes -MD $(CPPFLAGS) -# CFLAGS += -O2 - -AS = as -ASFLAGS = --32 - -LD = ld -LDFLAGS = -melf_i386 -no-pie -nostdlib -L$(LIBDIR) - -AR = ar -#ARFLAGS = rvU -ARFLAGS = rsU - -# other programs we use -OBJDUMP = objdump -OBJCOPY = objcopy -NM = nm -READELF = readelf -PERL = perl - -# delete target files if there is an error, or if make is interrupted -.DELETE_ON_ERROR: - -# don't delete intermediate files -.PRECIOUS: %.o $(BUILDDIR)/boot/%.o $(BUILDDIR)/kernel/%.o \ - $(BUILDDIR)/lib/%.o $(BUILDDIR)/user/%.o - -# -# Update $(BUILDDIR)/.vars.X if variable X has changed since the last time -# 'make' was run. -# -# Rules that use variable X should depend on $(BUILDDIR)/.vars.X. If -# the variable's value has changed, this will update the vars file and -# force a rebuild of the rule that depends on it. -# - -$(BUILDDIR)/.vars.%: FORCE - echo "$($*)" | cmp -s $@ || echo "$($*)" > $@ - -.PRECIOUS: $(BUILDDIR)/.vars.% - -.PHONY: FORCE - -# -# Transformation rules - these ensure that all necessary compilation -# flags are specified -# -# Note use of 'cpp' to convert .S files to temporary .s files: this allows -# use of #include/#define/#ifdef statements. However, the line numbers of -# error messages reflect the .s file rather than the original .S file. -# (If the .s file already exists before a .S file is assembled, then -# the temporary .s file is not deleted. This is useful for figuring -# out the line numbers of error messages, but take care not to accidentally -# start fixing things by editing the .s file.) -# -# The .c.X rule produces a .X file which contains the original C source -# code from the file being compiled mixed in with the generated -# assembly language code. Can be helpful when you need to figure out -# exactly what C statement generated which assembly statements! -# - -.SUFFIXES: .S .b .X .i - -.c.X: - $(CC) $(CFLAGS) -g -c -Wa,-adhln $*.c > $*.X - -.c.s: - $(CC) $(CFLAGS) -S $*.c - -#.S.s: -# $(CPP) $(CPPFLAGS) -o $*.s $*.S - -#.S.o: -# $(CPP) $(CPPFLAGS) -o $*.s $*.S -# $(AS) $(ASFLAGS) -o $*.o $*.s -a=$*.lst -# $(RM) -f $*.s - -.s.b: - $(AS) $(ASFLAGS) -o $*.o $*.s -a=$*.lst - $(LD) $(LDFLAGS) -Ttext 0x0 -s --oformat binary -e begtext -o $*.b $*.o - -#.c.o: -# $(CC) $(CFLAGS) -c $*.c - -.c.i: - $(CC) -E $(CFLAGS) -c $*.c > $*.i - -# -# Location of the QEMU binary -# -QEMU = /home/course/csci352/bin/qemu-system-i386 - -# try to generate a unique GDB port -GDBPORT = $(shell expr `id -u` % 5000 + 25000) - -# QEMU's gdb stub command line changed in 0.11 -QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \ - then echo "-gdb tcp::$(GDBPORT)"; \ - else echo "-s -p $(GDBPORT)"; fi) - -# options for QEMU -# -# run 'make' with -DQEMUEXTRA=xxx to add option 'xxx' when QEMU is run -# -# does not include a '-serial' option, as that may or may not be needed -QEMUOPTS = -drive file=disk.img,index=0,media=disk,format=raw $(QEMUEXTRA) - -######################################## -# RULES SECTION -######################################## - -# -# All the individual parts -# - -# -# We have a bit of a chicken/egg problem here. When we create the -# user.img file, a new version of include/userids.h is generated -# in build/new_userids.h; this is compared to the existing userids.h -# file, and if they differ, it is copied into include/userids.h. -# This, unfortunately, should trigger a rebuild of anything that -# includes <userids.h>, but that is all of the user/*.c files along -# with kernel/kernel.c. We could move the user.img creation earlier, -# which would automatically be incorporated into the build of the -# kernel, but it wouldn't automatically trigger recreating the -# userland stuff. We settle for having the build process tell the -# user that a rebuild is required. -# - -all: lib bootstrap kernel userland util user.img disk.img - -# Rules etc. for the various sections of the system -include lib/Make.mk -include boot/Make.mk -include user/Make.mk -include kernel/Make.mk -include util/Make.mk - -# -# Rules for building the disk image -# - -disk.img: $(BUILDDIR)/kernel/kernel.b $(BUILDDIR)/boot/boot user.img BuildImage - ./BuildImage -d usb -o disk.img -b $(BUILDDIR)/boot/boot \ - $(BUILDDIR)/kernel/kernel.b 0x10000 \ - user.img 0x30000 - -# -# Rules for running with QEMU -# - -# how to create the .gdbinit config file if we need it -.gdbinit: util/gdbinit.tmpl - sed "s/localhost:1234/localhost:$(GDBPORT)/" < $^ > $@ - -# "ordinary" gdb -gdb: - gdb -q -n -x .gdbinit - -# gdb with the super-fancy Text User Interface -gdb-tui: - gdb -q -n -x .gdbinit -tui - -qemu: disk.img +qemu: bin/disk.img $(QEMU) -serial mon:stdio $(QEMUOPTS) -qemu-nox: disk.img - $(QEMU) -nographic $(QEMUOPTS) - -qemu-gdb: disk.img .gdbinit - @echo "*** Now run 'gdb'." 1>&2 - $(QEMU) -serial mon:stdio $(QEMUOPTS) -S $(QEMUGDB) - -qemu-nox-gdb: disk.img .gdbinit - @echo "*** Now run 'gdb'." 1>&2 - $(QEMU) -nographic $(QEMUOPTS) -S $(QEMUGDB) - -# -# Create a printable namelist from the kernel file -# -# kernel.nl: only global symbols -# kernel.nll: all symbols -# - -kernel.nl: $(BUILDDIR)/kernel/kernel - nm -Bng $(BUILDDIR)/kernel/kernel.o | pr -w80 -3 > kernel.nl - -kernel.nll: $(BUILDDIR)/kernel/kernel - nm -Bn $(BUILDDIR)/kernel/kernel.o | pr -w80 -3 > kernel.nll - -# -# Generate a disassembly -# - -kernel.dis: $(BUILDDIR)/kernel/kernel - objdump -d $(BUILDDIR)/kernel/kernel > kernel.dis - -# -# Cleanup etc. -# clean: - rm -rf $(BUILDDIR) .gdbinit *.nl *.nll *.lst *.i *.X *.dis + rm -fr .zig-cache + rm -fr bin -realclean: clean - rm -f LOG *.img $(UTIL_BIN) +build: + zig build -# -# Automatically generate dependencies for header files included -# from C source files. -# -$(BUILDDIR)/.deps: $(foreach dir, $(SUBDIRS), $(wildcard $(BUILDDIR)/$(dir)/*.d)) - @mkdir -p $(@D) - $(PERL) util/mergedep.pl $@ $^ +bin/boot.bin: build + cd bin && \ + objcopy -S -O binary -j .text boot boot.bin --include $(BUILDDIR)/.deps +bin/user.img: build + cd bin && \ + ./mkblob init shell + +bin/disk.img: build bin/boot.bin bin/user.img + cd bin && \ + ./BuildImage -d usb -o disk.img -b boot.bin \ + kernel 0x10000 user.img 0x40000 -.PHONY: all clean realclean diff --git a/boot/boot.ld b/boot/boot.ld new file mode 100644 index 0000000..e6fc072 --- /dev/null +++ b/boot/boot.ld @@ -0,0 +1,9 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(bootentry) + +SECTIONS +{ + . = 0x0; + .text : { *(.text .stub .text.*) } +} diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..0485259 --- /dev/null +++ b/build.zig @@ -0,0 +1,351 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const c_flags = &[_][]const u8{ + // lang + "-std=c99", + // warnings + "-Wall", + "-Wextra", + "-pedantic", + // flags + "-fno-pie", + "-fno-stack-protector", + "-fno-omit-frame-pointer", + "-ffreestanding", + "-fno-builtin", +}; + +const ld_flags = &[_][]const u8{ + "-nmagic", + "-nostdlib", + "--no-warn-rwx-segments", +}; + +const boot_src = &[_][]const u8{"boot/boot.S"}; + +const kernel_src = &[_][]const u8{ + "kernel/cio.c", + "kernel/clock.c", + "kernel/isrs.S", + "kernel/kernel.c", + "kernel/kmem.c", + "kernel/list.c", + "kernel/procs.c", + "kernel/sio.c", + "kernel/startup.S", + "kernel/support.c", + "kernel/syscalls.c", + "kernel/user.c", + "kernel/vm.c", + "kernel/vmtables.c", + "lib/klibc.c", +}; + +const lib_src = &[_][]const u8{ + "lib/bound.c", + "lib/cvtdec0.c", + "lib/cvtdec.c", + "lib/cvthex.c", + "lib/cvtoct.c", + "lib/cvtuns0.c", + "lib/cvtuns.c", + "lib/memclr.c", + "lib/memcpy.c", + "lib/memset.c", + "lib/pad.c", + "lib/padstr.c", + "lib/sprint.c", + "lib/str2int.c", + "lib/strcat.c", + "lib/strcmp.c", + "lib/strcpy.c", + "lib/strlen.c", +}; + +const ulib_src = &[_][]const u8{ + "lib/entry.S", + "lib/ulibc.c", + "lib/ulibs.S", +}; + +const Prog = struct { + name: []const u8, + source: []const []const u8, +}; + +const util_progs = &[_]Prog{ + // mkblob + Prog{ + .name = "mkblob", + .source = &[_][]const u8{"util/mkblob.c"}, + }, + // listblob + Prog{ + .name = "listblob", + .source = &[_][]const u8{"util/listblob.c"}, + }, + // BuildImage + Prog{ + .name = "BuildImage", + .source = &[_][]const u8{"util/BuildImage.c"}, + }, +}; + +const user_progs = &[_]Prog{ + // idle + Prog{ + .name = "idle", + .source = &[_][]const u8{"user/idle.c"}, + }, + // init + Prog{ + .name = "init", + .source = &[_][]const u8{"user/init.c"}, + }, + // progABC + Prog{ + .name = "progABC", + .source = &[_][]const u8{"user/progABC.c"}, + }, + // progDE + Prog{ + .name = "progDE", + .source = &[_][]const u8{"user/progDE.c"}, + }, + // progFG + Prog{ + .name = "progFG", + .source = &[_][]const u8{"user/progFG.c"}, + }, + // progH + Prog{ + .name = "progH", + .source = &[_][]const u8{"user/progH.c"}, + }, + // progI + Prog{ + .name = "progI", + .source = &[_][]const u8{"user/progI.c"}, + }, + // progJ + Prog{ + .name = "progJ", + .source = &[_][]const u8{"user/progJ.c"}, + }, + // progKL + Prog{ + .name = "progKL", + .source = &[_][]const u8{"user/progKL.c"}, + }, + // progKL + Prog{ + .name = "progKL", + .source = &[_][]const u8{"user/progKL.c"}, + }, + // progMN + Prog{ + .name = "progMN", + .source = &[_][]const u8{"user/progMN.c"}, + }, + // progP + Prog{ + .name = "progP", + .source = &[_][]const u8{"user/progP.c"}, + }, + // progQ + Prog{ + .name = "progQ", + .source = &[_][]const u8{"user/progQ.c"}, + }, + // progR + Prog{ + .name = "progR", + .source = &[_][]const u8{"user/progR.c"}, + }, + // progS + Prog{ + .name = "progS", + .source = &[_][]const u8{"user/progS.c"}, + }, + // progTUV + Prog{ + .name = "progTUV", + .source = &[_][]const u8{"user/progTUV.c"}, + }, + // progW + Prog{ + .name = "progW", + .source = &[_][]const u8{"user/progW.c"}, + }, + // progX + Prog{ + .name = "progX", + .source = &[_][]const u8{"user/progX.c"}, + }, + // progY + Prog{ + .name = "progY", + .source = &[_][]const u8{"user/progY.c"}, + }, + // progZ + Prog{ + .name = "progZ", + .source = &[_][]const u8{"user/progZ.c"}, + }, + // shell + Prog{ + .name = "shell", + .source = &[_][]const u8{"user/shell.c"}, + }, +}; + +const AddSourcesOpts = struct { exe: *std.Build.Step.Compile, sources: []const []const []const u8, c_flags: []const []const u8 }; + +fn add_sources(b: *std.Build, opts: AddSourcesOpts) void { + // add asm and c source files + for (opts.sources) |source| { + for (source) |file| { + if (std.mem.endsWith(u8, file, ".c")) { + // c file + opts.exe.addCSourceFile(.{ .file = b.path(file), .flags = opts.c_flags }); + } else { + // assembly file + opts.exe.addAssemblyFile(b.path(file)); + } + } + } +} + +const BuildKernBinaryOpts = struct { + name: []const u8, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + sources: []const []const []const u8, + linker: ?[]const u8 = null, + entry: []const u8 = "_start", +}; + +fn build_kern_binary(b: *std.Build, opts: BuildKernBinaryOpts) void { + // create compile step + const exe = b.addExecutable(.{ + .name = opts.name, + .target = opts.target, + .optimize = opts.optimize, + .strip = true, + }); + + // add include path + exe.addIncludePath(b.path("include/")); + exe.entry = .{ .symbol_name = opts.entry }; + + // add asm and c source files + add_sources(b, .{ + .exe = exe, + .sources = opts.sources, + .c_flags = c_flags, + }); + + if (opts.linker != null) { + exe.setLinkerScript(b.path(opts.linker.?)); + } + + const step = b.addInstallArtifact(exe, .{ .dest_dir = .{ + .override = .{ .custom = "../bin" }, + } }); + b.getInstallStep().dependOn(&step.step); +} + +const BuildNativeBinaryOpts = struct { + name: []const u8, + optimize: std.builtin.OptimizeMode, + sources: []const []const []const u8, +}; + +fn build_native_binary(b: *std.Build, opts: BuildNativeBinaryOpts) void { + // create compile step + const exe = b.addExecutable(.{ + .name = opts.name, + .target = b.graph.host, + .optimize = opts.optimize, + .strip = true, + }); + + // include libc + exe.linkLibC(); + + // add asm and c source files + add_sources(b, .{ + .exe = exe, + .sources = opts.sources, + .c_flags = &.{}, + }); + + const step = b.addInstallArtifact(exe, .{ .dest_dir = .{ + .override = .{ .custom = "../bin" }, + } }); + b.getInstallStep().dependOn(&step.step); +} + +pub fn build(b: *std.Build) !void { + + // context + const target = b.standardTargetOptions(.{ + .default_target = .{ + .cpu_arch = std.Target.Cpu.Arch.x86, + .os_tag = std.Target.Os.Tag.freestanding, + .abi = std.Target.Abi.gnu, + .ofmt = std.Target.ObjectFormat.elf, + }, + }); + const optimize = std.builtin.OptimizeMode.ReleaseSmall; + + // boot + build_kern_binary(b, .{ + .name = "boot", + .target = target, + .optimize = optimize, + .sources = &.{ + boot_src, + }, + .linker = "boot/boot.ld", + .entry = "bootentry", + }); + + // kernel + build_kern_binary(b, .{ + .name = "kernel", + .target = target, + .optimize = optimize, + .sources = &.{ + kernel_src, + lib_src, + }, + .linker = "kernel/kernel.ld", + }); + + // user_progs + for (user_progs) |prog| { + build_kern_binary(b, .{ + .name = prog.name, + .target = target, + .optimize = optimize, + .sources = &.{ + prog.source, + lib_src, + ulib_src, + }, + }); + } + + // util_progs + for (util_progs) |prog| { + build_native_binary(b, .{ + .name = prog.name, + .optimize = optimize, + .sources = &.{ + prog.source, + }, + }); + } +} diff --git a/kernel/sio.c b/kernel/sio.c index a5c7b75..fc20435 100644 --- a/kernel/sio.c +++ b/kernel/sio.c @@ -106,7 +106,7 @@ static uint8_t ier; // queue for read-blocked processes #ifdef QNAME -QTYPE QNAME; +extern QTYPE QNAME; #endif /* @@ -253,7 +253,7 @@ static void sio_isr( int vector, int ecode ) { sprint( b256, "sio isr: IIR %02x\n", ((uint32_t) iir) & 0xff ); PANIC( 0, b256 ); } - + } // should never reach this point! @@ -329,7 +329,7 @@ void sio_init( void ) { */ outb( UA4_LCR, UA4_LCR_WLS_8 | UA4_LCR_1_STOP_BIT | UA4_LCR_NO_PARITY ); - + /* ** Set the ISEN bit to enable the interrupt request signal, ** and the DTR and RTS bits to enable two-way communication. @@ -450,7 +450,7 @@ int sio_readc( void ) { // assume there is no character available ch = -1; - // + // // If there is a character, return it // @@ -675,7 +675,7 @@ void sio_dump( bool_t full ) { if( incount ) { cio_puts( "SIO input queue: \"" ); - ptr = innext; + ptr = innext; for( n = 0; n < incount; ++n ) { put_char_or_code( *ptr++ ); } @@ -685,7 +685,7 @@ void sio_dump( bool_t full ) { if( outcount ) { cio_puts( "SIO output queue: \"" ); cio_puts( " ot: \"" ); - ptr = outnext; + ptr = outnext; for( n = 0; n < outcount; ++n ) { put_char_or_code( *ptr++ ); }