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
6 changes: 5 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions issues/a-c-program-clang-links-for-toyos-carries-no-build-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
status: open
kind: defect
opened: 2026-10-04
---

# A C program clang links for ToyOS carries no build-id

A killed program's frame is recorded with its file's build-id, and
`/system/bin/symbolize` names it only from the file carrying the same one
(`toyos-symbols/src/frame.rs`). The Rust target links with `--build-id`; the
clang ToyOS driver (`clang/lib/Driver/ToolChains/ToyOS.cpp` in the LLVM fork)
passes no `--build-id` to `ld.lld`, so a C program has no `PT_NOTE` and its
frames are recorded `id=-`. The namer then names one from whatever file of that
name it finds, with nothing to tell another build of it apart. Fuchsia's
driver passes the flag (`Fuchsia.cpp`).

**Exit**: a C program the ToyOS clang links carries a GNU build-id note, and a
C case killed by a fault is recorded with it.
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ never on the stick. The window is **[the reboot binary's spawn → the reset
register write]**, and the candidates in it are all consistent with what the
file shows:

- `symbols::read_backtrace_table` reads 2 MiB of symbol tables off the boot
stick — 512 SCSI READ(10) commands, each holding the block `Handle` and
`XHCI` with preemption off. It is the only device call between the two
records and the only thing in the window that takes the 46–100 ms the healthy
gap is.
- The spawn's device reads after `spawn: TLS`. These boots ran
`symbols::read_backtrace_table`, 2 MiB of symbol tables off the boot stick in
512 SCSI READ(10) commands, each holding the block `Handle` and `XHCI` with
preemption off; #711 deletes it, because the kernel no longer reads a
program's symbol tables. The device read left between the two records is the
build-id note: at most a page per `PT_NOTE`, one note in an lld-linked
program, through the same block path. A wedge in that path stays a candidate;
the 2 MiB read that matched the healthy 46–100 ms gap does not.
- `quiesce`'s `flush_disks` and `hand_back`, which landed at `88841f56` a few
minutes before the first hang; `hand_back` takes `XHCI` while `logd`'s own
write to the same stick may still be in flight.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
status: open
kind: defect
opened: 2026-10-04
---

# `screen_panic_muted` reads the panel before the report has reached its armed line

`screen_panic_muted` (`tests/toyos.rs`) takes one screendump as soon as the
panel shows `PANIC:` and then asserts the line `halt_all_cpus` paints last,
`panic: rebooting in 60 s, timed by …`. Nothing orders the dump after that
line: a guest slow between the two is read mid-report. A whole-suite run with
the host's load average at 48 (14 cores) red it with the dump ending inside
the kernel backtrace, before the armed line; the same test alone was green in
10 s.

**Exit**: the test waits for the armed line itself, bounded, and a guest
slowed between `PANIC:` and that line still passes.
45 changes: 24 additions & 21 deletions issues/the-kernel-still-parses-what-userland-writes.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,27 +42,30 @@ first: the kernel mounts ROOT itself, but what mounts `/boot` and `/home`, and
with what authority, is the open question.

**Move 3 — the panic-time symbol resolver**, small and independent. Ruled
(owner, 2026-10-03, "adopt", `issues/toyos-explains-itself.md`): the
kernel names only itself; a killed program is reported as file and offset, and
one userland naming service shares the kernel's lookup and demangling code.
The kernel keeps its own ELF reader, `toyos-elf`: the orchestrator's reading
of the owner's crate ruling of the same day, "if it makes sense we wrote our
own elf parser or aml interpreter due to security or other important reasons
we do it". ELF parsing is already a pure crate that forbids unsafe and is
tested against a crafted corpus, and the symbol machinery moved onto it in the
2026-08-15 consolidation. What is left: the kernel stops naming a program's
addresses (`resolve_user_symbol` in `kernel/src/process.rs`); the per-spawn
table `read_backtrace_table` builds in `kernel/src/loader/symbols.rs`, and the
copy each task carries, are deleted; a userland service names a reported file
and offset; and `rustc-demangle`'s standing is decided. It is an unforked
crates.io dependency, ~2k lines of third-party string parsing in Ring 0 on the
panic path: fork it into the estate like every other third-party source, or
record the exemption deliberately. **Exit**: a guest test whose program dies
of a fault reads the kernel's report as the program's file and the faulting
offset, with no user symbol in it, and the userland service names the
faulting function from that file and offset; no kernel source builds a backtrace
table from a program's file; and `rustc-demangle` is forked or its exemption
recorded.
(owner, 2026-10-03, `issues/toyos-explains-itself.md`): he chose
"Adopt it: Built after the folder work, as part of the observability plan.
Frees at least 2 MiB per running program, the kernel stops parsing program
files' name tables, library frames get names." and wrote "adopt, make sure the
code can never panic or fail is that possible? i dont want to allow a kernel
crash because of that and it seems brittle by nature do you agree? can we
share a lot of code there? should we?". The kernel keeps its own ELF reader,
`toyos-elf`: the orchestrator's reading of the owner's crate ruling of the same
day, "if it makes sense we wrote our own elf parser or aml interpreter due to
security or other important reasons we do it". The kernel names only itself: a
killed program's frame is recorded as the file it ran, the offset in that file
and the file's build-id (`toyos-symbols/src/frame.rs`), and
`/system/bin/symbolize` names it with the kernel's own lookup and demangling
code. What is left: a library's frames print bare, because the kernel records
only the executable's image for a process; and `rustc-demangle`'s standing. It
is an unforked crates.io dependency, ~2k lines of third-party string parsing in
Ring 0 on the panic path, now for the kernel's own names alone: fork it into
the estate like every other third-party source, or record the exemption
deliberately. **Exit**: `disk_backtrace`, the T14's shared-boot row,
reads its killed child's report as the child's file, offset and build-id with
no user symbol in it, and `symbolize` names the faulting function from them;
no kernel source builds a backtrace table from a program's file; a library's
frame is recorded as its file and offset; and `rustc-demangle` is forked or its
exemption recorded.

