gnome: el muro de input NO era libudev-zero — el fd de TakeDevice era bloqueante

Tres cambios de diagnóstico en `gnome-start` que, juntos, dieron el veredicto:

1. SONDA DE INPUT antes de lanzar el shell. `libinput list-devices` hace lo mismo
   que `init_libinput()` de mutter (udev_new + create_context + assign_seat) pero
   abriendo el devnode por su cuenta. Volvió con rc=0 y los tres dispositivos
   listados ⇒ **la hipótesis libudev-zero del runbook queda REFUTADA**: la
   enumeración por /sys funciona.

2. El volcado POR HILO corría DESPUÉS del `kill -ABRT`, o sea sobre un
   /proc/<pid>/task que ya no existía: salía vacío. Movido antes. Ahí apareció el
   dato: `tid 201 [Mutter Input Th] wchan=evdev_read syscall=0`. El hilo de input
   dormido en un `read()` de evdev dentro del kernel. (El core no servía: gdb no
   desenrolla a través de musl y devuelve `?? ()` para los 11 hilos no principales.)

3. `MUTTER_DEBUG` es una LISTA DE TÓPICOS (g_parse_debug_string sobre
   meta_debug_keys), no un booleano: con `1` no encendía nada. Ahora
   `backend,input,kms`, y el tópico `backend` imprime la última línea antes del
   cuelgue: «Opening and taking control of device file '/dev/input/event0'».

Más: ping D-Bus a login1 en el diagnóstico (el daemon respondía: no estaba
tildado) y copia de los logs de /tmp —que es tmpfs— a la raíz ext4 para poder
sacarlos con debugfs junto al core.

