diff --git a/docs/phase13-1-c-ion-bug-b2-upstream.md b/docs/phase13-1-c-ion-bug-b2-upstream.md new file mode 100644 index 0000000..2c9ed01 --- /dev/null +++ b/docs/phase13-1-c-ion-bug-b2-upstream.md @@ -0,0 +1,167 @@ +# Bug B.2 — Rapport pour upstream Redox ion + +> Document produit le 2026-05-16 dans le cadre de +> [`phase13-1-c-cursor.md`](phase13-1-c-cursor.md). À copier-coller (en +> traduisant en anglais) dans une issue sur +> `gitlab.redox-os.org/redox-os/ion/-/issues` quand on aura un compte. + +--- + +## Title + +ion: page fault `0x70` when a background job dies on broken pipe (Wayland client + compositor scenario) + +## Environment + +- **Redox image** : harddrive.img du 2026-05-08 (build x86_64 desktop, rebuild depuis `redox-src` la veille) +- **ion** : version embarquée dans cette image (binaire stripped, source upstream synced via cookbook recipe `core/ion`) +- **QEMU** : `qemu-system-x86_64` avec `-enable-kvm -cpu host -k fr` sur Linux host (CachyOS 7.0.8) +- **Repro** : 100 % reproductible sur 3 sessions distinctes avec reboot frais entre chaque + +## Symptôme + +Quand un job background termine sur `Broken pipe` (ex: client Wayland dont le compositor vient de fermer son socket), **ion crashe avec un page fault à l'adresse 0x70**. Le kernel logue UNHANDLED EXCEPTION pour le process ion. Le shell devient indisponible jusqu'au prochain login. + +``` +ion: ([Page fault: 0000000000000070 US +RFLAG: 0000000000010212 +CS: 000000000000002b +RIP: 00000000002335ae +RSP: 0000000000bb0e60 +SS: 0000000000000023 +FSBASE 000000000092b000 +GSBASE 0000000000000000 +KGSBASE ffff80007fed8000 +RAX: 0000000000000070 <-- valeur fautive = offset du field +RCX: 0000000000000000 +RDX: 0000000000233550 +RDI: 0000000000000001 +RSI: 00000000006c9970 +R8: 0000000000000000 +R9: 0000000000000000 +R10: 0000000000000000 +R11: 0000000000000008 +RBX: 000000000092b000 +RBP: 0000000000bb0e70 +R12: 000000000092acc8 +R13: 00000000006afe20 +R14: 0000000000000001 +R15: 00000000006afe20 + FP 0000000000bb0e70: PC 0000000000f27745 + FP 0000000000bb0f40: PC 0000000000f0a167 + FP 0000000000bb0f70: PC 0000000000f0a02d + FP 0000000000bb0ff0: PC 0000000000eabd43 + + FP ffff80001798fe80: PC ffffffff8008a42d + kernel::arch::x86_shared::interrupt::exception::page::inner + FP ffff80001798ff50: PC ffffffff80087ee7 + kernel::arch::x86_shared::interrupt::exception::page + 0000000000bb0e70: GUARD PAGE +kernel::context::signal:INFO -- UNHANDLED EXCEPTION, CPU #3, PID 45, NAME /usr/bin/ion +``` + +Pattern : déréférence d'un pointer null avec accès au champ à l'offset 0x70 (`RAX=0x70`, `addr=0x70`). + +## Repro minimal + +```sh +# Dans une session ion fraîche, avec le compositor Wayland +# (https://gitlab.com/leyoda/redox-wayland-compositor) : +redox-wl-compositor & # backround job [0] +sleep 1 +redox-wl-real-client-simple-window & # background job [1] +sleep 2 +# Maintenant, tuer le compositor SANS quitter le client proprement. +# Le compositor ferme son socket, le client reçoit Broken pipe et exit +# avec status non-zéro. C'est en traitant cette terminaison que ion crashe. +pkill redox-wl-compositor +``` + +Reproductible aussi avec n'importe quelle paire client-serveur Unix : +- serveur en bg qui ferme son socket +- client en bg qui écrit dessus et reçoit EPIPE + +## Hypothèse de localisation (best-effort sans symboles) + +Le crash semble survenir dans `BackgroundEventCallback`. La closure dans +`src/main.rs:224-234` : + +```rust +shell.set_background_event(Some(Arc::new(|njob, pid, kind| match kind { + BackgroundEvent::Added => eprintln!("ion: bg [{}] {}", njob, pid), + BackgroundEvent::Stopped => eprintln!("ion: ([{}] {}) Stopped", njob, pid), + BackgroundEvent::Resumed => eprintln!("ion: ([{}] {}) Running", njob, pid), + BackgroundEvent::Exited(status) => { + eprintln!("ion: ([{}] {}) exited with {}", njob, pid, status) + } + BackgroundEvent::Errored(error) => { + eprintln!("ion: ([{}] {}) errored: {}", njob, pid, error) + } +}))); +``` + +Appelée depuis `src/lib/shell/pipe_exec/job_control.rs:148-228` (`watch_background`), elle-même runs depuis un thread dédié au polling de waitpid. + +Note : sur la sortie observée, **le message "exited with" est imprimé AVANT le page fault** : + +``` +[real-client] FAIL: Backend error: Io error: Broken pipe (os error 32) +root:~# [src/procmgr.rs:299 WARN] Cancellation for unknown id Id(26) +root:~# +ion: ([Page fault: 0000000000000070 ... +``` + +Donc le callback aurait fini son `eprintln!`, le crash arrive juste après — possiblement dans le code qui suit, peut-être le `get_process!(|process| { process.forget(); ... })` ou dans le drop du job, ou dans le retour de thread vers le main thread. + +## Disassembly stripped autour de RIP=0x2335ae + +``` +2335a2: 48 8b 85 60 fe ff ff mov -0x1a0(%rbp), %rax +2335a9: 48 d1 e0 shl $1, %rax +2335ac: 48 85 c0 test %rax, %rax +2335af: 74 0d je 0x2335be +``` + +Pattern Vec/Box drop : "test capacity*2, jump-if-zero, else free pointer". +**Le `0x2335ae` tombe au milieu de l'instruction `test`** — possible que +le strip + offset stable ne pointe pas exactement à la même instruction +sur l'image installée vs le rebuild local. RAX=0x70 au crash suggère un +load `mov [rdi+0x70], rax` (ou similaire) où rdi est null. Le champ à +l'offset 0x70 d'une struct Rust commune (BackgroundProcess ? File ? +Mutex> ?) — à confirmer avec un build debug. + +## Side-bug observé en parallèle + +Sur la même session, juste avant le crash : +``` +[src/procmgr.rs:299 WARN] Cancellation for unknown id Id(26) +``` + +Ce warning vient de relibc (`procmgr`), pas d'ion. Peut être lié — une +cancellation de syscall qui laisse un state inconsistent, qu'ion +manipule ensuite. À investiguer côté relibc aussi. + +## Demande + +1. Identifier la fonction à `0x2335ae` (rebuild non-stripped + addr2line). +2. Confirmer si c'est un null deref dans `process.forget()`, drop de + `BackgroundProcess`, ou la déstructure du `Vec`. +3. Patch. + +## Workaround utilisateur en attendant + +Toujours quitter les clients background **avant** le serveur : + +```sh +# Bon ordre : +redox-wl-real-client-simple-window & +# (interagir) +fg %1 # ramener au foreground +# ESC ou autre pour quitter proprement +pkill redox-wl-compositor # seulement après que tous les clients sont sortis +``` + +## Référence + +Bug détecté pendant le port d'un compositor Wayland minimal sur Redox : +https://gitlab.com/leyoda/redox-wayland-compositor (phase 13.1.b / 13.1.c). diff --git a/run-qemu.sh b/run-qemu.sh index 9bc4206..2fe44c5 100755 --- a/run-qemu.sh +++ b/run-qemu.sh @@ -84,6 +84,24 @@ if [[ ! -e /dev/fuse ]]; then exit 3 fi +# Phase 13.1.c B.4 : warn si l'image est trop petite. L'image desktop par +# défaut fait ~680 Mo et sature dès qu'on tente `pkg update` (cf. session +# de la phase 13.1.b). Pour confort, recommandation : 4 Go minimum, idéal +# 10 Go. La grossir avec : +# qemu-img resize -f raw 10G +# /build/fstools/bin/redoxfs-resize +IMAGE_SIZE_BYTES=$(stat -c %s "$IMAGE" 2>/dev/null || echo 0) +MIN_IMAGE_SIZE=$((4 * 1024 * 1024 * 1024)) # 4 GiB +if [[ "$IMAGE_SIZE_BYTES" -gt 0 && "$IMAGE_SIZE_BYTES" -lt "$MIN_IMAGE_SIZE" ]]; then + image_size_human=$(numfmt --to=iec --suffix=B "$IMAGE_SIZE_BYTES" 2>/dev/null || echo "${IMAGE_SIZE_BYTES} octets") + echo "WARN : image $IMAGE de taille ${image_size_human} (< 4 GiB recommandés)" >&2 + echo " Tu risques 'No space left on device' au moindre pkg update." >&2 + echo " Pour la grossir :" >&2 + echo " qemu-img resize -f raw $IMAGE 10G" >&2 + echo " $REDOX_SRC/build/fstools/bin/redoxfs-resize $IMAGE" >&2 + echo " (warning seulement, on continue)" >&2 +fi + # --- 2. Mount --- mkdir -p "$MOUNT" echo "==> monter $IMAGE -> $MOUNT"