Claude/labwc eclipse desktop 08f1ft - #414
Open
Pryancito wants to merge 2302 commits into
Open
Conversation
…n itimer QEMU bisection (repair-canary kernel, smp=1) shows plain external commands and fork+exec chains WITHOUT an alarm all survive (/bin/busybox true, ls, env true, sh -c 'exec true'), while anything using timeout (setitimer/alarm) crashes -- and timeout 5 true crashes immediately (the 5s timer never fires), so it is the ARMING of the itimer, not its delivery. Fatal #GP is an IRQ delivered right after an sti (lock guard pop_off) in lookup_inode_at, because the executor kernel stack is already corrupted; the mangled slot is a saved timer_tick return address. sys_setitimer/TimerHeap/arm_itimer are bounds-clean by inspection, so the bug is in the arm_itimer->timer_set->NAIVE_TIMER/timer_tick interaction with the executor stack.
…y), not just arm Distinguishing hang from crash via QMP screendumps: timeout 5 true and sh -c 'true & wait' only HANG (VM alive) -- a separate reaping bug -- while timeout -s TERM 1 sleep 5, where the 1s timer actually fires and delivers SIGALRM->SIGTERM to the blocked sleep, CRASHES (QMP gone). So the corruptor is the timer-IRQ signal-delivery path (arm_itimer callback -> deliver_timer_signal in timer_tick/hard IRQ -> lock_linux + signal_set force-waking a blocked task), not the arming. Corrects the previous commit's 'arming' conclusion.
…m signal cascade A stack-scan (dts_scan, reverted) bracketing deliver_timer_signal's entry and its find_process/thread_ids/signals.insert/signal_set found ZERO corruption under the crashing repro, yet the VM still crashed. So the itimer's IRQ-context delivery is clean. The corruption is downstream: timeout's SIGALRM handler frame setup (handle_signal/setup_uspace) and/or delivering the terminating SIGTERM to the still-running, nanosleep-blocked child. Records the narrowed next targets (handle_signal, sys_kill/send_signal_to_process, Process::exit / PROCESS_TERMINATED callback).
…poses fs-node UAF
The intermittent-corruption crash (repro: timeout -s TERM 1 sleep 5) triple-
faulted DURING exception delivery: the #GP raised on the corrupt executor stack
could not push its frame -> #DF -> triple fault, silently (nothing on serial).
- Give #GP (vector 13) a dedicated IST stack (mirrors the existing #DF IST1) so
the handler runs on known-good stack instead of triple-faulting.
- In the #GP handler, repair a mangled saved kernel RIP (0xffffff00_00xxxxxx with
its top byte overwritten -> a to a non-canonical addr) and resume; tight
signature (kernel .text pointer, mangled top byte) so it never fires on a real
#GP or a user address.
Result: the repro no longer silently dies -- it reaches a diagnosable panic:
[KERNEL PAGE FAULT] vaddr=0x97 rip=0xffffff00001bfee7
-> <rcore_fs_mountfs::MNode as INode>::metadata, a vtable call through a
corrupt inner-inode pointer (rcx=0x87). So the true root cause is a
corrupted/use-after-freed filesystem MNode inode, reached via a path lookup
during the signal cascade -- matching the original Arc<MountFS>::drop_slow
symptom. docs/README-crash-repro.md updated with the disasm + next step.
Re-running the repro: one run reaches the diagnosable MNode::metadata #PF panic, another still silently triple-faults. The wild write sprays small garbage (0x01/0x0a/0x87) across stack return addresses AND heap Arc/vtable pointers, so fault interception can't reliably keep the machine alive -- the real fix is to stop the wild write. Mitigation kept as pure hardening + it surfaced the root cause.
…memset victim Two decisive new findings on the intermittent kernel corruption, plus hardening: - Simpler reproducer: `cat /proc/self/exe > /dev/null` perturbs the same state with NO signal handler and NO itimer. It usually hangs silently and ~1 boot in ~15 surfaces a clean kernel page fault. This removes the entire signal/timer half of the hypothesis space -- the corruption lives in the plain exec/path-lookup / /proc/self/exe read machinery. - The wild write is a `memset` (compiler_builtins set_bytes), and memset is a VICTIM: the faulting destination 0xffffff9c0169fbc8 is a valid executor-stack heap address with one byte flipped (byte4 0x00->0x9c); the implied span rules out a runaway length. Same tiny-value byte-spray signature as the mangled return address (top byte ff->01) and the MNode inode vtable (->0x87). All three are the same primary writer scattering small byte values across live pointers; each fault is wherever a corrupted pointer is next USED. Instrumentation / hardening left in place: - kernel-private #PF handler (zCore/src/handler.rs) now walks the frame-pointer chain and raw-scans the stack for kernel code pointers via the blocking serial writer, using rbp/rsp captured by kstats::note_fault_regs (set in the arch trap entry). Prints [kfault-bt] frames naming the memset's caller when the diagnosable-fault variant lands. - MNode poison + inode-pointer guard (vendor/rcore-fs-mountfs) returns EIO and logs the clobber pattern instead of dereferencing a garbage vtable; verified it does not false-positive on normal fs traffic. Also refined the diagnosis: the MNode canary fired 0/6 boots, so the primary corruption victim is the executor stack, not heap fs nodes. Docs updated in docs/README-crash-repro.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…self/exe recursion
busybox standalone re-executes applets via execve("/proc/self/exe"), and
sys_execve stored that LITERAL string as the new image's execute_path. The
magic link then became self-referential: lookup_inode_at("/proc/self/exe")
reads execute_path() == "/proc/self/exe" and recurses without bound. The
coroutine stack is a guard-page-less 128 KiB heap allocation, so the runaway
recursion silently writes thousands of stack frames DOWNWARD into neighbouring
heap allocations -- spraying saved return addresses (the "mangled top byte"
ff->01/0a #GP RIPs), ASCII "/proc/self/exe" bytes, and small values (the 0x87
MNode vtable) over other tasks' state.
This one bug was every observed symptom: the `timeout -s TERM 1 sleep 5`
triple fault (busybox timeout spawns sleep via the same /proc/self/exe re-exec,
so the child's execve itself recursed), the deterministic `cat /proc/self/exe`
hang, the memset-victim #PF, and ALSO the "separate" `timeout 5 true` shell
hang -- confirmed same root cause by the fix.
Fix, two layers:
- sys_execve (linux-syscall/src/task.rs): canonicalize -- when the exec path is
"/proc/self/exe", substitute the CURRENT execute_path (the real binary path)
before loading/storing; the literal magic string is never stored again.
- lookup_inode_at (linux-object/src/fs/mod.rs): recursion guard -- an empty or
self-referential execute_path returns ELOOP like a real symlink cycle.
Verified in QEMU (one boot, zero corruption banners):
- cat /proc/self/exe: hang (14/14 boots) -> exit 0
- timeout -s TERM 1 sleep 5 (x3): triple fault -> exit 143 each, kernel alive
- timeout 5 true: shell hang -> exit 0
- env/readlink/ls regression checks pass; children now named "busybox" not "exe"
Docs: full deduction chain + residual non-fatal quirks recorded in
docs/README-crash-repro.md. Recommended follow-up: guard page under executor
stacks so any future runaway recursion faults cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…ddress 0 The VMAR's first-fit search (find_free_area) started at offset 0, so a process whose low address space is unmapped could get an anonymous mmap(NULL, ...) placed AT ADDRESS 0. Userspace treats the returned 0 as a valid pointer; every syscall null-check then bounces it with EFAULT. Observed as `dd bs>=4096` failing "Bad address" on any input: glibc placed dd's I/O buffer at 0x0 and read(0, NULL, 4096) was rejected ([read-efault] + [mmap-trace] instrumentation pinned it: "anon len=0x1000 -> 0x0"). Fix: a min_offset floor threaded through map_ext_min -> determine_offset -> find_free_area, applied ONLY by the Linux sys_mmap path (both anon and file branches) with MMAP_MIN_ADDR = 64 KiB (the Linux vm.mmap_min_addr default). Every candidate in find_free_area is clamped to the floor so a mapping ending below it cannot reintroduce a low address. MAP_FIXED is exempt, like Linux. Scoping matters: a first attempt put the floor globally in determine_offset and shifted the ELF loader's app sub-VMAR off base 0, which relocated every non-PIE image (absolute vaddrs 0x400000+) and SIGSEGV'd all of userspace. allocate() and all non-mmap callers therefore keep floor 0; the constraint is documented at both sites. Also keeps a [read-efault] diagnostic in sys_read (fires only on error) that names the failing layer (inode read vs copy-out) -- it is what caught this. Verified in QEMU: dd bs=4096/8192/65536 all pass (before: EFAULT); boot, ls, env, fork/exec all normal; `timeout -s TERM 1 sleep 5` still exits 143 with the kernel alive; `cat /proc/self/exe` still exits 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
sys_read converted a read chunk STARTING with byte 0x03 (ETX) into a terminal interrupt whenever the fd was 0 -- without checking that stdin is a terminal. Any program reading binary data through a pipe on stdin was killed by a spurious SIGINT whenever a chunk began with 0x03 (guaranteed within a couple of MB of binary): `cat /bin/busybox | wc -c`, tar/gzip pipelines, X startup scripts piping data. Terminal interrupts belong to the line discipline, and both the VT `Stdin` (termios ISIG, stdio.rs) and the PTY slaves (pty.rs, devfs/pty.rs) already convert VINTR into SIGINT for the foreground pgrp themselves -- the syscall- level check was pure downside. Removed, with a comment explaining why it must not come back. Note: `^C` at the *serial* console prompt was only ever handled cosmetically by this hack (it fired when the shell read the byte -- it never interrupted a foreground `sleep`, before or after). Serial input does not run through the termios line discipline; routing it there is a separate follow-up. VT and PTY consoles are unaffected. Verified in QEMU: cat/dd binary pipelines now complete (2124608 bytes, exit 0; before: SIGINT/exit 130); crash-repro regression `timeout -s TERM 1 sleep 5` still exits 143 with the kernel alive; cat /proc/self/exe, dd bs=4096, ls/env all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
The executor (coroutine) stacks are guard-page-less 128 KiB heap allocations: a runaway kernel call chain overflows them silently into neighbouring heap allocations, and the resulting corruption is near-impossible to attribute (the /proc/self/exe self-reference recursion cost exactly that hunt). The existing canary at the stack base was only checked AFTER a future yielded -- an overflowing task never yields, so it never fired. Now the timer IRQ checks the currently-running executor's base canary on every tick (x86_64 trap handler -> executor::check_current_executor_canary): any deep overflow passes through the canary words, so future runaway recursion panics with a labelled "[stack-canary] COROUTINE STACK OVERFLOW" banner (executor id, task id, stack base) within ~4 ms instead of corrupting the heap silently. try_lock discipline throughout (hard-IRQ context): if the interrupted code holds the runtime lock the check simply skips that tick. Cost: one try_lock + 4 volatile reads per tick. Verified in QEMU: full regression suite green (binary pipelines, timeout crash repro exit 143, cat /proc/self/exe, dd bs=4096, ls/env), zero false positives. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft Claude/labwc eclipse desktop 08f1ft
…physical size
Real-hardware startx test (RTX 2060 SUPER + 32" 1366x768 TV over HDMI) showed
two display-sizing gaps: the whole system ran at a hardcoded 1024x768 (4:3
stretched across a 16:9 panel) because rboot.conf pinned resolution=1024x768,
and Xorg computed DPI from a fake 270x203mm physical size (the 96-DPI guess)
instead of the panel's real 885x497mm.
rboot:
- New `resolution=auto` (config Resolution enum: Keep/Auto/Exact). Auto reads
the display's EDID (which rboot already fetches) and picks the GOP mode
matching the EDID-preferred detailed timing; if the firmware does not offer
it, falls back to the largest offered mode (firmware-validated against the
display, and a TV upscales its standard timings far better than it stretches
a small 4:3 mode).
- `resolution=WxH` no longer PANICS ("graphic mode not found") when the mode
is not offered -- it warns and keeps the current mode. A config value could
previously brick boot on any machine whose firmware lacked that mode.
- EDID is read before mode selection (was after).
kernel (DRM GETCONNECTOR):
- Physical dimensions now prefer the EDID when the connector reports none:
exact mm from the preferred detailed timing (bytes 66-68), else cm from
bytes 21/22, else the old 96-DPI guess. DPI-aware clients get real numbers.
Both rboot.conf files (template + shipped) switch to resolution=auto.
Verified in QEMU: boots at 1920x1080 (auto picked the largest GOP mode; was
1024x768), clean console render, full regression suite green (timeout repro
exit 143, pipelines, dd, cat /proc/self/exe). rboot fmt/clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft feat(boot/drm): EDID-driven display sizing -- resolution=auto + real …
Two "impossible" unwrap panics were reported from real-hardware/QEMU runs: - thread.rs:79 (lock_linux) during startx on a pre-fix kernel build - process.rs:138 (linux()) once during boot Both are ext-Box downcast failures: every process/thread the Linux layer creates carries its ext and ext is immutable, so a failure means either a kernel-internal Zircon object leaked into a Linux-only path or the ext Box was corrupted (the /proc/self/exe recursion bug produced exactly this signature on old builds). The bare `Option::unwrap()` panic named neither the object nor the reason, making the reports unactionable. Replace both with descriptive panics that print the pid/tid, process name and the two possible causes. No behavioural change on the happy path. (Current HEAD boots clean 5/5 in QEMU with zero panics; the boot-time report is believed to be from a stale kernel ELF, but if it ever recurs the panic will now identify the process.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft diag(task): name the culprit when linux()/lock_linux() downcasts fail
Real-hardware report: a mixed ESP (new rboot.conf with `resolution=auto`, old
BootX64.efi without auto support) died before drawing anything --
`panicked at src/config.rs:66: ParseIntError { kind: InvalidDigit }` -- because
the resolution parser unwrapped `"auto".parse::<usize>()`. The config file is
user-editable and version-skewed in practice, so the parser must degrade, not
panic.
Hardened every value parser in rboot's config:
- resolution: malformed/unknown values warn and fall back to Auto (which
itself falls back to keeping the current mode when EDID/GOP give nothing).
- kernel_stack_address / kernel_stack_size / physical_memory_offset:
malformed numbers warn and keep the built-in default. The hex parser also
no longer slices `&value[2..]` blind (out-of-bounds panic on short values)
and accepts values with or without the 0x prefix.
Verified: rboot builds, fmt/clippy clean; QEMU boots to the shell at the
auto-selected 1920x1080 with the hardened binary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft fix(rboot): config parser must never brick boot
…n hardware) startx was a manual runtime chore: a fresh install / QEMU boot came up to a bare shell and the user had to `apk add` xorg-server, an input driver and fonts by hand -- and a real-hardware run showed xf86-input-libinput simply missing, so X started with no keyboard or mouse. New xtask module `xorg` populates the rootfs at BUILD time via `apk add --root <rootfs>` (Alpine's supported offline root-install: no chroot, no running target), pulling the whole stack: xorg-server (built-in modesetting, which this kernel's DRM scheme drives), xf86-input-libinput, xinit, mesa-dri-gallium + mesa-gl, xkeyboard-config/setxkbmap/xkbcomp, the base fonts X refuses to start without (font-misc-misc/cursor/encodings/dejavu), xterm and xrandr/xset. Two-image coverage: - Full rootfs (installed btrfs / real hardware): gets everything -> startx works out of the box. - Live initramfs (QEMU boots this, not the disk): xorg::copy_into_live copies the X-owned trees in UNCAPPED (LIVE_KEEP omits usr/bin+usr/lib and the per-file cap would truncate them), excluding usr/lib/dri so the RAM image stays bounded -- X runs without GL in QEMU, full software GL on the installed system. Robustness: - apk 3.x (the Chimera static binary this repo ships) needs --initdb: the empty v2-style db mod.rs writes makes it abort "Failed to open apk database". Verified: with --initdb + an absolute --cache-dir the command reaches the network cleanly (only the sandbox's mirror 403 blocks it here). - Best-effort like nvidia_firmware: an offline build / unreachable mirror / missing package warns and still produces a bootable image. - Persistent gitignored cache (ignored/apk-cache) + no forced --update-cache, so a later OFFLINE build reuses the .apks a prior online build fetched. - Knobs: ECLIPSE_XORG=0 skips it; ECLIPSE_XORG_PACKAGES="..." overrides the set (non-Alpine repos); ECLIPSE_XORG_LIVE=0 keeps the installer initramfs lean. xtask builds; fmt/clippy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft feat(build): bake the X.Org stack into the image (works in QEMU and o…
…x was never baked) `make image` calls `make(false)`, whose incremental path (taken whenever rootfs/<arch> already exists -- the common case, and one is even checked in) RETURNED before desktop::install / xorg::install. Those ran only on a from-scratch `clear` build, so an ordinary rebuild shipped an image with no X server: the freshly-built QEMU image booted to "sh: startx: not found". - Call desktop::install + xorg::install on the incremental path too (before its early return). apk-add of already-present packages is a no-op, so repeat builds stay cheap; desktop::install is idempotent config writes. - xorg::install now verifies the result and reports LOUDLY: on success it prints that the server/startx/libinput driver are present; on failure it prints a boxed, unmissable notice explaining that the image will say "startx: not found" and how to fix it (network to the mirror, or ECLIPSE_XORG_PACKAGES to match the repo's names). No more silent skip. xtask builds; fmt/clippy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft fix(build): run Xorg/desktop install on incremental builds too (start…
…: not found" cause)
The build normally runs as an unprivileged user (`make` on a dev box). apk 3.x
refuses to create a package database as non-root without --usermode, so the
build-time `apk add --root` exited immediately with
"ERROR: Use --usermode to allow creating database as non-root" and was warned
past -- shipping an image with no X server ("sh: startx: not found"), regardless
of network. (This is what the user hit; a direct run of the exact command
reproduced the error.)
apk also REFUSES --usermode when run as root ("--usermode not allowed as root"),
so the flag can't just always be passed. Detect the euid via `id -u` (no new
crate dep) and pass --usermode only when non-root; a root/CI build keeps the
privileged path.
xtask builds; fmt/clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft fix(build): pass apk --usermode for non-root builds (the real "startx…
…rg was baked in) `make qemu` handed QEMU the ESP *directory* via `-drive fat:rw:$(esp)`, whose built-in FAT emulation is FAT16 and caps at ~504 MiB. Baking the X.Org stack into the live initramfs pushed the ESP past that, so QEMU aborted before boot: "Directory does not fit in FAT16 (capacity 516.06 MB)". justrun (x86_64) now builds a real FAT32 image sized to the ESP contents (du + 128 MiB) with mkfs.vfat + mtools and boots `-drive file=$(esp_img)` -- the same kind of image a real hardware ESP is. Falls back to the old `fat:rw:$(esp)` directory method when those tools are absent (fine for small images / minimal hosts), selected at parse time via `have_vfat`. Verified the image build in isolation: a 642 MiB payload (133 MiB zcore + 380 MiB Xorg initramfs) mkfs/mmd/mcopy's cleanly and all EFI files land in the image; `make -n` shows QEMU now points at esp.img. No change to real-hardware install (install-eclipse writes a real 1 GiB FAT32 ESP already). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft fix(qemu): boot a real FAT32 ESP image, not fat:rw dir (broke once Xo…
The build-time `apk add --root rootfs/x86_64 --initdb` clobbered the base system: --initdb starts from an empty database, so to satisfy xorg-server apk pulls the ENTIRE dependency closure (musl, libc, ld-musl, base libs) and writes it over Eclipse's hand-staged busybox/musl. The result booted to every VT shell SIGSEGV'ing identically (jump to a garbage PC 0x6100 through the broken loader), one of which tripped the new descriptive panic "Thread::lock_linux(): ... has no LinuxThread ext" during teardown. Fix: install into a throwaway staging root (ignored/xorg-stage), then copy ONLY the X-owned trees into the real rootfs — usr/bin, usr/lib, usr/libexec, usr/share, etc/fonts, etc/X11 — never bin/ lib/ sbin/ or base /etc. The base loader/libc/busybox are preserved; X is purely additive (musl lives in /lib, which is skipped, so usr/lib carries only the X + mesa libs). Also create the `libc.musl-x86_64.so.1 -> ld-musl-x86_64.so.1` alias the Alpine X binaries link against, additively, so they resolve libc against the base's own musl. xtask builds; fmt/clippy clean. NOTE for anyone with an already-broken rootfs from the previous build: clear it (`rm -rf rootfs/x86_64`) before rebuilding so the base is re-staged fresh — the incremental build path does not restore a clobbered /lib/ld-musl. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft fix(build): install Xorg into a staging root, not over the base rootfs
… run
After the staging fix the base boots clean, but Alpine's dynamically-linked X
binaries still crashed: `mcookie` (run by startx) SIGSEGV'd at pc=0x1bd0
("Couldn't create cookie"). Cause: they are linked against Alpine's musl, but
the rootfs shipped Eclipse's own musl-cross `ld-musl-x86_64.so.1`; a
newer-musl binary on the older loader jumps to a bogus low PC.
musl keeps a stable, BACKWARD-compatible ABI, so make Alpine's musl the single
loader: copy just `lib/ld-musl-x86_64.so.1` out of the (already-fetched)
dependency closure into the real rootfs (0755), replacing Eclipse's. Eclipse's
older base binaries keep running on the newer loader; the Alpine X binaries get
the musl they were built against. This is the "one musl for everything" the user
asked for, done with a single file rather than clobbering the whole base (which
is what broke boot before). The `libc.musl-x86_64.so.1` soname alias is created
regardless of which loader is in place. ECLIPSE_XORG_MUSL=0 opts out.
xtask builds; fmt/clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft feat(build): use Alpine's musl as the one loader so Alpine X binaries…
With the Alpine-musl loader in place, `mcookie` now creates the xauth cookie and the Alpine Xorg binary actually runs — far enough to try opening its log and abort fatally: "Cannot open log file /var/log/Xorg.0.log". The staged base has no /var/log. Create it in the full rootfs (xorg staging) and in the minimal live/installer root (build_live_rootfs), which is what boots under QEMU. Xorg creates /tmp/.X11-unix itself at runtime, so only /var/log is needed here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…ao0ib fix(lunarbar): agent-review findings — keycodes, popup zone, stale-in…
A dynamically-linked crash (labwc and its libraries) left only `unhandled page fault @ … pc=0x7f…` plus a `Done(139)` — the PC is an absolute address with no indication of which mapping it belongs to, so it could not be symbolised. Add `VmAddressRegion::describe_addr`, which locates the mapping containing an address and returns its base, the byte offset into the backing file (VMO), and the backing object's name (a file path for a file mapping, already set by the file get_vmo path; empty for anonymous memory). The SIGSEGV handler now prints, for both the faulting PC and the bad data address: [crash] pid=N pc 0x7f… in /usr/lib/libwlroots.so.13 + 0x1234 (map base 0x7f…) so the exact fault can be located offline with `addr2line -e <file> 0x<off>` / `objdump`, turning an opaque compositor `Done(139)` into a pinned crash site. Anonymous mappings (heap/stack/JIT) print as `[anon] + off`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft diag(crash): name the library + offset a userspace SIGSEGV faults in
init wires a supervised service's stdio to /dev/null, so a black-screen labwc bring-up left no diagnostics anywhere — impossible to tell an output problem (no DRM output / mode) from an input one (libinput found no pointer) from a plain render issue. The labwc wrapper now redirects the real binary's stdout and stderr to /tmp/labwc.log, so `cat /tmp/labwc.log` after a failed or blank start shows wlroots' backend/output/connector discovery, the libinput devices it opened, and any errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft diag(labwc): capture the compositor's log to /tmp/labwc.log
This reverts commit a3418cc.
labwc runs stably now (the SIGSEGV is gone) but the screen is black with no cursor, and /tmp/labwc.log is empty — labwc's default WLR_ERROR level logs nothing when it starts without errors, i.e. it likely composited fine and the frame just never reached the display. Since the software cursor is drawn INTO the frame (WLR_NO_HARDWARE_CURSORS=1), "black" and "no cursor" are one symptom: the composited frame isn't being scanned out. Two diagnostics to localise it in a single rebuild: - labwc wrapper now passes `-d` (labwc's documented debug flag → wlroots WLR_DEBUG), so /tmp/labwc.log shows backend/output/mode/page-flip activity — telling us whether wlroots even creates an output and page-flips. - `present_now` logs, one-shot: the first present (fb id, crtc, active vs graphics VT, software-KMS path) and the first VT-gated drop. This separates "compositor never presents" (no present log) from "presents are suppressed because a text VT is foreground" (drop log) from "presents happen but the blit doesn't show" (present log, no drop, still black). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft diag(labwc,drm): full wlroots debug log + one-shot present/VT-gate trace
`copy_into_live` (the step that stages usr/bin + usr/lib + the glibc loader trees into the RAM-resident live root QEMU actually boots) only ran when Xorg was installed in the full rootfs. labwc reached the live initramfs only as a side effect of that Xorg sweep — so a labwc-only build, or one where the Xorg apk step failed, booted with the compositor binary and libwlroots ABSENT. The wrapper (usr/local/bin/labwc, which IS in LIVE_KEEP) then found no /usr/bin/labwc, printed "real binary not found (apk add labwc)" and exited 127, and eclipse-init's respawn loop restarted it forever — a black screen with the compositor never actually present, easy to misdiagnose as an output or input bug when the binary was never there to begin with. Gate `copy_into_live` on `have_xorg || have_labwc` instead of `have_xorg` alone, and move the Xorg apk-enabled check (`enabled()`) onto the `have_xorg` term only, so `ECLIPSE_XORG_LIVE` remains the master switch but a labwc-only build (or `ECLIPSE_XORG=0`) still gets its compositor staged into the live root. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
Recoge los hallazgos concretos de la busqueda de fallos: aliasing UB en ROUTES_STORAGE con dos NICs, deteccion de fin de TX por TDH en vez del bit DD, offsets de FEXTNVM6/7 que apuntan a MMIO reservado, SECRC sin activar fuera de PCH, IDs igb que el resto del driver no puede manejar, reensamblado RX inalcanzable, y una decena mas de severidad media/baja. Sin correcciones aplicadas todavia. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFLBAPffWxDbLjrj9TEoW4
docs(e1000e): auditoria de bugs del driver e1000e.rs
Cambios en drivers/src/net/e1000e.rs: - ROUTES_STORAGE: Box::leak por-NIC en vez de static mut compartido (aliasing UB + rutas cruzadas entre tarjetas). - can_send()/send(): deteccion de fin de TX por el bit DD del descriptor en vez de una lectura de TDH (Intel documenta que TDH refleja prefetch, no el write-back real); init_tx pre-marca el anillo TX con DD=1. - FEXTNVM6/FEXTNVM7: offsets corregidos a 0x00010/0x000E4 (antes caian en MMIO reservado junto a PBA y los workarounds de ULP/SPT eran no-ops). - RCTL_SECRC: incondicional (antes solo en is_pch(), dejando 4 bytes de FCS pegados a cada frame en 82574L/QEMU). - matched(): se retiran los IDs de familia igb (I210/I211) que el resto del driver no puede manejar (RXDCTL nunca se habilita para ellos). - poll_pending: auto-recuperacion si la cola de jobs diferidos evictó el bottom-half y dejo el flag/IMS atascados. - process_rx_slot: tope de reensamblado real (antes BUF_SIZE hacia la rama de fusion inalcanzable) y contabiliza el descarte en rx_dropped. - tx_dropped/rx_csum_bad: campos por instancia en vez de estaticos de archivo compartidos entre NICs. - Nombre de interfaz: incluye device/function del PCI para no colisionar entre dos NICs en el mismo bus. - CTRL_FRCSPD/CTRL_FRCDPX: bits corregidos (11/12, estaban al reves). - FWSM_FW_VALID: constante duplicada y sin uso, eliminada. - TIPG: IPGR2 corregido a 6 (antes 12). - TXDCTL.QUEUE_ENABLE: log de aviso si el latch no llega a activarse. Anade el modulo de test tx_ring_tests (4 tests) que ejercita el nuevo camino DD-bit de TX contra un NIC simulado. cargo test --features mock e1000e: 11/11 OK. cargo build (no_std real): OK. cargo clippy: limpio. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFLBAPffWxDbLjrj9TEoW4
…k-screen cause Root cause of the labwc black screen / missing cursor, found by reproducing it end-to-end in QEMU with a glibc labwc/wlroots stack: the in-kernel ELF loader (`zircon_object::util::elf_loader::relocate`, which eagerly relocates the ELF interpreter itself before it ever runs — this kernel's substitute for a real ld.so's userspace self-bootstrap) silently SKIPPED `R_X86_64_IRELATIVE` (type 37) relocations, leaving those GOT slots at zero. glibc's own dynamic linker links against CPU-feature-dispatched IFUNC routines (optimized memcpy/memset/strlen/…) even for internal use during its own bootstrap, so a real glibc interpreter's `.rela.plt` legitimately carries many IRELATIVE entries. Every call through an unresolved one jumped to address 0. This explained the exact observed symptom: the interpreter bootstraps far enough to load and run the main program, and labwc logged correctly through its whole config/output-setup phase (none of which happened to call a broken slot) — modeset even succeeded — but the first render pass, heavy on memcpy/memset for the software (pixman) blit path, hit one and the process went silently dark. No crash, no log line: just nothing ever drawn. Unlike every other relocation type here (which write a fixed address), IRELATIVE's value is the RETURN VALUE of *calling* the resolver — exactly what a real ld.so does for its own IFUNCs (glibc's `elf_ifunc_invoke`). Implemented by actually running the resolver: a brief, synchronous ring-3 excursion into the target address space using the same primitive every thread already uses (`UserContext::enter_uspace`), with a deliberately-unmapped sentinel return address so the resolver's `ret` traps into a recognisable, clean instruction- fetch page fault instead of jumping into garbage. A hardware interrupt (the timer) arriving mid-call is serviced and the same call is resumed exactly where it left off, precisely as the normal thread-execution loop already does for ordinary user code. Any OTHER trap aborts the whole batch rather than writing a possibly-bogus value into a live GOT slot — the same fail-closed contract `relocate()` already has for every other error path. `relocate()` gains a second `scratch_vmar` parameter: the interpreter's own sub-VMAR is sized exactly to its LOAD segments with zero spare room for the resolver's throwaway stack page, so the scratch mapping borrows space from the PARENT address space (passed in from both `loader/mod.rs` call sites) instead. x86_64-only (the only arch this was reproduced and verified on); every other architecture keeps the previous skip-and-warn behaviour unchanged. Verified in QEMU with the full glibc labwc/wlroots/lunarbg/lunarbar/foot desktop stack: before this fix, labwc modesets and then goes silent (black screen, no cursor); after, lunarbg renders the wallpaper, lunarbar's panels come up, foot's terminal starts, and the compositor shows a fully rendered desktop with a visible cursor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
`present_now`'s one-shot "first present" log (added earlier this session) passed `DRM_STATE.lock().graphics_vt` directly as a `warn!()` argument. Rust extends a temporary's lifetime to the end of its ENCLOSING STATEMENT, so that MutexGuard stayed alive for the whole macro call — including the log line's formatting and serial write, not just the field read. `present_now` takes the same lock again a few lines later for VT-gating; on a slow serial console that window was wide enough to strand another CPU on it, tripping the >8s deadlock detector (`HOLDER cpu=N at drm.rs:818`, the line this diagnostic added). Read `graphics_vt` into a local first, so the guard drops immediately after the field read — before `warn!` ever runs — matching how the field is read everywhere else in this file. Found while reproducing the labwc black screen end-to-end in QEMU: this diagnostic's own bug intermittently wedged the compositor's very first present. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft Claude/labwc eclipse desktop 08f1ft
fix(e1000e): corrige los 13 bugs de la auditoria previa
A labwc build that hangs right after "Loading user-specified backends due to WLR_BACKENDS: drm,libinput" (the log line printed just before wlroots opens its libseat session) is most likely blocked in the libseat<->seatd handshake — but seatd's own stdout/stderr was wired to /dev/null by init like every other supervised service's, leaving no visibility into whether seatd ever accepted the connection, what backend/permission state it saw, or where the handshake actually stalled. Same fix as the labwc wrapper's /tmp/labwc.log: redirect seatd's output (with -l debug for full protocol tracing) to /tmp/seatd.log. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
…8f1ft diag(seatd): capture seatd's own log to /tmp/seatd.log
El camino RX no agrupaba ningun acceso MMIO por paquete: cada frame recibido pagaba su propio round-trip completo, sin importar cuantos llegaran en la misma rafaga. Bajo QEMU (el objetivo de pruebas habitual de este driver) cada acceso MMIO tipicamente dispara una VM exit completa, asi que esta sobrecarga por-paquete domina el coste real de mover los bytes. Cambios en drivers/src/net/e1000e.rs, todos en el camino RX: - receive() cachea RDH una sola vez por llamada en vez de releerlo por MMIO en cada iteracion del bucle de drenaje (hasta 2 lecturas por iteracion antes). process_rx_slot ya no relee RDH: el invariante lo garantiza quien llama. - El doorbell RDT se difiere (rx_doorbell_dirty) y se agrupa con flush_rx_doorbell(), llamado una vez por rafaga en poll_with_irq_hint (tras iface.poll()) y en NetScheme::recv(). Se elimina tambien la lectura de flush sincrona que seguia a cada escritura de RDT. - ensure_rx_armed_if_link_up ya no relee STATUS por MMIO cuando el enlace ya se sabe activo (se invoca en cada poll). - La invalidacion de cache del buffer RX usa la longitud real del frame en vez de BUF_SIZE completo (2048B) — nada lee mas alla de esa longitud. Nuevo test rx_doorbell_is_batched_not_rung_per_packet que verifica el agrupamiento explicitamente. Los 13 tests existentes (RX, TX, coherency bench) siguen en verde; cargo build (no_std real) y clippy limpios. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFLBAPffWxDbLjrj9TEoW4
perf(e1000e): agrupa el doorbell RDT y elimina MMIO redundante en RX
La fila DISK del banco corria en el cwd del shell, que en ambos harnesses
es una raiz en RAM: medias la cache de paginas, no un filesystem sobre un
dispositivo de bloque. Esto adjunta una imagen btrfs identica a los dos
kernels por el MISMO controlador ich9-ahci, la monta y mide alli, asi que
el numero refleja las rutas de escritura/lectura/metadatos de btrfs sobre
el driver AHCI y nada mas.
Tres piezas:
- `ROOTKEEP=1` (linux-object/fs): conserva el medio de arranque como / y
se salta el auto-pivot. Sin el, el arranque agarra como raiz CUALQUIER
btrfs/ext2 de disco completo que encuentre -- correcto para un sistema
instalado, pero secuestra / en cuanto se adjunta un disco de datos. Con
el, el disco extra queda sin montar para que el usuario lo monte donde
quiera. Es un arreglo real, no solo para el banco.
- `-d IMG` en los dos harnesses (scripts/qemu-{,linux-}bench.sh): adjunta
una imagen raw por su propio ich9-ahci, fuera de la ruta de arranque, de
modo que el dispositivo bajo prueba es inequivoco. El harness de Linux
ademas descomprime y carga la pila modular libahci+ahci+btrfs desde el
/lib/modules del anfitrion antes de montar (ahci y btrfs son modulos en
el kernel de la distro; libata/sd_mod son builtin).
- scripts/qemu-btrfs-bench.sh: formatea una imagen maestra con el layout
exacto que la crate btrfs in-tree de Eclipse emite y lee
(-O ^free-space-tree, crc32c, nodos de 16K), da una copia fresca a cada
kernel (QEMU escribe in situ), corre --only disk sobre el montaje btrfs
en ambos y los imprime lado a lado.
Verificado de extremo a extremo: Eclipse monta /dev/sda btrfs y
escribe/lee; Linux carga los modulos, monta el mismo /dev/sda y
escribe/lee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwhtMqzF6Lq18WnrhJD3zR
…zada La comparacion pareada (misma imagen btrfs, mismo ich9-ahci, working set de 128 MiB) sobre disco real en vez de la cache de paginas. Eclipse pierde en todo y catastroficamente en lectura: secuencial 2,8 vs 379 MB/s (135x), aleatoria 4K 35 vs 29132 IOPS (828x), 28 ms por lectura de 4K. La causa es la ruta de I/O de bloque, tres factores multiplicandose, los tres ausentes en Linux: AHCI de profundidad de cola 1 por sondeo (un comando en vuelo, busy-wait antes del siguiente; Linux usa NCQ x32 con interrupciones), cache de bloque de 8 MiB contra 128 MiB de working set (94% de fallos; Linux cachea en la page cache del tamano de la RAM), y amplificacion de metadatos de btrfs (cada lectura logica de 4K recorre dos B-trees de nodos de 16K, y con la cache pequena cada una dispara varias lecturas fisicas a profundidad 1). La escritura sufre menos (5,7x) por agrupacion y la latencia de fsync empata: el coste esta en el volumen de lecturas concurrentes, no en la barrera de durabilidad. Se documentan las tres palancas por orden de impacto/coste (cache mas grande = una constante; NCQ+IRQ = rediseno del driver; readahead secuencial), medibles con el harness nuevo de una pasada. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NwhtMqzF6Lq18WnrhJD3zR
…aults; virtio-blk multi-sector panic
btrfs's per-inode read-extent cache always extended its scan from the OLD
high-water mark to the new offset, and never shrank: a page fault landing
past the cached range (exactly what a dynamic linker's scattered access into
a large mmap'd .so looks like) forced a full extent-tree walk over the
entire abandoned gap, and kept every extent seen so far in a Vec that gets
linearly rescanned on every subsequent read. Cost grew with how far into the
file a fault landed, not with how many pages were actually touched. Jumping
past the cached range now restarts the scan at the requested offset instead
of dragging the gap along, so a scattered access pattern costs work
proportional to what it actually reads.
Verified against the existing btrfs-rs test suite (unchanged, all passing)
and a host-side benchmark against a real generated rootfs.btrfs image.
Separately, VirtIoBlk::{read,write}_block forwarded whatever buffer length
BlockScheme callers passed straight to the vendored virtio-drivers crate,
whose VirtIOBlk::{read,write}_block hard-assert exactly 512 bytes and moves
one sector per call. linux-object's BlockByteDevice/CachedDevice issue
multi-sector requests (e.g. the 64 KiB read-ahead) to any BlockScheme, which
would panic the kernel the first time it hit virtio-blk. Split multi-sector
requests into per-sector calls, matching how the AHCI driver already handles
arbitrary-length buffers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nKUQTKTwoqrwgwQk7DiP9
…perf-889jeu fix(btrfs+virtio-blk): O(touched-range) extent rescans on mmap page f…
…128x
El comparativo btrfs sobre AHCI dio lectura aleatoria de 4K a 35 IOPS
(28 ms/op), 828x tras Linux. La causa NO era el driver: `dd` crudo sobre
/dev/sda da 108 MB/s en bloques grandes. Era la politica de cache y
readahead de CachedDevice sobre el.
Dos fallos, ambos medidos:
1. El readahead se aplicaba a TODA lectura menor que la ventana, aleatoria
incluida. Cada lectura de 4K arrastraba la ventana entera (1 MiB = 256x
de mas) y expulsaba justo los metadatos de btrfs que la siguiente
lectura volvia a pedir -- puro thrash. Ahora el readahead solo se
dispara en continuacion byte-exacta de la lectura anterior: un stream
secuencial colapsa en un comando grande por ventana; un paseo aleatorio
pide solo sus 4K y deja el cache lleno de los metadatos que reusara.
2. El cache era de 8 MiB contra working sets mucho mayores. Subido a
64 MiB, cubre los metadatos de btrfs y buena parte de los datos
calientes.
Medido en QEMU, misma imagen btrfs sobre el mismo AHCI:
lectura aleatoria 4K 35 -> 4472 IOPS 128x
latencia aleatoria 28380 -> 224 us 127x
escritura secuencial 7,7 -> 31,9 MB/s 4,1x
lectura secuencial 2,8 -> 10,3 MB/s 3,7x
fsync (mejor) 6,43 -> 1,72 ms
crear ficheros 134 -> 240 files/s
Probada y descartada por medida la deteccion floja de secuencialidad
("hacia delante dentro de una ventana"): hundia el aleatorio 10x
(4472 -> 446 IOPS) porque btrfs si mete lecturas aleatorias dentro de una
ventana; la version estricta gano con datos. Arranque normal verificado
sin regresion con el cache mayor.
Queda abierta la lectura secuencial (10 vs 108 MB/s crudos): los
metadatos intercalados de btrfs rompen la cadena secuencial estricta, asi
que muchas lecturas no disparan readahead. Necesitaria estado de readahead
por-stream, no una constante.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwhtMqzF6Lq18WnrhJD3zR
…peed-9fkd4w Claude/eclipse os processing speed 9fkd4w
This branch never merged master's 1b1d289 ("mejoras a drivers, velocidad, procesamiento y demas"), so has_ready()'s lock-contention fallback and AHCI's zero-copy DMA are already in their safe state here -- but a future merge from master could silently reintroduce either: - has_ready(): that commit flipped the try_lock() failure fallback from true to false, contradicting its own doc comment and reintroducing a lost-wake class of bug (a CPU can halt through a wake it hasn't observed yet, with no timer backstop in the executor's idle path). - AHCI zero-copy DMA: that commit re-enabled a path deliberately disabled with `&& false` since June, with no coherence/pinning audit against real hardware. The equivalent functional fix (flip false back to true, redisable the DMA path) still needs to land on master itself, where 1b1d289 actually lives -- a direct push there was blocked by this session's auto-mode guard, so it's pending a separate approval/route. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHCn5RNRcwR5PY1sYBHY2S
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.