From f7e058f9481eaf9670def5bea0fe83819d290510 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:15:17 +0530 Subject: [PATCH 01/16] fix: improve host runner errors and test CLI parsing --- src/main.rs | 132 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 24 deletions(-) diff --git a/src/main.rs b/src/main.rs index ebc462f..e0c260a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,41 +2,125 @@ use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source}; use std::env; use std::process::{exit, Command}; +const USAGE: &str = "Usage: cargo run -- [uefi|bios]"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum BootMode { + Uefi, + Bios, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum CliAction { + Run(BootMode), + Help, +} + +fn parse_args(args: &[String]) -> Result { + match args { + [] => Err("missing boot mode"), + [arg] if arg.eq_ignore_ascii_case("-h") || arg.eq_ignore_ascii_case("--help") => { + Ok(CliAction::Help) + } + [arg] if arg.eq_ignore_ascii_case("uefi") => Ok(CliAction::Run(BootMode::Uefi)), + [arg] if arg.eq_ignore_ascii_case("bios") => Ok(CliAction::Run(BootMode::Bios)), + [_] => Err("unknown boot mode; expected 'uefi' or 'bios'"), + _ => Err("expected exactly one argument"), + } +} + fn main() { - let uefi_path = env!("UEFI_PATH"); - let bios_path = env!("BIOS_PATH"); - let args: Vec = env::args().collect(); - - let uefi = match args.get(1).map(|s| s.to_lowercase()).as_deref() { - Some("uefi") => true, - Some("bios") => false, - Some("-h") | Some("--help") => { - println!("Usage: cargo run -- [uefi|bios]"); - exit(0); + let args: Vec = env::args().skip(1).collect(); + let mode = match parse_args(&args) { + Ok(CliAction::Run(mode)) => mode, + Ok(CliAction::Help) => { + println!("{USAGE}"); + return; } - _ => { - eprintln!("Usage: cargo run -- [uefi|bios]"); - exit(1); + Err(message) => { + eprintln!("rustalis: {message}\n{USAGE}"); + exit(2); } }; + let uefi_path = env!("UEFI_PATH"); + let bios_path = env!("BIOS_PATH"); + let mut cmd = Command::new("qemu-system-x86_64"); cmd.args(["-serial", "mon:stdio", "-display", "none"]); - if uefi { - let prebuilt = Prebuilt::fetch(Source::LATEST, "target/ovmf") - .expect("failed to obtain OVMF firmware"); - let code = prebuilt.get_file(Arch::X64, FileType::Code); - let vars = prebuilt.get_file(Arch::X64, FileType::Vars); - cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}")); - cmd.arg("-drive").arg(format!("if=pflash,format=raw,unit=0,file={},readonly=on", code.display())); - cmd.arg("-drive").arg(format!("if=pflash,format=raw,unit=1,file={},snapshot=on", vars.display())); - } else { - cmd.arg("-drive").arg(format!("format=raw,file={bios_path}")); + match mode { + BootMode::Uefi => { + let prebuilt = Prebuilt::fetch(Source::LATEST, "target/ovmf").unwrap_or_else(|error| { + eprintln!( + "rustalis: failed to obtain OVMF firmware ({error:?}). Check network access or the cached firmware in target/ovmf." + ); + exit(1); + }); + let code = prebuilt.get_file(Arch::X64, FileType::Code); + let vars = prebuilt.get_file(Arch::X64, FileType::Vars); + cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}")); + cmd.arg("-drive").arg(format!( + "if=pflash,format=raw,unit=0,file={},readonly=on", + code.display() + )); + cmd.arg("-drive").arg(format!( + "if=pflash,format=raw,unit=1,file={},snapshot=on", + vars.display() + )); + } + BootMode::Bios => { + cmd.arg("-drive").arg(format!("format=raw,file={bios_path}")); + } } - let status = cmd.status().expect("failed to start qemu-system-x86_64"); + let status = cmd.status().unwrap_or_else(|error| { + eprintln!( + "rustalis: failed to start qemu-system-x86_64: {error}. Install QEMU and ensure qemu-system-x86_64 is on PATH." + ); + exit(1); + }); if !status.success() { exit(status.code().unwrap_or(1)); } } + +#[cfg(test)] +mod tests { + use super::{BootMode, CliAction, parse_args}; + + fn args(values: &[&str]) -> Vec { + values.iter().map(|value| (*value).to_owned()).collect() + } + + #[test] + fn accepts_bios_and_uefi_modes_case_insensitively() { + assert_eq!( + parse_args(&args(&["uefi"])), + Ok(CliAction::Run(BootMode::Uefi)) + ); + assert_eq!( + parse_args(&args(&["BIOS"])), + Ok(CliAction::Run(BootMode::Bios)) + ); + } + + #[test] + fn accepts_help_flags() { + assert_eq!(parse_args(&args(&["--help"])), Ok(CliAction::Help)); + assert_eq!(parse_args(&args(&["-h"])), Ok(CliAction::Help)); + } + + #[test] + fn rejects_missing_unknown_and_extra_arguments() { + assert_eq!(parse_args(&args(&[])), Err("missing boot mode")); + assert_eq!( + parse_args(&args(&["pxe"])), + Err("unknown boot mode; expected 'uefi' or 'bios'") + ); + assert_eq!( + parse_args(&args(&["uefi", "extra"])), + Err("expected exactly one argument") + ); + } +} From 87cd8f63869569567e17dd0ba9a9e5779050247d Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:17:14 +0530 Subject: [PATCH 02/16] style: apply rustfmt formatting to workspace sources --- src/main.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index e0c260a..0ae5194 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source}; use std::env; -use std::process::{exit, Command}; +use std::process::{Command, exit}; const USAGE: &str = "Usage: cargo run -- [uefi|bios]"; @@ -59,7 +59,8 @@ fn main() { }); let code = prebuilt.get_file(Arch::X64, FileType::Code); let vars = prebuilt.get_file(Arch::X64, FileType::Vars); - cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}")); + cmd.arg("-drive") + .arg(format!("format=raw,file={uefi_path}")); cmd.arg("-drive").arg(format!( "if=pflash,format=raw,unit=0,file={},readonly=on", code.display() @@ -70,7 +71,8 @@ fn main() { )); } BootMode::Bios => { - cmd.arg("-drive").arg(format!("format=raw,file={bios_path}")); + cmd.arg("-drive") + .arg(format!("format=raw,file={bios_path}")); } } From 91f2f8881cb3948a7cd8f649c1d09c438921c86a Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:17:27 +0530 Subject: [PATCH 03/16] style: apply rustfmt formatting to workspace sources --- kernel/src/fs.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index f8e9448..a552b1b 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -59,7 +59,10 @@ impl RamFs { if self.find(path).is_some() { return Err(FsError::AlreadyExists); } - let slot = self.entries.iter().position(|entry| !entry.used) + let slot = self + .entries + .iter() + .position(|entry| !entry.used) .ok_or(FsError::NoFreeSlots)?; let entry = &mut self.entries[slot]; entry.path[..path.len()].copy_from_slice(path.as_bytes()); @@ -100,8 +103,11 @@ impl RamFs { /// Return the path of the Nth occupied slot, for simple directory listings. pub fn path_at(&self, occupied_index: usize) -> Option<&str> { - self.entries.iter().filter(|entry| entry.used) - .nth(occupied_index).map(Entry::path) + self.entries + .iter() + .filter(|entry| entry.used) + .nth(occupied_index) + .map(Entry::path) } pub fn file_count(&self) -> usize { @@ -109,7 +115,9 @@ impl RamFs { } fn find(&self, path: &str) -> Option { - self.entries.iter().position(|entry| entry.used && entry.path() == path) + self.entries + .iter() + .position(|entry| entry.used && entry.path() == path) } } @@ -120,7 +128,10 @@ fn validate_path(path: &str) -> Result<&str, FsError> { if path.len() > MAX_PATH_BYTES { return Err(FsError::PathTooLong); } - if path.split('/').any(|part| part.is_empty() || part == "." || part == "..") { + if path + .split('/') + .any(|part| part.is_empty() || part == "." || part == "..") + { return Err(FsError::InvalidPath); } Ok(path) From cbd214978bad00424a7d2c7c0e1d55417a13476d Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:17:29 +0530 Subject: [PATCH 04/16] style: apply rustfmt formatting to workspace sources --- kernel/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 649d844..971a299 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -use bootloader_api::{entry_point, BootInfo}; +use bootloader_api::{BootInfo, entry_point}; use core::panic::PanicInfo; mod arch; From 128ab89b21a92852493aa0bd9ce877bec5682071 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:17:32 +0530 Subject: [PATCH 05/16] style: apply rustfmt formatting to workspace sources --- kernel/src/arch/x86_64/interrupts.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index b24d1ac..04d5059 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -14,5 +14,7 @@ pub struct InterruptStackFrame { pub fn init() { // SAFETY: maskable interrupts stay disabled until an IDT and handlers // have been installed. This prevents delivery to an incomplete table. - unsafe { asm!("cli", options(nomem, nostack, preserves_flags)); } + unsafe { + asm!("cli", options(nomem, nostack, preserves_flags)); + } } From e98243cd49924426d1d519be334680f46f428626 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:18:52 +0530 Subject: [PATCH 06/16] fix: pin OVMF firmware release for reproducible UEFI runs --- src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0ae5194..96cb4e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use std::env; use std::process::{Command, exit}; const USAGE: &str = "Usage: cargo run -- [uefi|bios]"; +// Pin the firmware release so a fresh checkout does not silently change boot firmware. +const OVMF_SOURCE: Source = Source::EDK2_STABLE202508_R1; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum BootMode { @@ -51,7 +53,7 @@ fn main() { match mode { BootMode::Uefi => { - let prebuilt = Prebuilt::fetch(Source::LATEST, "target/ovmf").unwrap_or_else(|error| { + let prebuilt = Prebuilt::fetch(OVMF_SOURCE, "target/ovmf").unwrap_or_else(|error| { eprintln!( "rustalis: failed to obtain OVMF firmware ({error:?}). Check network access or the cached firmware in target/ovmf." ); From ce53e084f0da5c37728cec358421758f089a4453 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:18:55 +0530 Subject: [PATCH 07/16] test: harden RAM filesystem edge cases From 8b624bda35b400905f853a8476cfe4e017cd76c8 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:18:58 +0530 Subject: [PATCH 08/16] test: verify memory map aggregation --- kernel/src/memory/mod.rs | 84 +++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/kernel/src/memory/mod.rs b/kernel/src/memory/mod.rs index 06d5c92..e33903d 100644 --- a/kernel/src/memory/mod.rs +++ b/kernel/src/memory/mod.rs @@ -13,28 +13,82 @@ pub struct MemorySummary { } pub fn summarize(regions: &MemoryRegions) -> MemorySummary { - let mut usable_bytes = 0; - let mut reserved_bytes = 0; - - for region in regions.iter() { - let size = region.end.saturating_sub(region.start); - match region.kind { - MemoryRegionKind::Usable => usable_bytes = usable_bytes.saturating_add(size), - _ => reserved_bytes = reserved_bytes.saturating_add(size), + summarize_regions( + regions + .iter() + .map(|region| (region.kind, region.start, region.end)), + ) +} + +fn summarize_regions( + regions: impl IntoIterator, +) -> MemorySummary { + let mut summary = MemorySummary { + usable_bytes: 0, + reserved_bytes: 0, + region_count: 0, + }; + + for (kind, start, end) in regions { + let size = end.saturating_sub(start); + match kind { + MemoryRegionKind::Usable => { + summary.usable_bytes = summary.usable_bytes.saturating_add(size); + } + _ => { + summary.reserved_bytes = summary.reserved_bytes.saturating_add(size); + } } + summary.region_count += 1; } - MemorySummary { - usable_bytes, - reserved_bytes, - region_count: regions.iter().count(), - } + summary } #[cfg(test)] mod tests { + use super::{MemoryRegionKind, MemorySummary, summarize_regions}; + #[test] - fn saturating_arithmetic_is_safe_for_memory_counters() { - assert_eq!(u64::MAX.saturating_add(1), u64::MAX); + fn summarizes_usable_reserved_and_region_count() { + let summary = summarize_regions([ + (MemoryRegionKind::Usable, 0, 1024), + (MemoryRegionKind::Bootloader, 1024, 1536), + (MemoryRegionKind::Usable, 4096, 8192), + ]); + + assert_eq!( + summary, + MemorySummary { + usable_bytes: 5120, + reserved_bytes: 512, + region_count: 3, + } + ); + } + + #[test] + fn empty_memory_map_has_zero_totals() { + assert_eq!( + summarize_regions(core::iter::empty::<(MemoryRegionKind, u64, u64)>()), + MemorySummary { + usable_bytes: 0, + reserved_bytes: 0, + region_count: 0, + } + ); + } + + #[test] + fn invalid_ranges_and_overflow_are_handled_safely() { + let summary = summarize_regions([ + (MemoryRegionKind::Usable, 10, 5), + (MemoryRegionKind::Usable, 0, u64::MAX), + (MemoryRegionKind::Usable, 0, 1), + ]); + + assert_eq!(summary.usable_bytes, u64::MAX); + assert_eq!(summary.reserved_bytes, 0); + assert_eq!(summary.region_count, 3); } } From e2fba00f218596d16cf5a4c09fb0db0c99ef1fcb Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:19:01 +0530 Subject: [PATCH 09/16] ci: gate boot marker on successful smoke test --- kernel/src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 971a299..9827d45 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -34,10 +34,15 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { logger::info("RAM filesystem initialized; /welcome.txt verified"); logger::info_u64("RAM filesystem files", ramfs.file_count() as u64); } else { - logger::error("RAM filesystem smoke test failed"); + logger::error("RAM filesystem smoke test failed; boot success withheld"); + loop { + core::hint::spin_loop(); + } } - logger::info("Rustalis booted successfully."); + // This marker is a CI/debugging contract: emit it only after early init and + // the RAM filesystem smoke test have completed successfully. + logger::info("[BOOT_OK] Rustalis booted successfully."); loop { core::hint::spin_loop(); } From 6f94cad21c36d11c5ff4170e2497a3e3819ecd14 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:19:04 +0530 Subject: [PATCH 10/16] docs: link troubleshooting guide --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ec3c6a9..7a86423 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,8 @@ On Windows PowerShell: cargo run -- uefi ``` +For common toolchain, QEMU, firmware, and boot troubleshooting, see [docs/troubleshooting.md](docs/troubleshooting.md). + ## Contributing Rustalis is intended to be a genuine open-source collaboration project. Start with the issue tracker before implementing a large subsystem. From 50d054be4d73b2befdd8d2dd32c972b4984b39b4 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:19:07 +0530 Subject: [PATCH 11/16] ci: add bounded QEMU boot test --- .github/workflows/rust.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 307acca..beab609 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,7 +28,7 @@ jobs: run: cargo test --workspace qemu: - name: Build boot image + name: Build and boot smoke test runs-on: ubuntu-latest needs: check steps: @@ -40,3 +40,23 @@ jobs: run: sudo apt-get update && sudo apt-get install -y qemu-system-x86 - name: Build boot image run: cargo build --verbose + - name: Boot BIOS image and assert serial success marker + shell: bash + run: | + set +e + timeout --signal=TERM --kill-after=5s 20s cargo run -- bios > qemu.log 2>&1 + status=$? + set -e + cat qemu.log + + if ! grep -Fq "[BOOT_OK] Rustalis booted successfully." qemu.log; then + echo "Kernel did not emit the expected boot-success marker." + exit 1 + fi + + # A healthy kernel intentionally remains in its idle loop; timeout + # is therefore expected after the marker has been observed. + if [ "$status" -ne 0 ] && [ "$status" -ne 124 ]; then + echo "QEMU runner exited unexpectedly with status $status." + exit "$status" + fi From 3ec5d8cfaea85fb08d4ea55252ee985ef7fd3fa3 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:19:10 +0530 Subject: [PATCH 12/16] style: format interrupt initialization From 5b66cd096685c55590a247865937a5612b964b62 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:19:22 +0530 Subject: [PATCH 13/16] test: cover RAM filesystem limits and path boundaries --- kernel/src/fs.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index a552b1b..3dbb3a6 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -3,8 +3,10 @@ //! This is a RAM-backed prototype, not persistent storage. It deliberately avoids //! allocation so it can be used before the kernel heap and block-device layer exist. -const MAX_PATH_BYTES: usize = 64; -const MAX_FILE_BYTES: usize = 256; +/// Maximum path length in bytes, including the leading slash. +pub const MAX_PATH_BYTES: usize = 64; +/// Maximum file content size in bytes. +pub const MAX_FILE_BYTES: usize = 256; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum FsError { @@ -17,6 +19,9 @@ pub enum FsError { } /// A small, allocation-free filesystem suitable for kernel bring-up. +/// +/// Each slot stores a fixed-size path and content buffer. On x86_64, an entry +/// currently occupies approximately 344 bytes; exact layout is compiler-dependent. pub struct RamFs { entries: [Entry; FILES], } @@ -139,7 +144,10 @@ fn validate_path(path: &str) -> Result<&str, FsError> { #[cfg(test)] mod tests { - use super::{FsError, RamFs}; + extern crate std; + + use super::{FsError, MAX_FILE_BYTES, MAX_PATH_BYTES, RamFs}; + use std::format; #[test] fn create_write_read_and_remove() { @@ -167,7 +175,57 @@ mod tests { fs.create("/a").unwrap(); assert_eq!(fs.create("/b"), Err(FsError::NoFreeSlots)); fs.write("/a", b"ok").unwrap(); - assert_eq!(fs.write("/a", &[0; 257]), Err(FsError::FileTooLarge)); + assert_eq!( + fs.write("/a", &[0; MAX_FILE_BYTES + 1]), + Err(FsError::FileTooLarge) + ); assert_eq!(fs.read("/a").unwrap(), b"ok"); } + + #[test] + fn handles_empty_files_and_content_replacement_boundaries() { + let mut fs = RamFs::<1>::new(); + fs.create("/data").unwrap(); + assert_eq!(fs.read("/data").unwrap(), b""); + + let maximum = [b'x'; MAX_FILE_BYTES]; + fs.write("/data", &maximum).unwrap(); + assert_eq!(fs.read("/data").unwrap(), &maximum); + + fs.write("/data", b"short").unwrap(); + assert_eq!(fs.read("/data").unwrap(), b"short"); + + assert_eq!( + fs.write("/data", &[b'z'; MAX_FILE_BYTES + 1]), + Err(FsError::FileTooLarge) + ); + assert_eq!(fs.read("/data").unwrap(), b"short"); + } + + #[test] + fn rejects_malformed_paths_without_consuming_slots() { + let mut fs = RamFs::<2>::new(); + fs.create("/keep").unwrap(); + + for path in ["relative", "/", "/trailing/", "/a//b", "/./a", "/a/../b"] { + assert_eq!(fs.create(path), Err(FsError::InvalidPath), "path: {path}"); + } + + assert_eq!(fs.file_count(), 1); + assert_eq!(fs.read("/keep").unwrap(), b""); + assert_eq!(fs.create("/b"), Ok(())); + assert_eq!(fs.file_count(), 2); + } + + #[test] + fn accepts_maximum_path_and_rejects_one_byte_over() { + let mut fs = RamFs::<2>::new(); + let maximum = format!("/{}", "a".repeat(MAX_PATH_BYTES - 1)); + let too_long = format!("/{}", "a".repeat(MAX_PATH_BYTES)); + + assert_eq!(maximum.len(), MAX_PATH_BYTES); + assert_eq!(fs.create(&maximum), Ok(())); + assert_eq!(fs.create(&too_long), Err(FsError::PathTooLong)); + assert_eq!(fs.file_count(), 1); + } } From 6dfa46810db3b15fde67ff00428cb24252ed35a3 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:19:25 +0530 Subject: [PATCH 14/16] docs: add troubleshooting and firmware reproducibility guide --- docs/troubleshooting.md | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/troubleshooting.md diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..57f3f9e --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,57 @@ +# Troubleshooting Rustalis + +This guide separates host setup failures from kernel boot failures. Rustalis is experimental; a successful image build does not prove that the kernel boots correctly. + +## Prerequisites + +- Use the toolchain specified by `rust-toolchain.toml` (Rust nightly). +- Install Cargo and Git. +- Install QEMU with the `qemu-system-x86_64` executable available on `PATH`. +- For UEFI runs, the runner pins OVMF to EDK2 stable 202508-r1 through `Source::EDK2_STABLE202508_R1`. The `ovmf-prebuilt` crate validates the downloaded archive against its embedded SHA-256 and caches firmware under `target/ovmf`. + +Check the toolchain and QEMU executable: + +```sh +rustc --version +cargo --version +qemu-system-x86_64 --version +``` + +## Build or test failures + +Run the checks individually to identify the failing stage: + +```sh +cargo fmt --all -- --check +cargo check --workspace +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +cargo build +``` + +- **Toolchain/target errors:** confirm Rustup selected the repository's toolchain and that the target named in the error is installed. +- **Dependency errors:** check network access and Cargo's registry cache, then retry the failing command. +- **Formatting errors:** run `cargo fmt --all` and inspect the resulting diff before committing. + +## Host runner failures + +Run either supported mode: + +```sh +cargo run -- uefi +cargo run -- bios +``` + +- **QEMU cannot start:** install the QEMU x86_64 system emulator and ensure `qemu-system-x86_64` is on `PATH`. +- **UEFI firmware acquisition fails:** check network access and write permissions for the repository's `target/ovmf` cache. BIOS mode does not require OVMF. +- **Invalid command:** use exactly one mode argument, `uefi` or `bios`; use `--help` for usage. + +## Kernel boot failures + +The QEMU runner configures serial output and disables the graphical display. Read the serial log first: + +- If no kernel log appears, investigate image creation, boot firmware, and QEMU configuration. +- If initialization logs appear but the final success message is absent, investigate the last subsystem log before the failure. +- A successful `cargo build` only verifies image construction. Treat runtime boot as verified only after the kernel actually runs and its expected serial output is observed. + +When reporting a failure, include the command, host OS, Rust toolchain version, QEMU version, and complete relevant serial/error output. Do not include secrets or private environment values. From 92a029d4dd8b18c232ff5f64db5dd0966859a05e Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:20:21 +0530 Subject: [PATCH 15/16] ci: install rust-src for bootloader build-std --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index beab609..a980740 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: - components: llvm-tools-preview + components: llvm-tools-preview, rust-src - name: Install QEMU run: sudo apt-get update && sudo apt-get install -y qemu-system-x86 - name: Build boot image From 56f090f4b40e105ad1c27a479d6e84db0114231a Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Sun, 25 Oct 2026 08:20:28 +0530 Subject: [PATCH 16/16] build: install rust-src for bootloader build-std --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5135f42..8ba015f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] channel = "nightly" -components = ["llvm-tools-preview", "rustfmt", "clippy"] +components = ["llvm-tools-preview", "rustfmt", "clippy", "rust-src"] targets = ["x86_64-unknown-none", "x86_64-unknown-uefi"] profile = "minimal"