diff --git a/.gitignore b/.gitignore index 849a50a..011a068 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,7 @@ .idea/ # Zig -zig-cache/ \ No newline at end of file +zig-cache + +# Build dir +bin/* diff --git a/README.md b/README.md index b09249f..c1560af 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,26 @@ -Compile: zig build-exe kmain.zig -target i386-freestanding --linker-script link.ld +# Pluto -Run: qemu-system-i386 -kernel kmain -curses +## Build +Requires *xorriso* and the grub tools (such as *grub-mkrescue*). +``` +mkdir -p bin/kernel +mkdir -p bin/iso/boot/grub +zig build +``` -To exit qemu: Esc + 2, type `quit` \ No newline at end of file +Note that the `mkdir` invocations are only needed once. See `zig build --help` for a list of options. + +## Run +``` +zig build run +``` +To debug the kernel: +``` +zig build run -Ddebug=true +zig build debug +``` + +# Test +``` +zig build test +``` diff --git a/build.zig b/build.zig index c97c285..32589c3 100644 --- a/build.zig +++ b/build.zig @@ -1,43 +1,124 @@ // Zig version: 0.4.0 const Builder = @import("std").build.Builder; +const Step = @import("std").build.Step; const builtin = @import("builtin"); -const Array = @import("std").ArrayList; +const std = @import("std"); +const ArrayList = std.ArrayList; +const warn = std.debug.warn; +const mem = std.mem; + +const src_files = [][]const u8{"kernel/kmain"}; + +fn concat(allocator: *std.mem.Allocator, str: []const u8, str2: []const u8) !std.Buffer { + var b = try std.Buffer.init(allocator, str); + try b.append(str2); + return b; +} pub fn build(b: *Builder) void { - const kernel_out_dir = "bin/kernel"; - const kernel_src = "src/kernel/"; + const debug = b.option(bool, "debug", "build with debug symbols / make qemu wait for a debug connection") orelse false; + var build_path = b.option([]const u8, "build-path", "path to build to") orelse "bin"; + var src_path = b.option([]const u8, "source-path", "path to source") orelse "src"; + var target = b.option([]const u8, "target", "target to build/run for") orelse "x86"; + const builtin_target = if (mem.eql(u8, target, "x86")) builtin.Arch.i386 else unreachable; - var kernel = b.addExecutable("pluto.elf", kernel_src ++ "kmain.zig"); - //kernel.addAssemblyFile(kernel_src ++ "start.s"); + const iso_path = concat(b.allocator, build_path, "/pluto.iso") catch unreachable; - kernel.setOutputDir(kernel_out_dir); - kernel.setBuildMode(b.standardReleaseOptions()); - kernel.setTarget(builtin.Arch.i386, builtin.Os.freestanding, builtin.Abi.gnu); - kernel.setLinkerScriptPath("link.ld"); + var objects_steps = buildObjects(b, builtin_target, build_path, src_path); + var link_step = buildLink(b, builtin_target, build_path); + const iso_step = buildISO(b, build_path, iso_path.toSlice()); - const run_objcopy = b.addSystemCommand([][]const u8 { - "objcopy", "-O", "binary", "-S", kernel.getOutputPath(), kernel_out_dir ++ "/pluto.bin", + for (objects_steps.toSlice()) |step| b.default_step.dependOn(step); + b.default_step.dependOn(link_step); + for (iso_step.toSlice()) |step| b.default_step.dependOn(step); + + buildRun(b, builtin_target, build_path, iso_path.toSlice(), debug); + buildDebug(b); + buildTest(b, src_path); +} + +fn buildTest(b: *Builder, src_path: []const u8) void { + const step = b.step("test", "Run all tests"); + const src_path2 = concat(b.allocator, src_path, "/") catch unreachable; + for (src_files) |file| { + var file_src = concat(b.allocator, src_path2.toSlice(), file) catch unreachable; + file_src.append(".zig") catch unreachable; + const tst = b.addTest(file_src.toSlice()); + step.dependOn(&tst.step); + } +} + +fn buildDebug(b: *Builder) void { + const step = b.step("debug", "Debug with gdb"); + const cmd = b.addSystemCommand([][]const u8{ + "gdb", + "-ex", + "symbol-file bin/iso/boot/pluto.elf", + "-ex", + "target remote localhost:1234", }); - run_objcopy.step.dependOn(&kernel.step); + step.dependOn(&cmd.step); +} - b.default_step.dependOn(&run_objcopy.step); +fn buildRun(b: *Builder, target: builtin.Arch, build_path: []const u8, iso_path: []const u8, debug: bool) void { + const step = b.step("run", "Run with qemu"); + const qemu = if (target == builtin.Arch.i386) "qemu-system-i386" else unreachable; + var qemu_flags = ArrayList([]const u8).init(b.allocator); + qemu_flags.appendSlice([][]const u8{ + qemu, + "-cdrom", + iso_path, + "-boot", + "d", + "-serial", + "stdio", + }) catch unreachable; + if (debug) + qemu_flags.appendSlice([][]const u8{ + "-s", + "-S", + }) catch unreachable; + const cmd = b.addSystemCommand(qemu_flags.toSlice()); + step.dependOn(&cmd.step); +} - const run_qemu = b.addSystemCommand([][]const u8 { - "qemu-system-i386", - "-display", "curses", - "-kernel", kernel.getOutputPath(), - }); +fn buildISO(b: *Builder, build_path: []const u8, iso_path: []const u8) ArrayList(*Step) { + const grub_build_path = concat(b.allocator, build_path, "/iso/boot/") catch unreachable; + const iso_dir_path = concat(b.allocator, build_path, "/iso") catch unreachable; + const grub_cmd = b.addSystemCommand([][]const u8{ "cp", "-r", "grub", grub_build_path.toSlice() }); + const iso_cmd = b.addSystemCommand([][]const u8{ "grub-mkrescue", "-o", iso_path, iso_dir_path.toSlice() }); + var steps = ArrayList(*Step).init(b.allocator); + steps.append(&grub_cmd.step) catch unreachable; + steps.append(&iso_cmd.step) catch unreachable; + return steps; +} - const run_qemu_debug = b.addSystemCommand([][]const u8 { - "qemu-system-i386", - "-display", "curses", - "-kernel", kernel.getOutputPath(), - "-s", "-S", - }); +fn buildLink(b: *Builder, target: builtin.Arch, build_path: []const u8) *Step { + const exec = b.addExecutable("pluto.elf", null); + const elf_path = concat(b.allocator, build_path, "/iso/boot") catch unreachable; + exec.setOutputDir(elf_path.toSlice()); + exec.setLinkerScriptPath("link.ld"); + exec.setTarget(target, builtin.Os.freestanding, builtin.Abi.gnu); + for (src_files) |file| { + var file_obj = concat(b.allocator, build_path, "/") catch unreachable; + file_obj.append(file) catch unreachable; + file_obj.append(".o") catch unreachable; + exec.addObjectFile(file_obj.toSlice()); + } + return &exec.step; +} - run_qemu.step.dependOn(&kernel.step); - //run_qemu_debug.step.dependOn(&kernel.step); - - b.default_step.dependOn(&run_qemu.step); -} \ No newline at end of file +fn buildObjects(b: *Builder, target: builtin.Arch, build_path: []const u8, src_path: []const u8) ArrayList(*Step) { + var objects = ArrayList(*Step).init(b.allocator); + const src_path2 = concat(b.allocator, src_path, "/") catch unreachable; + for (src_files) |file| { + var file_src = concat(b.allocator, src_path2.toSlice(), file) catch unreachable; + file_src.append(".zig") catch unreachable; + const obj = b.addObject(file, file_src.toSlice()); + obj.setOutputDir(build_path); + obj.setTarget(target, builtin.Os.freestanding, builtin.Abi.gnu); + objects.append(&obj.step) catch unreachable; + } + return objects; +} diff --git a/grub/grub.cfg b/grub/grub.cfg new file mode 100644 index 0000000..8d3fd21 --- /dev/null +++ b/grub/grub.cfg @@ -0,0 +1,7 @@ +set timeout=0 # How long grub should wait before booting default menu entry +set default=0 + +menuentry "pluto" { + multiboot /boot/pluto.elf + boot +} diff --git a/grub/stage2_eltorito b/grub/stage2_eltorito new file mode 100644 index 0000000..817dd07 Binary files /dev/null and b/grub/stage2_eltorito differ