**Small trim to evaluate:** main-thread exit killing the process is kernel policy
the review called unnecessary. A process could end when its last thread does, or
Expand Down
4 changes: 2 additions & 2 deletions issues/toyos-explains-itself.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ ToyOS answers what it is doing, what it did and what it is made of from
inside itself, with programs it ships. Today the answers are scattered: the
log carries numbers in prose (`irq:`, `tlb:`, `PMM:`, `sched:`, `syscalls:`),
the trace ring has LLDB as its only reader, nothing reads RAPL or C-state
residency, a process's memory is a byte sum
that reads 0 under contention, and a user symbol is resolved by the kernel.
residency, and a process's memory is a byte sum that reads 0 under
contention.
Each pillar's own track carries its steps and its exits; this track holds the
counters and sizes pillars, which no other track does.

Expand Down
2 changes: 1 addition & 1 deletion kernel/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,5 @@ toyos-untrusted = { path = "../toyos-untrusted" }
toyos-userbound = { path = "../toyos-userbound" }
toyos-wallclock = { path = "../toyos-wallclock" }
toyos-xhci = { path = "../toyos-xhci" }
rustc-demangle = "0.1"
hashbrown = { version = "0.16", default-features = false }
dlmalloc = { version = "0.2", default-features = false }
6 changes: 3 additions & 3 deletions kernel/src/arch/aarch64/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ fn user_report(frame: &Frame) {
}
log!(" pc:");
if percpu::current_pid().is_some() {
crate::process::resolve_user_symbol(frame.elr).log_bare(frame.elr);
crate::process::record_user_frame(frame.elr).log_bare(frame.elr);
} else {
log!(" {:#x}", frame.elr);
}
Expand Down Expand Up @@ -388,7 +388,7 @@ fn user_backtrace(start_fp: u64, max_frames: usize) {
if ret == 0 {
break;
}
crate::process::resolve_user_symbol_return(ret).log_bare(ret);
crate::process::record_user_frame_return(ret).log_bare(ret);
fp = saved;
}
}
Expand Down Expand Up @@ -510,7 +510,7 @@ pub(crate) fn report_panic(message: &core::panic::PanicInfo, frame: u64) {
let (pc, fp, sp) = percpu::syscall_context();
log!(" Syscall: num={} user_pc={pc:#x} user_sp={sp:#x}", percpu::syscall_num());
log!(" User backtrace:");
crate::process::resolve_user_symbol(pc).log_bare(pc);
crate::process::record_user_frame(pc).log_bare(pc);
user_backtrace(fp, 20);
}
}
Expand Down
10 changes: 5 additions & 5 deletions kernel/src/arch/x86_64/idt/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn user_backtrace(start_rbp: u64, max_frames: usize) {
};
let Ok(return_addr) = read_user_u64(rbp + 8) else { break };
if return_addr == 0 { break; }
process::resolve_user_symbol_return(return_addr).log_bare(return_addr);
process::record_user_frame_return(return_addr).log_bare(return_addr);
rbp = saved_rbp;
}
}
Expand Down Expand Up @@ -211,7 +211,7 @@ fn crash_report_exception(ctx: &ExceptionContext) {
log!(" rip:");
if ring3 {
if pid.is_some() {
process::resolve_user_symbol(ctx.frame.rip).log_bare(ctx.frame.rip);
process::record_user_frame(ctx.frame.rip).log_bare(ctx.frame.rip);
} else {
log!(" {:#x}", ctx.frame.rip);
}
Expand Down Expand Up @@ -267,7 +267,7 @@ fn crash_report_exception(ctx: &ExceptionContext) {
log!(" Syscall: num={} user_rip={:#x} user_rsp={:#x}",
percpu::syscall_num(), user_rip, percpu::user_rsp());
log!(" User backtrace:");
process::resolve_user_symbol(user_rip).log_bare(user_rip);
process::record_user_frame(user_rip).log_bare(user_rip);
user_backtrace(percpu::syscall_rbp(), 20);
}
}
Expand Down Expand Up @@ -325,7 +325,7 @@ fn crash_report_panic(info: &core::panic::PanicInfo, rbp: u64) {
log!(" Syscall: num={} user_rip={:#x} user_rsp={:#x}",
percpu::syscall_num(), user_rip, percpu::user_rsp());
log!(" User backtrace:");
process::resolve_user_symbol(user_rip).log_bare(user_rip);
process::record_user_frame(user_rip).log_bare(user_rip);
user_backtrace(percpu::syscall_rbp(), 20);
}
}
Expand Down Expand Up @@ -391,7 +391,7 @@ pub(super) fn double_fault_handler(frame: &TrapFrame) -> ! {

log!(" User backtrace:");
if pid.is_some() {
process::resolve_user_symbol(maybe_rip).log_bare(maybe_rip);
process::record_user_frame(maybe_rip).log_bare(maybe_rip);
user_backtrace(user_rbp, 20);
} else {
log!(" {:#x}", maybe_rip);
Expand Down
34 changes: 19 additions & 15 deletions kernel/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ use crate::mm::policy::{CachePolicy, Prot};
use crate::mm::{PAGE_2M, PAGE_BYTES};
use crate::process::{
Admission, ElfInfo, Endowments, OwnedAlloc, PageAlloc, PageFaultTrace, PageTables, Parent,
Pid, ProcessAccounting, ProcessData, ProcessEntry, ThreadData, ThreadEntry, UserStack,
PROCESS_TABLE,
Pid, ProcessAccounting, ProcessData, ProcessEntry, ThreadData, ThreadEntry, UserImage,
UserStack, PROCESS_TABLE,
};
use crate::sync::Lock;
use crate::{scheduler, vfs, UserAddr};
Expand All @@ -40,6 +40,7 @@ use toyos_elf::section::SectionTable;
use toyos_elf::sym::{self, SymTab};
use toyos_elf::rela::{FillLattice, Rules, FILL_GRANULE};
use toyos_elf::{Layout, RelocError, TlsSegment};
use toyos_symbols::frame::BuildId;

const USER_STACK_SIZE: usize = 4 * PAGE_2M as usize; // 8 MB

Expand Down Expand Up @@ -572,12 +573,17 @@ pub fn spawn<H>(
let sp = user_stack.write_argv(argv);
let t_tls = crate::clock::nanos_since_boot();

let syms = symbols::read_backtrace_table(
backing.as_ref(), &layout, path, base,
image_start.raw(), image_end,
user_stack.base().raw(), user_stack.top(),
);
let sym_bytes = syms.resident_bytes();
// What a crash record names a frame by. The build-id is read at each
// `PT_NOTE`'s own offset, a page of it at most.
let image = Arc::new(UserImage {
name: String::from(path),
build_id: BuildId::find(&header_data, |note| {
Some(read_file_range(backing.as_ref(), note.offset, note.filesz.min(PAGE_BYTES as u64) as usize))
}),
start: image_start.raw(),
end: image_end,
bias: base,
});

let (ks_alloc, ks_sp) = match alloc_kernel_stack(process_start, entry, sp, 0) {
Some(ks) => ks,
Expand Down Expand Up @@ -637,9 +643,6 @@ pub fn spawn<H>(
syscall_total_ns: 0,
}));

// One table, two holders: cloned so a crash report on this thread reads names without the process table.
let syms = Arc::new(syms);

#[cfg(feature = "test-actuators")]
crate::process::debug_kill_marked_place(parent);

Expand All @@ -649,7 +652,8 @@ pub fn spawn<H>(
Arc::clone(&object),
start::make_name(name),
proc_data,
Arc::clone(&syms),
// Two holders: a crash report on this thread reads it without the process table.
Some(Arc::clone(&image)),
ThreadEntry::new(thread_data),
node,
));
Expand All @@ -662,7 +666,7 @@ pub fn spawn<H>(
ks_sp,
child_pt.clone(),
thread_pointer,
syms,
Some(image),
);
table.get_mut(pid).unwrap().threads_mut().get_mut(tid).unwrap().set_sched(sched);
(tid, dst)
Expand All @@ -678,8 +682,8 @@ pub fn spawn<H>(
crate::process::debug_hold_marked_spawn(parent, &object);

let t3 = crate::clock::nanos_since_boot();
log!("spawn: {} pid={} tid={} dst={} base={:#x} entry={:#x} root={:#x} symbols={}KiB (layout={}ms relocs={}ms deps={}ms tls={}ms total={}ms)",
path, pid, tid, dst.0, base, entry, child_pt.lock().root().phys(), sym_bytes / 1024,
log!("spawn: {} pid={} tid={} dst={} base={:#x} entry={:#x} root={:#x} (layout={}ms relocs={}ms deps={}ms tls={}ms total={}ms)",
path, pid, tid, dst.0, base, entry, child_pt.lock().root().phys(),
(t1 - t0) / 1_000_000, (t2 - t1) / 1_000_000, (t_deps - t2) / 1_000_000,
(t_tls - t_deps) / 1_000_000, (t3 - t0) / 1_000_000);

Expand Down
Loading
Loading