La causa está arreglada en tawasuyu (3dd88f582): `TakeDevice` abría sin
`O_NONBLOCK`. Receta re-pineada a 2445fa31c y re-sellada b3:7ffa9256.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 11:09:54 -04:00
co-authored by Claude Opus 5
parent 609fabdb76
commit 03bdd41b4a
2 changed files with 71 additions and 11 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ version = "0.0.1"
[source]
# El `commit` es el identificador inmutable (la URL es sólo locator, no entra al hash).
repo = "gitea@git.tawasuyu.net:tawasuyu/tawasuyu.git"
commit = "e65e943ef8f6fa77ea9b8565f5d73d9f511ab0df"
commit = "2445fa31c1b3d8b7da037d2434ace976e2444df4"
# El monorepo COMMITEA su propio `vendor/` — `[patch.crates-io] smithay = { path = "vendor/smithay" }`,
# una copia parcheada de smithay que mirada necesita para el tearing. `cargo vendor` de hammer escribe
# en `vendor/` por defecto y lo pisaba: el build moría con
+70 -10
View File
@@ -17,6 +17,12 @@ export PATH=/usr/bin:/bin:/usr/sbin:/sbin
[ -r /etc/gnome-mode ] && . /etc/gnome-mode
say() { echo "== gnome-qemu :: $*" | tee -a /dev/console /dev/ttyS0 2>/dev/null; }
dump() { echo "---- $1 ----" > /dev/ttyS0 2>/dev/null; cat "$1" > /dev/ttyS0 2>/dev/null; }
# OJO con el test de vivo: `kill -0` TIENE ÉXITO sobre un ZOMBI (el hijo existe como PID hasta que
# el padre lo cosecha, y acá el padre es este script, que sólo hace wait al final). La primera
# versión usaba kill -0 y por eso reportó "sin wayland-0 tras 45s" cuando en realidad gnome-shell ya
# había muerto en el primer segundo: el diagnóstico decía "0 hilos" porque no quedaba proceso, sólo
# 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; }
export LD_LIBRARY_PATH=/usr/lib:/lib:/usr/lib/gnome-shell:/usr/lib/mutter-16
# GI_TYPELIB_PATH es EL env crítico de esta sesión, y el que no tiene análogo en KDE: gnome-shell es
@@ -63,7 +69,11 @@ export MUTTER_DEBUG_SEND_KMS_MODIFIERS=0
if [ "${DIAG:-1}" = 1 ]; then
export G_MESSAGES_DEBUG=all
export MUTTER_DEBUG=1
# MUTTER_DEBUG es una LISTA DE TÓPICOS (g_parse_debug_string sobre meta_debug_keys, core/util.c:42),
# no un booleano: con `1` no matcheaba ninguna clave y no encendía nada. `backend` es el que
# imprime «Opening and taking control of device file '<path>'» por cada dispositivo que pasa por
# MetaDevicePool ⇒ dice EXACTAMENTE en cuál se queda parada la hebra de input.
export MUTTER_DEBUG=backend,input,kms
export EGL_LOG_LEVEL=debug
export LIBGL_DEBUG=verbose
fi
@@ -179,6 +189,35 @@ if [ "${DIAG:-1}" = 1 ]; then
fi
say "typelibs visibles: $(ls /usr/lib/girepository-1.0/*.typelib 2>/dev/null | wc -l) sistema + $(ls /usr/lib/gnome-shell/*.typelib 2>/dev/null | wc -l) del shell"
# ── SONDA DE INPUT — barata, y separa las dos hipótesis del muro ────────────────────────────────
# El muro es la hebra de input de mutter: arranca y nunca señala `input_thread_initialized`.
# `libinput list-devices` hace EXACTAMENTE lo que hace `init_libinput()` —udev_new +
# libinput_udev_create_context + libinput_udev_assign_seat("seat0")— pero con `open()/close()`
# pelados en vez del `TakeDevice` de logind. O sea que discrimina:
# · la sonda cuelga o no lista nada ⇒ el muro es libudev-zero/libinput (enumeración por /sys)
# · la sonda lista los dispositivos ⇒ libinput anda; el cuelgue está del lado de mutter
# (MetaDevicePool → TakeDevice, o algo anterior en el hilo: input settings, keymap)
# Ojo: `libinput` es un multiplexor de subcomandos y `list-devices` NO es interactivo, pero igual se
# le pone reloj — si el problema es un cuelgue, la sonda cuelga también, y hay que matarla.
say "sonda: /sys/class/input = $(ls /sys/class/input 2>/dev/null | tr '\n' ' ')"
say "sonda: /dev/input = $(ls /dev/input 2>/dev/null | tr '\n' ' ')"
if [ -x /usr/bin/libinput ]; then
( libinput list-devices >/tmp/libinput-devices.log 2>&1; echo "rc=$?" >>/tmp/libinput-devices.log ) &
LI_PID=$!
i=0
while [ $i -lt 24 ] && alive $LI_PID; do i=$((i+1)); sleep 0.5; done
if alive $LI_PID; then
say "!! SONDA COLGADA: 'libinput list-devices' no volvió en 12s ⇒ el muro ES libinput/libudev-zero"
say " wchan=$(cat /proc/$LI_PID/wchan 2>/dev/null) syscall=$(cat /proc/$LI_PID/syscall 2>/dev/null)"
kill -9 $LI_PID 2>/dev/null
else
say "sonda 'libinput list-devices' VOLVIÓ (no cuelga) — dispositivos:"
dump /tmp/libinput-devices.log
fi
else
say "sonda: no hay /usr/bin/libinput en la imagen"
fi
# GNOME_MODE=headless — EXPERIMENTO DECISIVO, no un modo de producción. El backend headless de
# mutter crea el seat con META_SEAT_NATIVE_FLAG_NO_LIBINPUT, o sea que SALTEA `init_libinput()`,
# que es justo el último paso del hilo de input antes de señalar `input_thread_initialized`
@@ -194,12 +233,6 @@ SHELL_PID=$!
# stream EN VIVO al serial (busybox sed no soporta -u ⇒ tail -f directo, sin pipes).
( echo "==== gnome-shell.log (stream vivo) ===="; tail -f /tmp/gnome-shell.log ) > /dev/ttyS0 2>/dev/null &
# OJO con el test de vivo: `kill -0` TIENE ÉXITO sobre un ZOMBI (el hijo existe como PID hasta que
# el padre lo cosecha, y acá el padre es este script, que sólo hace wait al final). La primera
# versión usaba kill -0 y por eso reportó "sin wayland-0 tras 45s" cuando en realidad gnome-shell ya
# había muerto en el primer segundo: el diagnóstico decía "0 hilos" porque no quedaba proceso, sólo
# 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 240 ]; do
[ -S "$XDG_RUNTIME_DIR/wayland-0" ] && break
@@ -222,15 +255,42 @@ else
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)"
# POR HILO, y ANTES del SIGABRT. La primera versión hacía este bucle DESPUÉS de abortar, o sea
# sobre un /proc/<pid>/task que ya no existía: salía vacío y no dijo nada. Es el dato más barato
# que hay para ubicar la «Mutter Input Thread», porque el core no sirve: gdb no puede desenrollar
# a través de musl (sin CFI) y devuelve `?? ()` para los 11 hilos que no son el principal.
say "--- por hilo (comm / wchan / syscall) ---"
for t in $(ls /proc/$SHELL_PID/task 2>/dev/null); do
say " tid $t [$(cat /proc/$SHELL_PID/task/$t/comm 2>/dev/null)] wchan=$(cat /proc/$SHELL_PID/task/$t/wchan 2>/dev/null) syscall=$(cut -d' ' -f1 /proc/$SHELL_PID/task/$t/syscall 2>/dev/null)"
done
# ¿Sigue vivo y respondiendo el login1? Si el shell está esperando una respuesta D-Bus que no
# llega, esto lo separa en dos: daemon muerto/colgado vs daemon vivo que ya contestó.
say "arje-logind-compat: pids=$(pgrep -f arje-logind-compat 2>/dev/null | tr '\n' ' ')"
for p in $(pgrep -f arje-logind-compat 2>/dev/null); do
say " logind pid $p state=$(sed -n 's/^State:[[:space:]]*//p' /proc/$p/status 2>/dev/null) wchan=$(cat /proc/$p/wchan 2>/dev/null)"
done
( dbus-send --system --print-reply --dest=org.freedesktop.login1 \
/org/freedesktop/login1 org.freedesktop.DBus.Properties.Get \
string:org.freedesktop.login1.Manager string:NAutoVTs >/tmp/login1-ping.log 2>&1
echo "rc=$?" >>/tmp/login1-ping.log ) &
PING_PID=$!
i=0; while [ $i -lt 16 ] && alive $PING_PID; do i=$((i+1)); sleep 0.5; done
if alive $PING_PID; then
say "!! login1 NO responde (ping D-Bus colgado 8s) ⇒ el daemon se quedó tildado"
kill -9 $PING_PID 2>/dev/null
else
say "login1 responde al ping D-Bus:"; dump /tmp/login1-ping.log
fi
# /tmp es tmpfs ⇒ el log del daemon se pierde con la VM. Copiarlo a la raíz ext4, que sí se
# sincroniza y se puede sacar con debugfs junto al core.
cp /tmp/logind-compat.log /logind-compat.log 2>/dev/null || true
cp /tmp/gnome-shell.log /gnome-shell.log 2>/dev/null || true
# 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
exec /bin/sh
fi
export WAYLAND_DISPLAY=wayland-0