Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ rustflags = [
"-C", "relocation-model=static",
"-C", "no-redzone=yes",
"-C", "force-frame-pointers=yes",
"-C", "link-arg=-z", "-C", "link-arg=separate-loadable-segments",
"-C", "link-arg=--image-base=0x400000",
]

# `build-std` is intentionally NOT in [unstable] here — it would apply
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ members = [
# `host-fuzz` feature; pulling it into the workspace would force the host
# feature surface on the kernel-target build graph. `cargo xtask fuzz ext2`
# drives it directly.
exclude = ["tests/pjdfstest", "kernel/fuzz"]
exclude = ["tests/pjdfstest", "kernel/fuzz", "userspace/std_hello"]
resolver = "2"

[workspace.package]
Expand Down
12 changes: 12 additions & 0 deletions kernel/src/mem/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,18 @@ pub fn load_user_elf_with_vmas(
// segments. Returns `(segment_count, page_aligned_image_end)`.
let (segments, mut image_end) = register_demand_vmas(bytes, parsed, 0, address_space)?;

// Eagerly map all PT_LOAD segment pages into the PML4. The VMAs
// above provide the fork-compatible VMA tree, while the eager
// mapping avoids a demand-paging bug where certain pages are served
// with incorrect (zero-filled) content (#852 workaround). The
// demand-paging path (`FileObject::fault`) will find these pages
// already present and never fire for the init binary.
for seg in parsed.load_segments() {
if let Err((e, _)) = map_user_segment(bytes, seg, _pml4, 0) {
return Err(e);
}
}

// Allocate the static TLS block when PT_TLS is present. The block is
// placed at `image_end` (page-aligned), registered as a writable AnonObject
// VMA so fork copies it, and the TCB address is recorded for `MSR_FS_BASE`.
Expand Down
25 changes: 24 additions & 1 deletion library/std/src/sys/pal/vibix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,31 @@ pub fn abort_internal() -> ! {

// SAFETY: must be called only once during runtime initialization.
pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {
// Nothing to initialize yet. Args/env handling is deferred to later phases.
}

// SAFETY: must be called only once during runtime cleanup.
pub unsafe fn cleanup() {}

/// Entry point for vibix userspace binaries.
///
/// The kernel loads ELF binaries with entry point set to `_start`.
/// Since vibix has no CRT, std provides the entry point directly.
/// `main` is the symbol rustc generates that calls `lang_start`.
#[cfg(not(test))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn _start() -> ! {
unsafe extern "C" {
fn main(argc: isize, argv: *const *const u8) -> isize;
}

// No args/env on vibix yet; pass zeros.
let ret = unsafe { main(0, crate::ptr::null()) };

// exit_group(ret)
unsafe {
vibix_abi::syscall::syscall1(231, ret as u64);
}
loop {
core::hint::spin_loop();
}
}
14 changes: 14 additions & 0 deletions userspace/std_hello/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "std_hello"
version = "0.1.0"
edition = "2021"
authors = ["vibix hackers"]
license = "MIT OR Apache-2.0"

[[bin]]
name = "std_hello"
path = "src/main.rs"

# Standalone package — not part of the main workspace. Built with
# `-Z build-std` against the in-repo std fork (see xtask std-hello).
[workspace]
5 changes: 5 additions & 0 deletions userspace/std_hello/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![feature(restricted_std)]

fn main() {
println!("hello from std");
}
193 changes: 192 additions & 1 deletion xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
//! without booting QEMU (issue #526, for CI pre-build)
//! shell-pipeline — boot with the shell-pipeline integration binary as PID 1
//! and assert `SHELL_PIPELINE_OK: 4` on serial (issue #462)
//! std-hello — build std_hello (println! via std PAL), boot as PID 1 on
//! ext2 root, assert `hello from std` on serial (issue #852)
//! lint — run clippy on xtask (host) and vibix (kernel, no_std)
//! isr-audit — scan ISR-reachable files for blocking-lock regressions
//! nm-check — RFC 0005 Phase 1 (#669): build the release kernel + ISO
Expand Down Expand Up @@ -257,6 +259,7 @@ fn main() -> R<()> {
repro_fork_build(&opts)?;
}
"shell-pipeline" => shell_pipeline(&opts)?,
"std-hello" => std_hello(&opts)?,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C2 --hidden -g '.github/**' '\bstd-hello\b|cargo xtask (test|smoke)' .

Repository: dburkart/vibix

Length of output: 4230


The std-hello subcommand is not wired into any CI workflow.

The grep across .github/workflows/ found no invocations of cargo xtask std-hello, despite the subcommand existing in xtask/src/main.rs. The PR objective explicitly requires this to be part of the CI smoke test pipeline, but test_all() still only runs unit and integration tests. The new subcommand will regress silently in CI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xtask/src/main.rs` at line 258, The new "std-hello" subcommand is implemented
in xtask (see the match arm "std-hello" => std_hello(&opts)? and the std_hello
function) but it's not exercised in CI; add it to the CI smoke pipeline and to
the aggregate runner: update test_all() in xtask/src/main.rs to call
std_hello(&opts)? (so local runners exercise it), and update the relevant GitHub
Actions workflow under .github/workflows (the smoke/test job) to run `cargo
xtask std-hello` as part of the smoke tests step (or add it to the existing
cargo xtask test_all invocation) so the subcommand is executed during CI.

"lint" => lint()?,
"isr-audit" => isr_audit::run(&workspace_root())?,
"nm-check" => {
Expand Down Expand Up @@ -438,7 +441,7 @@ fn main() -> R<()> {
other => {
eprintln!("unknown subcommand: {other}");
eprintln!(
"usage: cargo xtask [build|initrd|ext2-image|iso|run|test|test-unit|test-integration|smoke|pjdfstest|repro-fork|repro-fork-build|shell-pipeline|lint|isr-audit|nm-check|validate-target|bench|fuzz|clean] [--release] [--fault-test] [--panic-test] [--bench] [--fork-trace] [--shard=I/N (test-integration only)]"
"usage: cargo xtask [build|initrd|ext2-image|iso|run|test|test-unit|test-integration|smoke|pjdfstest|repro-fork|repro-fork-build|shell-pipeline|std-hello|lint|isr-audit|nm-check|validate-target|bench|fuzz|clean] [--release] [--fault-test] [--panic-test] [--bench] [--fork-trace] [--shard=I/N (test-integration only)]"
);
std::process::exit(2);
}
Expand Down Expand Up @@ -738,6 +741,55 @@ fn build_userspace_hello_dyn() -> R<PathBuf> {
Ok(bin)
}

/// Build the `std_hello` binary — a standard Rust binary targeting vibix
/// that uses `println!` via the vibix PAL. Built out-of-tree with
/// `-Z build-std` pointed at the in-repo std fork (issue #852).
///
/// The binary is compiled against `x86_64-unknown-vibix.json` and linked
/// statically with the custom std PAL; it exercises the full
/// target-spec → vibix_abi → std → println! stack.
fn build_userspace_std_hello() -> R<PathBuf> {
let ws = workspace_root();
let target_spec = ws.join(VIBIX_USERSPACE_TARGET);
let manifest = ws.join("userspace/std_hello/Cargo.toml");
let library_root = ws.join("library");

let target_dir = ws.join("target");
let mut cmd = Command::new("cargo");
cmd.current_dir(&ws)
// Point -Z build-std at the in-repo std fork.
.env("__CARGO_TESTS_ONLY_SRC_ROOT", &library_root)
.args(["build", "--manifest-path"])
.arg(&manifest)
.arg("--target-dir")
.arg(&target_dir)
.args([
"-Z",
"build-std=std,core,alloc,panic_abort",
"-Z",
"build-std-features=compiler-builtins-mem",
"-Z",
"unstable-options",
"-Z",
"json-target-spec",
"--target",
])
.arg(&target_spec);
check(cmd.status()?)?;

// The output lands under target/<target-triple>/debug/<bin-name>.
// The target triple for a JSON spec is the file-stem of the spec.
let bin = target_dir
.join("x86_64-unknown-vibix")
.join("debug")
.join("std_hello");
if !bin.exists() {
return Err(format!("std_hello binary missing at {} after build", bin.display()).into());
}
strip_debug(&bin)?;
Ok(bin)
}

/// Generate a minimal stub dynamic-linker ELF for the #764 integration test.
///
/// Produces an ET_DYN ELF64 with a single page-aligned PT_LOAD segment.
Expand Down Expand Up @@ -2585,6 +2637,145 @@ fn shell_pipeline(opts: &BuildOpts) -> R<()> {
}
}

/// Boot the `std_hello` binary as PID 1 under QEMU and assert the
/// `hello from std` marker appears on serial (issue #852).
///
/// This is the Phase 1 capstone test: a standard Rust binary using
/// `println!` runs on vibix and emits output on the serial console,
/// proving the entire stack works (target spec, vibix_abi, std PAL,
/// syscalls).
fn std_hello(opts: &BuildOpts) -> R<()> {
use std::collections::VecDeque;
use std::io::BufRead as _;
use std::time::Instant;

const HARD_CAP: Duration = Duration::from_secs(120);
const SUCCESS_MARKER: &str = "hello from std";
const PANIC_MARKER: &str = "KERNEL PANIC:";

let kernel = build(opts)?;
let init_bin = build_userspace_std_hello()?;

// Install std_hello as /init in the ext2 rootfs image.
let disk = ext2_image::build(&workspace_root(), Some(&init_bin), true)?;
let iso = workspace_root().join("target").join("vibix-std-hello.iso");
make_iso_with_cmdline(&kernel, &iso, "iso_std_hello", "root=/dev/vda")?;

let mut child = Command::new("qemu-system-x86_64")
.args([
"-M",
"q35",
"-cpu",
"max",
"-m",
"256M",
"-serial",
"stdio",
"-display",
"none",
"-no-reboot",
"-no-shutdown",
"-device",
"isa-debug-exit,iobase=0xf4,iosize=0x04",
])
.args(virtio_blk_args(&disk))
.arg("-cdrom")
.arg(&iso)
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;

let pid = child.id();
let stdout = child.stdout.take().ok_or("no stdout pipe")?;

let (cancel_tx, cancel_rx) = std::sync::mpsc::channel::<()>();
let hard_pid = pid;
let hard_watchdog = std::thread::spawn(move || {
if let Err(std::sync::mpsc::RecvTimeoutError::Timeout) = cancel_rx.recv_timeout(HARD_CAP) {
let _ = Command::new("kill").arg(hard_pid.to_string()).status();
}
});

let (tx, rx) = std::sync::mpsc::channel::<String>();
let reader_handle = std::thread::spawn(move || {
let mut reader = std::io::BufReader::new(stdout);
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
if tx.send(line.clone()).is_err() {
break;
}
}
Err(_) => break,
}
}
});

let start = Instant::now();
let mut success = false;
let mut failure: Option<String> = None;
let mut tail: VecDeque<String> = VecDeque::with_capacity(64);
const TICK: Duration = Duration::from_millis(200);

loop {
match rx.recv_timeout(TICK) {
Ok(line) => {
print!("{line}");
if tail.len() == 64 {
tail.pop_front();
}
tail.push_back(line.clone());

if line.contains(PANIC_MARKER) {
failure = Some(format!("kernel panic: {}", line.trim_end()));
break;
}
if line.contains(SUCCESS_MARKER) {
success = true;
break;
}
}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
if start.elapsed() > HARD_CAP {
failure = Some(format!("hard cap exceeded ({HARD_CAP:?}) without success"));
break;
}
}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
if !success && failure.is_none() {
failure = Some(format!("QEMU exited before `{SUCCESS_MARKER}` marker"));
}
break;
}
}
}

let _ = Command::new("kill").arg(pid.to_string()).status();
drop(cancel_tx);
let _ = hard_watchdog.join();
let _ = reader_handle.join();
let _ = child.wait();

match (success, failure) {
(true, _) => {
println!("→ std-hello: `{SUCCESS_MARKER}` in {:?} ✓", start.elapsed());
Ok(())
}
(false, Some(msg)) => {
eprintln!("--- captured serial (tail) ---");
for line in &tail {
eprint!("{line}");
}
eprintln!("------------------------------");
Err(format!("std-hello: {msg}").into())
}
(false, None) => Err("std-hello: terminated with no success and no failure marker".into()),
}
}

/// Parsed view of the repro-fork harness start banner.
///
/// The harness emits a dedicated `repro: CYCLES=N\n` line before the
Expand Down
Loading