From 34c7962b29279cfc240be640e365079c1929e7bd Mon Sep 17 00:00:00 2001 From: sergio Date: Tue, 28 Jul 2026 22:49:46 -0400 Subject: [PATCH] =?UTF-8?q?gnome:=20cae=20el=20SIGSEGV=20=E2=80=94=20el=20?= =?UTF-8?q?shell=20toma=20DRM=20master=20e=20input;=20muro=20nuevo=20en=20?= =?UTF-8?q?la=20hebra=20de=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dos bugs REALES de arje-logind-compat, encontrados arrancando la imagen y arreglados (con test) en el árbol de tawasuyu — todavía LOCALES, sin pushear: 1. La sesión se creaba PEREZOSAMENTE. `ensure_user` sólo corría dentro de un método del Manager, y `write_login_state` vive adentro. Pero mutter no empieza por D-Bus: lo primero que hace es sd_pid_get_session(), que arje-sdlogin-compat resuelve LEYENDO /run/systemd/sessions. Huevo y gallina: el estado en disco sólo se escribía después de un pedido que sólo ocurre si el estado ya existe. Ahora se crea al arrancar (eager_session). 2. El object path de la Session estaba MAL ESCAPADO: era `/session/_1`. La convención de systemd (bus_label_escape) codifica `_` todo lo que no sea [A-Za-z0-9] **y también el primer carácter si es dígito** ⇒ el id "1" da `_31`. Importa porque el cliente calcula el path por su cuenta y NO pregunta: mutter reimplementa la misma regla en meta-dbus-utils.c. Con `_1` no había nadie sirviendo ahí, la propiedad `Seat` volvía NULL y mutter —que no chequea— moría de SIGSEGV en get_seat_proxy. Los objetos User NO usan este escapado (systemd hardcodea `_`), así que user_path() queda igual. Y ARJE_LOGIN_STATE=1 YA EXISTÍA: el comentario del daemon dice literalmente "en arje (sin systemd) el launcher de sesión lo prende. Default off" — y el launcher es gnome-start. El puente que escribía /run/systemd/ a mano era reinventar esa perilla; queda de fallback inerte. Resultado: el shell ya no crashea. Corre con 12 hilos, /dev/dri/card0 abierto tres veces y /dev/input/event0 abierto — el TakeDevice de logind funciona y el compositor tiene DRM master e input. Queda bloqueado en meta_seat_impl_initable_init (meta-seat-impl.c:3154): espera en un condvar a que la "Mutter Input Thread" avise que inicializó, y nunca avisa. Hipótesis principal libudev-zero, que ya dio un episodio idéntico en el frente de la USB nvidia. El gnome-start ahora le fuerza un core con SIGABRT al proceso colgado: sin gdb en la imagen es la única forma de ver dónde está parado. Co-Authored-By: Claude Opus 5 (1M context) --- docs/runbooks/gnome-qemu-desktop.md | 99 +++++++++++++++-------------- scripts/gnome/gnome-start-qemu.sh | 27 +++++++- scripts/gnome/qemu-desktop-image.sh | 10 ++- 3 files changed, 86 insertions(+), 50 deletions(-) diff --git a/docs/runbooks/gnome-qemu-desktop.md b/docs/runbooks/gnome-qemu-desktop.md index 6f690d53..9354c187 100644 --- a/docs/runbooks/gnome-qemu-desktop.md +++ b/docs/runbooks/gnome-qemu-desktop.md @@ -1,8 +1,9 @@ # Runbook — arrancar gnome-shell en QEMU (y dónde está el muro hoy) -Estado al 2026-07-29: **gnome-shell arranca, toma el rol de display server Wayland y muere de -SIGSEGV en `meta_launcher_new`**. La causa está localizada con precisión (ver abajo); no es un -misterio pendiente de diagnosticar, es una pieza de arje que falta implementar. +Estado al 2026-07-29: **gnome-shell arranca, toma el DRM master y los dispositivos de input por +logind, y queda BLOQUEADO esperando que termine de inicializarse la hebra de input de mutter.** El +SIGSEGV que había antes está arreglado (dos bugs reales de `arje-logind-compat`, abajo). Nada de +esto es un misterio pendiente de diagnosticar: cada muro está localizado por backtrace. Hermano de [`kde-qemu-desktop.md`](kde-qemu-desktop.md), del que reusa el andamiaje: la base `work/metal-rootfs`, la inyección de mesa-llvmpipe/LLVM/musl, el truco del console-getty y el propio @@ -35,59 +36,65 @@ gdb -batch -q -ex "set sysroot $PWD/work/gnome-qemu-rootfs" \ -ex "core-file /tmp/core.gnome-shell" -ex "bt 25" work/gnome-qemu-rootfs/usr/bin/gnome-shell ``` -## EL MURO DE HOY — y por qué no es un bug de mutter que haya que rodear +## EL MURO DE HOY — la hebra de input de mutter ``` -#4 g_variant_get (value=0x0, format_string="(s&o)") ← GVariant NULO -#5 get_seat_proxy (...) at src/backends/meta-launcher.c:407 -#6 meta_launcher_new (flags=META_LAUNCHER_FLAG_TAKE_CONTROL) at meta-launcher.c:464 -... -#15 main () at src/main.c:664 +#5 g_cond_wait glib/gthread.c:1686 +#7 meta_seat_impl_initable_init src/backends/native/meta-seat-impl.c:3154 +#8 meta_seat_native_constructed src/backends/native/meta-seat-native.c:146 +#12 meta_backend_native_create_default_seat +#14 init_clutter src/backends/meta-backend.c:1293 ``` -Mutter pide la propiedad `Seat` del objeto **Session** de logind — el `(s&o)` es la firma estándar -de esa propiedad: `(nombre_del_seat, object_path_del_seat)`. `arje-logind-compat` adquiere -`org.freedesktop.login1` y sirve el **Manager**, pero no expone un objeto Session con esa propiedad, -así que la lectura devuelve NULL. Mutter no chequea y desreferencia ⇒ SIGSEGV. +El hilo principal arranca la «Mutter Input Thread» y **espera en un condvar a que ésa avise que +terminó de inicializarse. Nunca avisa.** El proceso queda vivo e inmóvil: 12 hilos, `State: S`, +`wchan=futex_do_wait`, con `/dev/dri/card0` abierto TRES veces y `/dev/input/event0` abierto — o sea +que el `TakeDevice` de logind YA funciona y el compositor tiene DRM master e input. Lo que no cierra +es la inicialización del asiento de libinput. -Que mutter no valide es una fragilidad suya, pero **el hueco es nuestro**: falta el objeto Session. +**Hipótesis principal, y no es nueva en este repo: `libudev-zero`.** `libinput_udev_assign_seat()` +enumera y monitorea por udev, y el rootfs no trae udev de verdad. Ya hubo un episodio idéntico en +otro frente —«input muerto = eudev pisó libudev-zero», ver [[mirada-usb-nvidia]]— así que el próximo +paso es mirar por ahí antes que en mutter. -### La cadena completa que hubo que armar para llegar hasta acá +Para volver a sacar este backtrace: el `gnome-start` **le fuerza un core al proceso colgado con +SIGABRT** después del timeout (sin gdb en la imagen es la única forma de ver dónde está parado), y +lo deja en la raíz con `sync`. -Cada eslabón se descubrió arrancando la imagen, y ninguno estaba anotado antes: +## LO QUE YA SE ARREGLÓ (dos bugs reales en arje-logind-compat) -1. **`gschemas.compiled` no existe.** meson NO lo genera cuando `DESTDIR` está seteado (lo dice en - el log del build: *"Skipping custom install script because DESTDIR is set"*). Sin él GSettings - aborta en el primer `g_settings_new()`. Se compila en el primer arranque. -2. **`GI_TYPELIB_PATH` con `/usr/lib/gnome-shell`.** St/Shell/Gvc/Shew se instalan APARTE de - `/usr/lib/girepository-1.0` porque son privados del shell. Es el env que no tiene análogo en KDE. -3. **`/var/run` no existía.** `arje-logind-compat` busca el bus en la ruta legacy - `/var/run/dbus/system_bus_socket`; sin el symlink reporta `bus_present=false` y se va a modo idle. -4. **Política D-Bus de `login1` ausente.** `system.conf` trae ``; normalmente systemd - instala el fichero de política del servicio. Acá no hay systemd y el artefacto de - arje-logind-compat es sólo el binario ⇒ hubo que escribirla. **Pertenece al artefacto de arje, - no a la imagen**; está en el script de imagen sólo para que se vea qué falta empaquetar. -5. **`/run/systemd/{sessions,seats,users}` vacíos.** `libelogind` (arje-sdlogin-compat) resuelve la - C-ABI sd-login LEYENDO esos ficheros, y nadie los escribe: el `Announce` de arje-logind-compat al - bus del fractal falla con `identity mismatch`, así que nunca hay sesión registrada. El - `gnome-start` los escribe A MANO — **eso es ANDAMIO, y está marcado como tal en el script**. Con - ese puente desaparece el `Failed to setup: Failed to find any matching session`. -6. **Y entonces aparece el muro real**: el objeto Session de D-Bus (arriba). +Ambos encontrados arrancando esta imagen, ambos con test, ambos **locales todavía — falta +commitear/pushear tawasuyu y re-pinear la receta**: -### Gotcha de diagnóstico que costó una iteración +1. **La sesión se creaba PEREZOSAMENTE.** `ensure_user` sólo corría dentro de un método del Manager, + y `write_login_state` vive adentro. Pero mutter no empieza por D-Bus: lo primero que hace es + `sd_pid_get_session()`, que arje-sdlogin-compat resuelve LEYENDO `/run/systemd/sessions`. Huevo y + gallina — el estado en disco sólo se escribía después de un pedido que sólo ocurre si el estado + ya existe. Ahora la sesión se crea al arrancar (`eager_session`, apagable con + `ARJE_LOGIN_EAGER=0`, uid por `ARJE_LOGIN_UID`). +2. **El object path de la Session estaba mal escapado.** Era `/session/_1`; la convención de systemd + (`bus_label_escape`) codifica como `_` todo lo que no sea `[A-Za-z0-9]` **y también el primer + carácter si es dígito**, así que el id `"1"` da **`_31`**. Importa porque el cliente calcula el + path por su cuenta y no pregunta: mutter reimplementa la misma regla en + `meta-dbus-utils.c:escape_dbus_component`. Con `_1` no había nadie sirviendo ahí, la propiedad + `Seat` volvía NULL y mutter —que no chequea— moría de **SIGSEGV en `get_seat_proxy`**. Los objetos + `User` NO usan este escapado (systemd hardcodea `_`), así que `user_path()` queda igual. -`kill -0 ` **tiene éxito sobre un ZOMBI** — el hijo existe como PID hasta que el padre lo -cosecha, y el padre es el propio `gnome-start`, que sólo hace `wait` al final. La primera versión del -script usaba `kill -0` y reportaba *"sin wayland-0 tras 45s"* cuando en realidad el shell había -muerto en el primer segundo. Se mira el `State:` de `/proc//status` (vacío o `Z` = muerto), y -así sale el código real: **139 = SIGSEGV**. +De paso, un arreglo de portabilidad musl preexistente: `libc::ioctl` toma `c_ulong` en glibc y +`c_int` en musl, así que `DRM_IOCTL_SET_MASTER` se guarda pelado y se convierte en el sitio de uso. + +**Y `ARJE_LOGIN_STATE=1` ya existía**: el comentario del daemon dice literalmente «en arje (sin +systemd) el launcher de sesión lo prende. Default off» — y el launcher es `gnome-start`. El puente +que escribía `/run/systemd/` a mano fue reinventar esa perilla; queda como fallback inerte. ## Lo que falta, en orden -1. **Objeto Session en `arje-logind-compat`** con la propiedad `Seat` (y `TakeControl`/`TakeDevice` - sobre él, que es lo que mutter llama enseguida para el DRM master). Esto destraba el arranque. -2. **Que escriba `/run/systemd/{sessions,seats,users}`** en vez del puente a mano — y de paso - arreglar el `identity mismatch` del Announce. -3. **Empaquetar la política D-Bus** dentro del artefacto de arje-logind-compat. -4. Recién después: el muro gráfico (GBM/EGL software sobre virtio-gpu), donde KDE ya dejó los fixes - pagos y precargados en `gnome-start-qemu.sh` (`GBM_ALWAYS_SOFTWARE`, `kms_swrast`, `LP_NUM_THREADS=1`). +1. **La hebra de input de mutter** (el muro de hoy): mirar `libudev-zero` primero. +2. **Commitear y pushear tawasuyu** con los dos arreglos de arje-logind-compat, y re-pinear el + `commit` de `recipes/arje-logind-compat.toml` para sellar el artefacto de verdad. Hoy la imagen + corre un binario compilado a mano vía `ALC_BIN=`, que **no es reproducible**. +3. **Empaquetar la política D-Bus de login1** dentro del artefacto de arje-logind-compat. +4. Arreglar el `identity mismatch` del `Announce` al bus del fractal (hoy sólo un WARN). +5. Recién después: el muro gráfico (GBM/EGL software sobre virtio-gpu), donde KDE ya dejó los fixes + pagos y precargados en `gnome-start-qemu.sh` (`GBM_ALWAYS_SOFTWARE`, `kms_swrast`, + `LP_NUM_THREADS=1`, `MUTTER_DEBUG_SEND_KMS_MODIFIERS=0`). diff --git a/scripts/gnome/gnome-start-qemu.sh b/scripts/gnome/gnome-start-qemu.sh index 1c4ba993..5182ee60 100755 --- a/scripts/gnome/gnome-start-qemu.sh +++ b/scripts/gnome/gnome-start-qemu.sh @@ -48,6 +48,16 @@ export LP_NUM_THREADS=1 # en virtio-gpu con render software ⇒ cursor invisible) y modeset simple en vez de atomic. export MUTTER_DEBUG_DISABLE_HW_CURSORS=1 export MUTTER_DEBUG_FORCE_KMS_MODE=simple +# MUTTER_DEBUG_KMS_THREAD_TYPE=user: mutter corre su hebra KMS con prioridad de TIEMPO REAL por +# defecto. En esta VM no hay privilegios de RT scheduling, y el síntoma es exactamente el que dio el +# diagnóstico — hilo principal dormido en `futex_do_wait` con la "KMS thread" viva pero sin avanzar, +# el shell con /dev/dri/card0 ya abierto y ningún socket wayland creado nunca. +export MUTTER_DEBUG_KMS_THREAD_TYPE=user +# MUTTER_DEBUG_SEND_KMS_MODIFIERS=0: el equivalente en mutter del KWIN_DRM_USE_MODIFIERS=0 que costó +# la campaña KDE. virtio-gpu ANUNCIA soportar drmModeAddFB2WithModifiers pero RECHAZA el modifier +# explícito (aun LINEAR=0) con EINVAL ⇒ no se puede crear el framebuffer del compositor. Los nombres +# de estas variables están verificados contra los strings de libmutter-16.so.0, no de memoria. +export MUTTER_DEBUG_SEND_KMS_MODIFIERS=0 if [ "${DIAG:-1}" = 1 ]; then export G_MESSAGES_DEBUG=all @@ -96,6 +106,11 @@ say "dbus sesión: ${DBUS_SESSION_BUS_ADDRESS:-NO ARRANCO}" # SU sesión vía la C-ABI sd-login, que libelogind resuelve LEYENDO /run/systemd/sessions. Sin este # daemon esos ficheros no existen y mutter muere en "Failed to find any matching session". if [ -x /usr/bin/arje-logind-compat ]; then + # ARJE_LOGIN_STATE=1: la perilla que YA traía el daemon y que nadie prendía. Su comentario en + # arje-compat lo dice explícito — "en un host CON systemd real pisaría su estado; en arje (sin + # systemd) el launcher de sesión lo prende. Default off" — y el launcher de sesión es ESTE script. + # Con ella escribe /run/systemd/{sessions,seats,users}, que es lo que libelogind lee. + export ARJE_LOGIN_STATE=1 /usr/bin/arje-logind-compat >/tmp/logind-compat.log 2>&1 & i=0 while [ $i -lt 40 ]; do @@ -175,7 +190,7 @@ SHELL_PID=$! # la entrada zombi. Se mira el State de /proc en su lugar. alive() { s=$(sed -n 's/^State:[[:space:]]*//p' /proc/$1/status 2>/dev/null); case "$s" in ""|Z*) return 1;; *) return 0;; esac; } i=0 -while [ $i -lt 90 ]; do +while [ $i -lt 240 ]; do [ -S "$XDG_RUNTIME_DIR/wayland-0" ] && break alive $SHELL_PID || { wait $SHELL_PID; RC=$?; say "!! gnome-shell murió (código $RC) antes de exponer wayland-0:"; dump /tmp/gnome-shell.log; say "cores: $(ls /core.* 2>/dev/null | tr '\n' ' ' || echo NINGUNO)"; exec /bin/sh; } i=$((i+1)); sleep 0.5 @@ -183,17 +198,25 @@ done if [ -S "$XDG_RUNTIME_DIR/wayland-0" ]; then say "compositor OK (wayland-0) — el shell ES el display server" else - say "!! sin wayland-0 tras 45s:"; dump /tmp/gnome-shell.log + say "!! sin wayland-0 tras 120s:"; dump /tmp/gnome-shell.log # El shell NO murió: quedó COLGADO. Sin gdb en la imagen, el diagnóstico barato es dónde está # bloqueado el hilo principal (wchan) y contra qué socket, que es lo que distingue "esperando una # respuesta D-Bus que no llega" de "spinning en el renderer". say "--- diagnóstico del cuelgue (pid $SHELL_PID) ---" + say "XDG_RUNTIME_DIR ($XDG_RUNTIME_DIR): $(ls -a $XDG_RUNTIME_DIR 2>/dev/null | tr '\n' ' ')" + say "sockets wayland en el sistema: $(find / -name 'wayland-*' -maxdepth 6 2>/dev/null | tr '\n' ' ')" say "wchan: $(cat /proc/$SHELL_PID/wchan 2>/dev/null)" say "state: $(grep -a ^State /proc/$SHELL_PID/status 2>/dev/null)" say "stack principal:"; cat /proc/$SHELL_PID/stack > /dev/ttyS0 2>/dev/null say "syscall: $(cat /proc/$SHELL_PID/syscall 2>/dev/null)" say "fds abiertos:"; ls -l /proc/$SHELL_PID/fd 2>/dev/null > /dev/ttyS0 say "hilos: $(ls /proc/$SHELL_PID/task 2>/dev/null | wc -l)" + # Sin gdb en la imagen, la única forma de ver DÓNDE está bloqueado es forzarle un core y leerlo + # con el gdb del host (`thread apply all bt`). SIGABRT lo produce y no lo maneja nadie. + say "forzando core del proceso colgado (SIGABRT)..." + kill -ABRT $SHELL_PID 2>/dev/null + sleep 12; sync; sync + say "cores: $(ls -la /core.* 2>/dev/null | tr '\n' ' ' || echo NINGUNO)" for t in $(ls /proc/$SHELL_PID/task 2>/dev/null); do say " tid $t wchan=$(cat /proc/$SHELL_PID/task/$t/wchan 2>/dev/null) comm=$(cat /proc/$SHELL_PID/task/$t/comm 2>/dev/null)" done diff --git a/scripts/gnome/qemu-desktop-image.sh b/scripts/gnome/qemu-desktop-image.sh index 26cc6f76..268008f4 100755 --- a/scripts/gnome/qemu-desktop-image.sh +++ b/scripts/gnome/qemu-desktop-image.sh @@ -88,8 +88,14 @@ echo "==> inyectando arje-logind-compat (el login1 del fractal)" # LEYENDO /run/systemd/{sessions,users,seats}. Pero alguien tiene que ESCRIBIR esos ficheros, y ese # alguien es arje-logind-compat, el daemon D-Bus que se hace pasar por org.freedesktop.login1. # libelogind entra solo (es dep de mutter); el daemon no, porque nadie lo declara como dep de build. -ALC="${ALC:-$(ls -dt store/*-arje-logind-compat 2>/dev/null | head -1)}" -if [ -n "$ALC" ] && [ -d "$ALC" ]; then +# ALC_BIN permite inyectar un binario RECIÉN COMPILADO en vez del artefacto sellado. Es para probar +# un cambio en arje-compat antes de commitear/pushear tawasuyu y re-sellar la receta (la receta pina +# un commit, así que sin push no hay artefacto nuevo). Lo que se pruebe así NO es reproducible: es +# andamio de iteración, y el camino de producción sigue siendo el artefacto del store. +if [ -n "${ALC_BIN:-}" ] && [ -x "$ALC_BIN" ]; then + install -Dm755 "$ALC_BIN" "$MERGED"/usr/bin/arje-logind-compat + echo " ⚠ binario LOCAL (no sellado): $ALC_BIN" +elif ALC="${ALC:-$(ls -dt store/*-arje-logind-compat 2>/dev/null | head -1)}"; [ -n "$ALC" ] && [ -d "$ALC" ]; then install -Dm755 "$ALC"/usr/bin/arje-logind-compat "$MERGED"/usr/bin/arje-logind-compat echo " ✓ $(basename "$ALC" | cut -c1-12)" else