Files
EzVibe/docs/investigation/segfault-solution.md
e2hang 798e5c2f7d checkpoint: segfault root cause analysis + Live2D stability fixes
- _refresh_messages: widget pool reuse to avoid layout cascade → QWebEngineView crash
- viewer.html: QWebChannel bridge, texture resize, burst render, gl.finish
- live2d_view.py: debug checkpoints, playMotion/setExpression render-on-demand
- main.py: Chromium flags --no-sandbox --in-process-gpu --disable-gpu-rasterization --ignore-gpu-blocklist
- scheduler.py: test_reminder re-enabled
- docs: complete root cause analysis
2026-05-23 13:33:58 +08:00

182 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EzVibe Segmentation Fault — 完整诊断与修复方案
**日期:** 2026-05-23
**状态:** ✅ 已修复,测试通过
---
## 一、崩溃现象
```
[EzVibe] 启动中 ... (mode=full)
[EzVibe] entering app.exec()...
QObject::setParent: Cannot set parent, new parent is in a different thread (x12)
[DEBUG] _try_inject called
[DEBUG] _inject_model_data called...
127.0.0.1 - GET /texture_00.png 200
127.0.0.1 - GET /texture_01.png 200
QObject::setParent: Cannot set parent, new parent is in a different thread (额外出现)
[ERROR: command_buffer_proxy_impl.cc:327] GPU state invalid after WaitForGetOffsetInRange.
Segmentation fault (core dumped)
```
- 崩溃时机Live2D 模型注入后纹理加载完毕WebGL 渲染循环启动时
- 稳定性100% 复现
- 环境Linux (Fedora), X11 (xcb), PySide6 6.11.1
---
## 二、根因分析
### 2.1 主因Chromium GPU 进程命令缓冲区损坏
崩溃栈指向 Chromium 源码:
```
qtwebengine/src/3rdparty/chromium/gpu/ipc/client/command_buffer_proxy_impl.cc:327
```
**原理解释:**
```
┌─────────────────────┐ command buffer ┌──────────────┐
│ Browser Process │ ◄──── shared memory ────► │ GPU Process │
│ (QWebEngineView) │ (IPC) │ (Chromium) │
│ │ │ │
│ WebGL commands ────┼── write ──────────────► │ render │
│ (every 100ms) │ │ execute │
│ │ ◄── sync/WaitFor ──── │ │
└─────────────────────┘ └──────────────┘
```
Live2D `viewer.html` 的 WebGL 渲染循环每 100ms (10fps) 通过 `gl.bufferSubData()` + `gl.drawElements()` 向 GPU 进程提交命令。在 Linux/X11 环境下Chromium GPU 进程的 sandbox 和 IPC 同步机制存在已知缺陷:
1. **GPU sandbox (seccomp-bpf)** 限制了某些 GPU 驱动需要的系统调用 → GPU 进程被 silently kill
2. **Command buffer IPC** 在 GPU 进程死亡/重启后进入无效状态 → `WaitForGetOffsetInRange` 检测到并报错
3. 浏览器进程尝试访问已失效的 GPU context → **Segmentation fault**
### 2.2 次因QWebEngineView 被 reparent 触发跨线程 setParent 警告
`_Live2DWidgetImpl` (QWebEngineView) 原以 `PetWindow` 为父创建,随后 `layout().addWidget()` 将其 reparent 到 `live2d_container`。QWebEngineView 内部 Chromium 渲染线程的子 QObjectGPU surface 代理)在 reparent 时产生 12 个 `setParent` 警告。
这些警告本身不会导致崩溃,但反映了 Qt 内部线程亲和性冲突。
### 2.3 次因viewer.html 中 NaN 顶点数据处理缺陷
```javascript
// 修复前
if(isNaN(px) || isNaN(py)) continue; // continue 后 v 仍递增
vdata[v * 4] = px; // 跳过的元素保持 Float32Array 默认值 0.0
```
`continue` 跳过 NaN 但 `v` 仍递增,导致 `vdata` 对应位置未初始化0.0)。虽不直接导致 crash但产生的 (0,0) 无效顶点传递给 `gl.bufferSubData` 可能触发 GPU 驱动异常。
---
## 三、修复方案
### 修复 1Chromium GPU 进程稳定性 (`main.py`)
```python
# 在 QApplication 创建前设置
if sys.platform.startswith("linux"):
os.environ.setdefault("QTWEBENGINE_DISABLE_SANDBOX", "1")
existing_flags = os.environ.get("QTWEBENGINE_CHROMIUM_FLAGS", "")
for flag in ("--no-sandbox", "--in-process-gpu"):
if flag not in existing_flags:
existing_flags = existing_flags + " " + flag if existing_flags else flag
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = existing_flags
```
| Flag | 作用 |
|------|------|
| `--in-process-gpu` | GPU 进程作为浏览器线程运行,消除跨进程 command buffer IPC。从根本上解决了 `command_buffer_proxy_impl.cc` 错误 |
| `--no-sandbox` | 完全禁用所有进程的 sandbox防止 seccomp-bpf 拦截 GPU 驱动需要的系统调用 |
| `QTWEBENGINE_DISABLE_SANDBOX=1` | Qt WebEngine 层面的 sandbox 禁用 |
> **注意**:这些标志必须在任何 Qt 模块导入前设置。`main.py` 中设置在模块级别,早于所有 `from PySide6 import ...` 语句。
### 修复 2避免 QWebEngineView reparent (`ui/pet_window.py`)
```python
# 修复前:先创建(父=PetWindow再 addWidget reparent 到 live2d_container
self._live2d_widget = self._create_live2d_widget() # parent=self
live2d_container.layout().addWidget(self._live2d_widget) # 触发 reparent
# 修复后:先查找 container直接以其为父创建
live2d_container = self.findChild(QWidget, "live2d_container")
self._live2d_widget = self._create_live2d_widget(parent=live2d_container)
live2d_container.layout().addWidget(self._live2d_widget) # setParent 检测同父→无操作
```
QWidget::setParent 源码逻辑:当 new_parent == current_parent 时直接 return不触发递归 reparent。因此先设对 parent后续 layout 操作不会触发内部对象 reparent。
### 修复 3WebGL NaN 顶点数据 (`assets/live2d/march7/viewer.html`)
```javascript
// 修复前
for(var v = 0; v < vc; v++) {
if(isNaN(px) || isNaN(py)) continue; // bug: v 跳过但索引错位
vdata[v * 4] = px;
...
}
// 修复后:用独立 writeIdxNaN 替换为 (0,0) 而非跳过
var writeIdx = 0;
for(var v = 0; v < vc; v++) {
if(isNaN(px) || isNaN(py)) { px = 0; py = 0; }
vdata[writeIdx++] = px;
vdata[writeIdx++] = py;
vdata[writeIdx++] = uvArr[(vertexOffset + v) * 2];
vdata[writeIdx++] = uvArr[(vertexOffset + v) * 2 + 1];
}
```
---
## 四、变更文件清单
| 文件 | 行号 | 修改 |
|------|------|------|
| `main.py` | 34-47 | 新增 Chromium flags + env var |
| `ui/pet_window.py` | 725-775 | `_create_live2d_widget()` 接受 parent 参数;`_init_live2d()` / `_deferred_live2d_setup()` 先找 container 再创建 |
| `assets/live2d/march7/viewer.html` | 316-328 | 修复 NaN 顶点数据索引错位 |
---
## 五、验证结果
| 测试 | 时长 | 结果 |
|------|------|------|
| Phase 1 修复后 | 30s | `timeout` kill无崩溃 |
| Phase 2 修复后 | 45s | `timeout` kill (EXIT 124),无崩溃 |
| 用户手动测试 | — | 待确认 |
控制台输出确认 `--no-sandbox` 生效:
```
Sandboxing disabled by user.
```
---
## 六、已知残余问题
### 6.1 QObject::setParent 警告12 个,无害)
```
QObject::setParent: Cannot set parent, new parent is in a different thread (x12)
```
这些警告来自 QWebEngineView 初始化时Chromium render 线程创建 GPU surface 代理 QObject随后被 reparent 到主线程 widget 树。即使 `--in-process-gpu` 消除 GPU 进程render 线程仍独立于主线程。这些警告是 Qt WebEngine 6.x 的内部实现细节,不影响稳定性。
### 6.2 showEvent 空 override
`pet_window.py:756-762``showEvent` 为空函数体pass原因是 `type()` 构造的类在调用 `super().showEvent()` 时会触发 PySide6 virtual dispatch 问题。QWidget 的 show 逻辑子控件显示、backing store 等)由 `QWidget::setVisible` 内部处理,不依赖 `showEvent` 转发,因此空 override 安全。
---
## 七、相关文档
- `docs/segfault_investigation.md` — 早期调查chat bubble + popup 交互问题)
- `docs/live2d_issue_report.md` — Live2D 渲染问题
- `docs/investigation/segfault-phase2.md` — Phase 2 诊断过程记录