docs: Qt6 Wayland null+8 static diagnosis — patch verdict + instrumented runbook
Phases A-C static diagnosis of the wl_proxy_add_listener null+8 crash. Evidence-backed root cause, candidate patch verdict, ruled-out hypothesis cross-check, and a copy-pasteable instrumented-rebuild runbook for the orchestrator. Verdict: candidate patch qtwaylandscanner-null-guard-listeners.patch guards the CORRECT site (every generated init_listener). Necessary but not sufficient alone — complementary to libwayland redox.patch hunk 1. v5.5 'verified FIXED' claim overstated: never tested in isolation, kded6 workaround masked it, stale-sysroot risk documented.
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
# Qt6 Wayland `wl_proxy_add_listener` null+8 Crash — Static Diagnosis (Phases A–C)
|
||||
|
||||
**Status:** STATIC DIAGNOSIS COMPLETE. Patch verdict issued. Instrumented-rebuild
|
||||
runbook below for the orchestrator to execute (Phase A instrumentation + forced
|
||||
rebuild chain + QEMU observation). No QEMU runs or multi-hour Qt rebuilds were
|
||||
performed in this diagnosis pass — host contention constraints honoured.
|
||||
|
||||
**Date:** 2026-07-20
|
||||
**Branch:** 0.3.1
|
||||
**Scope:** Phases A–C of `WAYLAND-IMPLEMENTATION-PLAN.md` § "Current Blocker"
|
||||
|
||||
---
|
||||
|
||||
## 1. VERDICT (root cause statement)
|
||||
|
||||
**The NULL proxy at the crash is the `wl_registry*` returned by
|
||||
`wl_display_get_registry(mDisplay)` in
|
||||
`QWaylandDisplay::setupConnection()` (`qwaylanddisplay.cpp:340`), and the crash
|
||||
site is the qtwaylandscanner-generated `QtWayland::wl_registry::init_listener()`
|
||||
calling `wl_registry_add_listener(NULL, ...)` → `wl_proxy_add_listener(NULL, ...)`
|
||||
→ `proxy->object.implementation = listener` faulting at address `0x8`.**
|
||||
|
||||
A secondary, alternative crash site (consistent with the commit-message root
|
||||
cause but NOT with the "before any compositor events" signature) is any
|
||||
generated `init_listener()` for a global bound via `wlRegistryBind()` during
|
||||
`QWaylandDisplay::registry_global()` (`qwaylanddisplay.cpp:634`).
|
||||
|
||||
The candidate patch
|
||||
(`local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch`) guards
|
||||
**both** sites because it modifies the code generator to emit
|
||||
`if (m_<iface>) <iface>_add_listener(...)` for **every** generated
|
||||
`init_listener()`.
|
||||
|
||||
**Patch verdict: the candidate patch guards the CORRECT site.** It is necessary
|
||||
but not sufficient alone — see §4.
|
||||
|
||||
---
|
||||
|
||||
## 2. EVIDENCE CHAIN (every claim cites file:line)
|
||||
|
||||
### 2.1 Crash signature confirmation
|
||||
|
||||
| Claim | Evidence |
|
||||
|-------|----------|
|
||||
| Fault address `0x8` = write to `proxy->object.implementation` | `local/recipes/wayland/libwayland/source/src/wayland-client.c:665` — `proxy->object.implementation = implementation;` |
|
||||
| `wl_object.implementation` is at offset 8 | `local/recipes/wayland/libwayland/source/src/wayland-private.h:70-74` — `struct wl_object { const struct wl_interface *interface; /* off 0 */ const void *implementation; /* off 8 */ uint32_t id; /* off 16 */ };` |
|
||||
| `wl_proxy.object` is at offset 0 | `local/recipes/wayland/libwayland/source/src/wayland-client.c:63-74` — `struct wl_proxy { struct wl_object object; ... };` (first member) |
|
||||
| Therefore `proxy==NULL` faults at `NULL+8` on the implementation write | Confirmed: `*(uintptr_t*)((char*)NULL + 8) = listener` = SIGSEGV at `0x8` |
|
||||
|
||||
### 2.2 Qt6 client init sequence (where the first add_listener fires)
|
||||
|
||||
Qt 6.11.1 ships the **entire Wayland QPA client plugin inside qtbase** (not
|
||||
qtwayland). Confirmed locations:
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp` | QWaylandDisplay (the main client-side display object) |
|
||||
| `local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay_p.h:105` | `class QWaylandDisplay : public QObject, public QtWayland::wl_registry` — **QWaylandDisplay IS-A wl_registry wrapper** |
|
||||
| `local/recipes/qt/qtbase/source/src/tools/qtwaylandscanner/qtwaylandscanner.cpp` | The code generator (vendored into qtbase, not qtwayland) |
|
||||
|
||||
Init sequence (`qwaylanddisplay.cpp`):
|
||||
|
||||
```
|
||||
Line 322: QWaylandDisplay::QWaylandDisplay(...)
|
||||
Line 327: mDisplay = wl_display_connect(nullptr);
|
||||
Line 328: if (mDisplay) { ← rules out NULL display
|
||||
Line 329: setupConnection();
|
||||
Line 330: }
|
||||
|
||||
Line 338: void QWaylandDisplay::setupConnection()
|
||||
Line 340: struct ::wl_registry *registry = wl_display_get_registry(mDisplay); ← CAN return NULL
|
||||
Line 341: init(registry); ← calls generated QtWayland::wl_registry::init(wl_registry*)
|
||||
```
|
||||
|
||||
The generated `init(obj)` (`qtwaylandscanner.cpp:1155-1160`):
|
||||
```cpp
|
||||
void wl_registry::init(struct ::wl_registry *obj)
|
||||
{
|
||||
m_wl_registry = obj; // line 1157 — assigned BEFORE init_listener
|
||||
if (hasEvents)
|
||||
init_listener(); // line 1159 — called AFTER assignment
|
||||
}
|
||||
```
|
||||
|
||||
The generated `init_listener()` (`qtwaylandscanner.cpp:1295-1298`, **PATCHED**):
|
||||
```cpp
|
||||
void wl_registry::init_listener()
|
||||
{
|
||||
if (m_wl_registry) wl_registry_add_listener(m_wl_registry, &m_wl_registry_listener, this);
|
||||
}
|
||||
```
|
||||
|
||||
**Therefore:** if `wl_display_get_registry()` returns NULL → `m_wl_registry = NULL`
|
||||
→ patched `init_listener()` skips the add_listener call → **no crash**.
|
||||
Unpatched (upstream Qt 6.11.1) → `wl_registry_add_listener(NULL, ...)` →
|
||||
`wl_proxy_add_listener(NULL, ...)` → **page fault at 0x8**.
|
||||
|
||||
### 2.3 Why `wl_display_get_registry()` can return NULL
|
||||
|
||||
`wl_display_get_registry()` is generated code (see
|
||||
`local/recipes/wayland/libwayland/source/tests/data/example-client.h:1027-1036`):
|
||||
|
||||
```c
|
||||
static inline struct wl_registry *
|
||||
wl_display_get_registry(struct wl_display *wl_display)
|
||||
{
|
||||
struct wl_proxy *registry;
|
||||
registry = wl_proxy_marshal_flags((struct wl_proxy *) wl_display,
|
||||
WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, ...);
|
||||
return (struct wl_registry *) registry; // NULL if marshal_flags returns NULL
|
||||
}
|
||||
```
|
||||
|
||||
`wl_proxy_marshal_flags` → `wl_proxy_marshal_array_flags`
|
||||
(`wayland-client.c:901-963`) returns NULL when:
|
||||
|
||||
| Condition | Source |
|
||||
|-----------|--------|
|
||||
| `create_outgoing_proxy()` → `proxy_create()` OOM (`zalloc` fails) | `wayland-client.c:487` |
|
||||
| `wl_map_insert_new` returns 0 (client ID space exhausted) | `wayland-client.c:496-499` |
|
||||
| `display->last_error` already set (fatal display error) | `wayland-client.c:922-924` |
|
||||
|
||||
The third condition is the most plausible: if the implicit display handshake
|
||||
during `wl_display_connect()` encounters a protocol or socket error,
|
||||
`display->last_error` is set, and the subsequent `wl_display_get_registry()`
|
||||
silently returns NULL without even attempting proxy creation.
|
||||
|
||||
### 2.4 Candidate patch guards the correct site
|
||||
|
||||
The candidate patch (`local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch`)
|
||||
modifies the generator at `qtwaylandscanner.cpp:1297` to emit
|
||||
`if (m_%s) %s_add_listener(...)` instead of `%s_add_listener(...)`.
|
||||
|
||||
This single generator change guards the `init_listener()` of **every** generated
|
||||
Wayland wrapper class — `wl_registry`, `wl_compositor`, `wl_shm`, `wl_seat`,
|
||||
`wl_output`, `xdg_wm_base`, and all `zwp_*`/`wp_*` extensions.
|
||||
|
||||
The patch is:
|
||||
- **Confirmed applied** to the live source tree (`qtwaylandscanner.cpp:1297`
|
||||
shows the patched 4-argument printf).
|
||||
- **Confirmed wired** in `local/recipes/qt/qtbase/recipe.toml` patches list
|
||||
(line 14: `"qtwaylandscanner-null-guard-listeners.patch"`).
|
||||
- **Confirmed applicable** to Qt 6.11.1 (context lines match; 3-arg → 4-arg
|
||||
printf transformation is unique).
|
||||
|
||||
### 2.5 Complementary guard already in libwayland
|
||||
|
||||
A **second, independent NULL guard** exists in
|
||||
`local/patches/libwayland/redox.patch` (added 2026-05-06 in commit
|
||||
`ea125b0b45`), which modifies `wl_proxy_add_listener` itself
|
||||
(`wayland-client.c:652-656`):
|
||||
|
||||
```c
|
||||
if (!proxy) {
|
||||
fprintf(stderr, "FATAL: wl_proxy_add_listener(NULL) caller=%p -- returning error\n",
|
||||
__builtin_return_address(0));
|
||||
return -1;
|
||||
}
|
||||
```
|
||||
|
||||
This converts the page fault into a logged error + `return -1`. **If this guard
|
||||
is active, the null+8 page fault CANNOT occur** — instead you see the FATAL log
|
||||
line. The presence of the page-fault crash signature therefore implies the
|
||||
libwayland guard was not yet compiled into the running binary at the time of
|
||||
observation (stale libwayland in qtbase's sysroot — see §6).
|
||||
|
||||
The two guards are **complementary, not redundant in purpose**:
|
||||
- qtwaylandscanner patch: prevents Qt from *calling* `wl_*_add_listener(NULL)`
|
||||
(clean — Qt proceeds with a no-op listener)
|
||||
- libwayland guard: prevents the *crash* if any caller (Qt or otherwise) passes
|
||||
NULL (defensive — logs + returns error, caller may misbehave)
|
||||
|
||||
Neither guard addresses the **root cause**: why proxy creation returns NULL.
|
||||
|
||||
---
|
||||
|
||||
## 3. CROSS-CHECK OF THE FOUR RULED-OUT HYPOTHESES
|
||||
|
||||
| # | Hypothesis | Plan's Verdict | Re-evaluation | Evidence |
|
||||
|---|-----------|----------------|---------------|----------|
|
||||
| 1 | relibc `calloc` doesn't zero | FALSE | **SOUND** | `local/sources/relibc/src/header/stdlib/mod.rs:281` — `ptr.write_bytes(0, size)` runs unconditionally on every successful `malloc`. Overflow path returns NULL + ENOMEM (line 287-288). Verified. |
|
||||
| 2 | Compositor protocol bug | FALSE | **SOUND** | Compositor is pure-Rust (`redbear-compositor`), protocol-compliant, doesn't crash. Client crashes during its own init. |
|
||||
| 3 | `wl_display_connect()` returns NULL | FALSE | **SOUND** | `qwaylanddisplay.cpp:327-329` — Qt checks `if (mDisplay)` before calling `setupConnection()`. A NULL display never reaches `get_registry`. |
|
||||
| 4 | `wl_display_get_registry()` returns NULL | FALSE | **PARTIALLY UNSOUND** — the plan claims "My null guard `if (!registry) _exit(1)` catches this", but **NO such guard exists** at `qwaylanddisplay.cpp:340-341`. The code is `struct ::wl_registry *registry = wl_display_get_registry(mDisplay); init(registry);` — unconditional. The hypothesis is only effectively ruled out because the *candidate patch*'s `init_listener()` guard implicitly handles NULL. **Without the patch, this is the leading crash cause.** |
|
||||
| 5 | libwayland `proxy_create()` returns NULL | UNLIKELY | **WEAKENED** — the plan says "Qt would catch NULL", but Qt does NOT check `wlRegistryBind()` / `wl_display_get_registry()` return values. That's exactly the gap the candidate patch closes. The hypothesis is plausible, not unlikely. |
|
||||
|
||||
**Net effect:** hypotheses 1–3 are soundly ruled out. Hypothesis 4's cited
|
||||
evidence (explicit `_exit` guard) does not exist in the source — the ruling
|
||||
holds only because of the candidate patch. Hypothesis 5 is under-weighted:
|
||||
NULL returns from proxy creation are not caught by Qt without the patch.
|
||||
|
||||
---
|
||||
|
||||
## 4. PATCH VERDICT
|
||||
|
||||
### Candidate: `local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch`
|
||||
|
||||
**Verdict: CORRECT SITE, NECESSARY, BUT NOT SUFFICIENT ALONE.**
|
||||
|
||||
| Question | Answer |
|
||||
|----------|--------|
|
||||
| Does the crash happen inside scanner-generated `init_listener()`? | **YES** — `wl_proxy_add_listener` is only reachable from `wl_*_add_listener` wrappers, which in Qt's client init path are only emitted by qtwaylandscanner inside `init_listener()`. The hand-written `wl_callback_add_listener` / `wl_buffer_add_listener` sites (see §5) all fire LATER, after the first roundtrip. |
|
||||
| Does the patch guard the actual crash site? | **YES** — the generator change emits `if (m_<iface>)` before every `wl_*_add_listener(m_<iface>, ...)` in every generated `init_listener()`. |
|
||||
| Is the patch the same as upstream commits `de2d74c37e`/`882c2974ec`? | **`de2d74c37e`/`882c2974ec` ARE the Red Bear commits that introduced this patch.** They are not upstream Qt commits. `de2d74c37e` (2026-05-06 17:18) made the direct source edit; `882c2974ec` (2026-05-07) extracted it into the patch file + wired recipe.toml. |
|
||||
| Was the patch runtime-validated? | **NO — not in isolation.** Commit `de2d74c37e` claims "VERIFIED: greeter UI boots", but this was done with THREE fixes simultaneously applied (qtwaylandscanner patch + libwayland NULL guard from `ea125b0b45` 13:44 same day + kded6 workaround renaming `libqwayland.so` to `.disabled`). The qtwaylandscanner patch alone was never proven. The "7 globals" claim in the commit message also disagrees with the compositor's actual 8 globals, suggesting incomplete observation. |
|
||||
| Is a corrected patch needed? | **NO** for the reported crash. The candidate guards the correct site. Additional hardening for non-scanner sites is recommended (§5) but is a separate concern. |
|
||||
|
||||
### Why "not sufficient alone"
|
||||
|
||||
The patch prevents Qt from calling `wl_*_add_listener(NULL)`, but it does NOT
|
||||
fix the root cause: **why `wl_display_get_registry()` or `wlRegistryBind()`
|
||||
returns NULL in the first place.** With the patch applied, Qt silently proceeds
|
||||
with `m_wl_registry == nullptr`, the registry listener is never attached, Qt
|
||||
receives no global announcements, and the desktop session fails to initialise
|
||||
(no screens, no compositor, no input devices) — a different failure mode than a
|
||||
page fault, but still a non-functional desktop.
|
||||
|
||||
The root cause requires Phase A instrumentation + Phase C libwayland audit to
|
||||
determine which of the three NULL-return paths (OOM, map exhaustion, or
|
||||
`display->last_error`) is firing.
|
||||
|
||||
### Relationship to existing patches
|
||||
|
||||
| Patch | Location | Status | Relationship |
|
||||
|-------|----------|--------|--------------|
|
||||
| `qtwaylandscanner-null-guard-listeners.patch` | `local/patches/qtbase/` | **Candidate (this diagnosis)** | Generator-level guard — prevents the call |
|
||||
| libwayland `redox.patch` hunk 1 | `local/patches/libwayland/redox.patch` | Applied (commit `ea125b0b45`) | Library-level guard — prevents the crash if call happens |
|
||||
| `patch-wayland-gen.sh` | `local/recipes/qt/qtbase/` | **ORPHANED** — never invoked from recipe.toml | Legacy sed-based post-build patcher (replaced by the generator patch) |
|
||||
| `qtwayland-empty-cursor-guards.patch` | `local/recipes/qt/qtbase/` | **ORPHANED** — not in recipe.toml patches list | Unrelated (cursor theme empty-image guards) |
|
||||
| `P0-fix-broken-include.patch`, `P1-qplatformopengl-guard.patch` | `local/patches/qtbase/` | **ORPHANED** — symlinks exist in recipe dir but not in patches list | Not applied to current builds |
|
||||
|
||||
**No file changes are needed to the candidate patch.** It is correct. The
|
||||
runbook below (§7) validates it end-to-end.
|
||||
|
||||
---
|
||||
|
||||
## 5. UNGUARDED `add_listener` SITES (hardening recommendations — NOT the crash site)
|
||||
|
||||
These hand-written `wl_*_add_listener` calls are NOT guarded by the candidate
|
||||
patch (which only covers scanner-generated code). All fire AFTER the first
|
||||
roundtrip, so they are NOT the "before any compositor events" crash site. Listed
|
||||
for completeness; a separate hardening patch could address them:
|
||||
|
||||
| File:Line | Call | When it fires |
|
||||
|-----------|------|---------------|
|
||||
| `qwaylanddisplay.cpp:1049` | `wl_callback_add_listener(mSyncCallback, &syncCallbackListener, this)` | `requestWaylandSync()` — during sync/roundtrip |
|
||||
| `qwaylandbuffer.cpp:27` | `wl_buffer_add_listener(buf, &listener, this)` | Buffer creation — during surface commit |
|
||||
| `qwaylandwindow.cpp:1807` | `wl_callback_add_listener(mFrameCallback, &callbackListener, this)` | Frame callback — during surface render |
|
||||
| `qwaylandtextinputv2.cpp:178` | `wl_callback_add_listener(m_resetCallback, &callbackListener, this)` | Text input reset — during IME interaction |
|
||||
| `qwaylandbrcmeglwindow.cpp:50` | `wl_buffer_add_listener(mBuffer, &buffer_listener, this)` | Broadcom EGL — hardware integration (not loaded on Redox) |
|
||||
|
||||
These are all covered by the libwayland-level NULL guard (`redox.patch` hunk 1),
|
||||
so they will log FATAL + return -1 rather than crash. A future hardening patch
|
||||
to Qt would add explicit `if (ptr)` checks at each site for cleaner diagnostics.
|
||||
|
||||
---
|
||||
|
||||
## 6. WHY THE CRASH PERSISTED DESPITE "VERIFIED FIXED" (May 2026)
|
||||
|
||||
The v5.5 changelog (`CONSOLE-TO-KDE-DESKTOP-PLAN.md:38-46`) and four other
|
||||
locations claim the crash is "verified FIXED" via commits `de2d74c37e` +
|
||||
`882c2974ec`. This claim is **overstated** for three reasons:
|
||||
|
||||
1. **Never tested in isolation.** The May 6 verification had three fixes
|
||||
applied simultaneously. The qtwaylandscanner patch was never proven to fix
|
||||
the crash on its own.
|
||||
|
||||
2. **The kded6 workaround masked it.** Commit `176624a008` (2026-05-06 16:34,
|
||||
one hour before `de2d74c37e`) deployed a wrapper script that renames
|
||||
`libqwayland.so` to `.disabled` before launching kded6, preventing Qt from
|
||||
loading the Wayland plugin at all. The "greeter UI boots" verification may
|
||||
have been of a greeter running WITHOUT the Wayland plugin (offscreen QPA),
|
||||
not WITH the patched Wayland plugin.
|
||||
|
||||
3. **Stale-sysroot rebuild chain.** The `WAYLAND-IMPLEMENTATION-PLAN.md:157-171`
|
||||
documents that "Modifying only libwayland does NOT update qtbase's static
|
||||
copy of `libwayland-client.a`. The old binary persists in qtbase's sysroot."
|
||||
If the full chain (libwayland → qtbase → qtdeclarative → qtwayland) was not
|
||||
force-rebuilt, the running binary may have linked against an unpatched
|
||||
libwayland, making the page fault reappear.
|
||||
|
||||
The crash re-appears as the active blocker in `WAYLAND-IMPLEMENTATION-PLAN.md`
|
||||
(dated 2026-07-08, two months after the "fix") and
|
||||
`CONSOLE-TO-KDE-DESKTOP-PLAN.md:234` (2026-06-20, "WIRED, UNVALIDATED"). The
|
||||
"verified FIXED" line at `CONSOLE-TO-KDE-DESKTOP-PLAN.md:43` directly
|
||||
contradicts line 240-243 of the same document.
|
||||
|
||||
**The v5.5 claim should be corrected to "WIRED, NEVER RUNTIME-VALIDATED IN
|
||||
ISOLATION" until the Phase A runbook below produces positive QEMU evidence.**
|
||||
|
||||
---
|
||||
|
||||
## 7. INSTRUMENTED-REBUILD RUNBOOK (Phase A diagnostics + forced chain)
|
||||
|
||||
This runbook is for the orchestrator to execute when host contention allows.
|
||||
It produces definitive evidence of WHICH proxy is NULL and WHY.
|
||||
|
||||
### 7.1 Phase A: Add fprintf diagnostics (3 files)
|
||||
|
||||
All edits are to the live source trees. They will be discarded on next
|
||||
`repo fetch`, so mirror them into patches if durability is needed.
|
||||
|
||||
**Edit 1: `local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp`**
|
||||
|
||||
In `setupConnection()` (around line 340), add:
|
||||
```cpp
|
||||
void QWaylandDisplay::setupConnection()
|
||||
{
|
||||
struct ::wl_registry *registry = wl_display_get_registry(mDisplay);
|
||||
fprintf(stderr, "[RB-DIAG] setupConnection: mDisplay=%p registry=%p\n", (void*)mDisplay, (void*)registry);
|
||||
init(registry);
|
||||
fprintf(stderr, "[RB-DIAG] setupConnection: init() returned, m_wl_registry=%p\n", (void*)object());
|
||||
```
|
||||
|
||||
**Edit 2: `local/recipes/qt/qtbase/source/src/tools/qtwaylandscanner/qtwaylandscanner.cpp`**
|
||||
|
||||
In the `init_listener()` generator (around line 1295), add a diagnostic printf
|
||||
INSIDE the generated function. Change the patched line to:
|
||||
```cpp
|
||||
printf(" fprintf(stderr, \"[RB-DIAG] init_listener m_%s=%p\\n\", (void*)m_%s);\n", interfaceName, interfaceName);
|
||||
printf(" if (m_%s) %s_add_listener(m_%s, &m_%s_listener, this);\n", interfaceName, interfaceName, interfaceName, interfaceName);
|
||||
printf(" else fprintf(stderr, \"[RB-DIAG] SKIPPED add_listener for %s (NULL proxy)\\n\");\n", interfaceName);
|
||||
```
|
||||
|
||||
**Edit 3: `local/recipes/wayland/libwayland/source/src/wayland-client.c`**
|
||||
|
||||
In `wl_proxy_marshal_array_flags` (around line 920), add before the
|
||||
`display->last_error` check:
|
||||
```c
|
||||
if (display->last_error)
|
||||
fprintf(stderr, "[RB-DIAG] marshal_flags: display->last_error=%d, opcode=%u\n",
|
||||
display->last_error, opcode);
|
||||
```
|
||||
|
||||
And in `proxy_create` (around line 487), add:
|
||||
```c
|
||||
if (proxy == NULL) {
|
||||
fprintf(stderr, "[RB-DIAG] proxy_create: zalloc FAILED (OOM)\n");
|
||||
return NULL;
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Phase B: Force-rebuild chain (in strict order)
|
||||
|
||||
The chain MUST be rebuilt in this order. Skipping any step leaves a stale
|
||||
binary in the next recipe's sysroot.
|
||||
|
||||
```bash
|
||||
# Step 0: Rebuild prefix (relibc headers if Phase A edit 3 touched wayland-client.c)
|
||||
touch relibc && make prefix
|
||||
|
||||
# Step 1: Force-rebuild libwayland (Phase A edit 3)
|
||||
rm -rf local/recipes/wayland/libwayland/target
|
||||
rm -f repo/x86_64-unknown-redox/libwayland.pkgar
|
||||
./target/release/repo cook libwayland --force-rebuild
|
||||
|
||||
# Step 2: Force-rebuild qtbase (Phase A edits 1 + 2; picks up new libwayland)
|
||||
rm -rf local/recipes/qt/qtbase/target
|
||||
rm -f repo/x86_64-unknown-redox/qtbase.pkgar
|
||||
./target/release/repo cook qtbase --force-rebuild
|
||||
|
||||
# Step 3: Force-rebuild qtdeclarative (links against new qtbase)
|
||||
rm -rf local/recipes/qt/qtdeclarative/target
|
||||
rm -f repo/x86_64-unknown-redox/qtdeclarative.pkgar
|
||||
./target/release/repo cook qtdeclarative --force-rebuild
|
||||
|
||||
# Step 4: Force-rebuild qtwayland (uses qtwaylandscanner from new qtbase)
|
||||
rm -rf local/recipes/qt/qtwayland/target
|
||||
rm -f repo/x86_64-unknown-redox/qtwayland.pkgar
|
||||
./target/release/repo cook qtwayland --force-rebuild
|
||||
|
||||
# Step 5: Rebuild the full image
|
||||
./local/scripts/build-redbear.sh redbear-full
|
||||
```
|
||||
|
||||
If `repo cook` is unavailable, use `make r.<recipe>` + `make cr.<recipe>`
|
||||
(clean + rebuild) equivalents.
|
||||
|
||||
### 7.3 Phase C: QEMU invocation + serial-log markers
|
||||
|
||||
```bash
|
||||
# Boot the instrumented image with serial console captured
|
||||
qemu-system-x86_64 \
|
||||
-cdrom build/x86_64/redbear-full.iso \
|
||||
-m 4096 \
|
||||
-serial file:/tmp/rb-wayland-diag.log \
|
||||
-netdev user,id=n0 -device e1000,netdev=n0 \
|
||||
-display none \
|
||||
2>&1 | tee /tmp/rb-qemu-stdout.log
|
||||
|
||||
# After boot, trigger a Wayland client (greeter or smoke test)
|
||||
# Inside the guest (via serial console):
|
||||
# export QT_QPA_PLATFORM=wayland
|
||||
# export WAYLAND_DISPLAY=wayland-0
|
||||
# /usr/lib/qt6/examples/wayland/minimal-qml # or any Qt6 Wayland client
|
||||
```
|
||||
|
||||
### 7.4 Markers to observe in `/tmp/rb-wayland-diag.log`
|
||||
|
||||
| Marker | Meaning | Action |
|
||||
|--------|---------|--------|
|
||||
| `[RB-DIAG] setupConnection: mDisplay=0x... registry=0x...` | Connection established; check if registry is NULL | If `registry=(nil)`, root cause is in `wl_display_get_registry` |
|
||||
| `[RB-DIAG] setupConnection: init() returned, m_wl_registry=0x...` | Registry wrapper initialised | If `(nil)`, the patched init_listener skipped |
|
||||
| `[RB-DIAG] init_listener m_wl_registry=0x...` | Generated init_listener executed for wl_registry | Confirms the scanner-generated code runs |
|
||||
| `[RB-DIAG] SKIPPED add_listener for wl_registry (NULL proxy)` | The candidate patch fired — NULL was caught | **Patch works. Root cause is upstream (proxy creation).** |
|
||||
| `[RB-DIAG] marshal_flags: display->last_error=N` | Display had a prior fatal error | **Root cause: display handshake failure.** Investigate socket/IPC. |
|
||||
| `[RB-DIAG] proxy_create: zalloc FAILED (OOM)` | Out of memory during proxy allocation | **Root cause: OOM.** Increase guest RAM or audit relibc allocator. |
|
||||
| `FATAL: wl_proxy_add_listener(NULL) caller=0x...` | libwayland guard fired (some caller passed NULL) | **The candidate patch did NOT catch this site** — the NULL came from a non-scanner call site (§5). Use `addr2line` on the caller address. |
|
||||
| `Page fault: 0000000000000008 US` | **The crash persisted** — BOTH guards failed to apply | Stale binary in sysroot. Re-run the forced rebuild chain. |
|
||||
|
||||
### 7.5 Decision tree after observation
|
||||
|
||||
```
|
||||
Observe the FIRST [RB-DIAG] or crash marker:
|
||||
|
||||
├── registry=(nil) in setupConnection
|
||||
│ ├── display->last_error set → ROOT CAUSE: display handshake failure (IPC/socket)
|
||||
│ ├── zalloc FAILED → ROOT CAUSE: OOM in proxy_create
|
||||
│ └── neither → ROOT CAUSE: wl_map_insert_new returned 0 (investigate)
|
||||
│
|
||||
├── registry=0x... (non-NULL) but SKIPPED add_listener
|
||||
│ └── Patch works; root cause is elsewhere in global binding (registry_global path)
|
||||
│
|
||||
├── FATAL: wl_proxy_add_listener(NULL) caller=0x...
|
||||
│ └── addr2line -e <binary> <caller_addr> → identifies the exact unguarded call site (§5)
|
||||
│
|
||||
└── Page fault at 0x8 (no markers)
|
||||
└── Stale binary — repeat forced rebuild chain with --no-cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. SUMMARY TABLE
|
||||
|
||||
| Question | Answer |
|
||||
|----------|--------|
|
||||
| Which proxy is NULL? | `wl_registry*` from `wl_display_get_registry()` (primary) or any `wl_<iface>*` from `wlRegistryBind()` (secondary) |
|
||||
| Why is it NULL? | `wl_proxy_marshal_array_flags` returns NULL: OOM, map exhaustion, or `display->last_error` set |
|
||||
| Where is the crash? | Generated `init_listener()` → `wl_*_add_listener(NULL)` → `wl_proxy_add_listener(NULL)` → `proxy->object.implementation = listener` at `wayland-client.c:665` |
|
||||
| Does the patch guard the crash site? | **YES** — `qtwaylandscanner.cpp:1297` emits `if (m_<iface>)` before every generated `add_listener` |
|
||||
| Is the patch sufficient alone? | **NO** — prevents crash but not the root cause (NULL proxy creation); also redundant with libwayland `redox.patch` hunk 1 |
|
||||
| Is a corrected patch needed? | **NO** — the candidate is correct for the reported crash |
|
||||
| Was the v5.5 "verified FIXED" sound? | **NO** — never tested in isolation; kded6 workaround masked it; stale-sysroot risk |
|
||||
| Are the ruled-out hypotheses sound? | Hypotheses 1–3 sound; 4 cites non-existent evidence; 5 is under-weighted |
|
||||
|
||||
---
|
||||
|
||||
## 9. FILES CHANGED IN THIS DIAGNOSIS
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `local/docs/QT6-WAYLAND-NULL8-DIAGNOSIS.md` | **NEW** — this report |
|
||||
| `local/patches/qtbase/` | **No changes** — candidate patch is correct |
|
||||
| `local/recipes/qt/qtbase/recipe.toml` | **No changes** — patch is already wired (line 14) |
|
||||
|
||||
---
|
||||
|
||||
## 10. NEXT STEPS (for the orchestrator)
|
||||
|
||||
1. **Execute the Phase A runbook (§7)** when host contention allows. The
|
||||
fprintf diagnostics will definitively identify which NULL-return path fires.
|
||||
2. **Do NOT remove the candidate patch** — it is correct and necessary.
|
||||
3. **Do NOT trust the "verified FIXED" claims** in
|
||||
`CONSOLE-TO-KDE-DESKTOP-PLAN.md:43`, `config/redbear-full.toml:183`,
|
||||
`local/recipes/kde/kwin/recipe.toml:2`, or
|
||||
`local/docs/archived/IMPLEMENTATION-MASTER-PLAN.md:61` until positive QEMU
|
||||
evidence from §7.4 confirms the crash is gone.
|
||||
4. **If the runbook reveals `display->last_error` as the root cause**, the fix
|
||||
is in the IPC/socket layer (kernel or relibc), not in Qt or libwayland.
|
||||
5. **If the runbook reveals OOM**, increase guest RAM or audit the relibc
|
||||
allocator (`dlmalloc`) for fragmentation under Qt6's allocation pattern.
|
||||
Reference in New Issue
Block a user