waypipe + weston 全屏乱码排查:一次被现象带偏的过程
七月 08, 2026 [linux] #waypipe #weston #wayland #fontconfig #debug问题现象
通过 waypipe 把远程主机的 Wayland 应用转发到本地显示:
waypipe ssh peter@dev weston
结果整个 Weston 窗口(包括标题栏和内部终端)所有文字都是整齐的方块(tofu),连 ASCII 字符都无法显示。
关键对照:
- 同样的命令连另一台服务器
douhao完全正常。 - 直接
ssh dev用本地终端登录,文字显示一切正常。
这两个对照信息看似缩小了范围,实际上把排查带偏了好几次。本文如实记录整个过程,重点是每一步用什么工具、看什么输出、为什么某个结论是错的。
排查前的基础信息收集
第一步永远是确认版本和环境,不要凭记忆。
# 本地
waypipe --version # -> waypipe 0.8.6
# 两台服务器
ssh dev "waypipe --version; weston --version" # -> 0.9.2 / weston 14.0.2
ssh douhao "waypipe --version; weston --version" # -> 0.9.2 / weston 14.0.2
误判一:以为是字体缺失
最初的直觉:方块 = 缺字体。于是去数远程的字体:
ssh dev "fc-list | wc -l" # -> 168
ssh dev "fc-list | grep -iE 'mono|dejavu|noto'"
# /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf: DejaVu Sans Mono ...
# /usr/share/fonts/truetype/noto/NotoSansMono-Regular.ttf ...
fc-list 列出系统所有已注册字体。dev 上有 168 个字体,DejaVu Sans Mono、Noto 一应俱全。
结论:字体文件不缺。「缺字体」被排除。
这里其实已经埋下伏笔——fc-list 能列出字体,不代表字体查询系统本身是健康的。但当时没意识到。
误判二:以为是 GPU DMABUF 传输问题
既然字体不缺,转去看 dev 和 douhao 的硬件差异:
ssh dev "lspci -nn | grep -iE 'vga|3d'; ls -l /dev/dri"
ssh douhao "lspci -nn | grep -iE 'vga|3d'; ls -l /dev/dri"
发现一个「重大差异」:
| GPU | 缓冲区类型 | |
|---|---|---|
| dev(乱码) | 真实 Intel 显卡 (i915/iris, renderD128) | 硬件 tiled/带 modifier 的 DMABUF |
| douhao(正常) | virtio-gpu(虚拟机) | 线性 (linear) 缓冲区 |
推理链看起来很完美:Weston 在 dev 上用 GL 渲染器输出 Intel 平铺格式的显存缓冲区,waypipe 通过网络传这些 GPU buffer 时 tiling modifier 没正确还原 → 乱码;douhao 是虚拟 GPU、线性缓冲,所以正常。
于是给出三个方案(对应 waypipe --help 里的选项):
waypipe ssh peter@dev weston --renderer=pixman # 让 weston 用 CPU 软件渲染,输出线性缓冲
waypipe --no-gpu ssh peter@dev weston # 让 waypipe 不转发 GPU dmabuf
waypipe --allow-tiled ssh peter@dev weston # 允许带 modifier 的 tiled 缓冲
结果三个全部还是乱码。
--no-gpu走的是纯 SHM 内存缓冲路径,理论上完全绕开 GPU。它都乱码,说明根因不在 GPU / DMABUF 传输。GPU 差异理论被推翻。
教训:一个「显著差异」不等于「根因」。dev 和 douhao 有很多不同点,GPU 只是最扎眼的那个。
误判三:以为是 waypipe 版本不匹配
加 --debug 抓真实日志,这是转折点:
waypipe --debug ssh peter@dev "weston --renderer=pixman" 2>&1 | head -60
日志里跳出版本号:
c2951318: ... version: 0.8.6 <- 本地 client
s747338: ... version: 0.9.2 <- 远程 server
waypipe 0.8.x 和 0.9.x 之间线协议(wire protocol)确实不保证兼容,看起来是铁证。
但对照 douhao 时立刻自相矛盾:
waypipe --version # 本地 0.8.6
ssh dev "waypipe --version" # 0.9.2
ssh douhao "waypipe --version" # 0.9.2
ssh dev "ls -l \$(command -v waypipe)" # 236056 字节
ssh douhao "ls -l \$(command -v waypipe)" # 236056 字节,完全一致
| 连接 | 本地 client | 远程 server | 结果 |
|---|---|---|---|
| dev | 0.8.6 | 0.9.2 | 乱码 |
| douhao | 0.8.6 | 0.9.2 | 正常 |
两条连接是完全相同的 0.8.6 ↔ 0.9.2 组合。如果纯粹是版本协议不兼容,douhao 也该乱码。版本不匹配被推翻。
顺手也排除了 CPU/SIMD 差异(waypipe 用手写 AVX2/AVX512 内核做缓冲区 diff):
grep -m1 flags /proc/cpuinfo | grep -oE 'avx512[a-z]*|avx2' # 三方都查
结果本地是 i7-11700T(有 avx512),两台服务器都是 i7-14700K(相同,avx2、无 avx512)。两台服务器 CPU 完全一样,SIMD 路径不是差异点。
至此,dev 和 douhao 在版本、CPU、Mesa、字体文件上全都相同,硬件 GPU 差异又已被 --no-gpu 排除。必须换思路。
定位根因:对比 fontconfig 匹配结果
回到最朴素的观察:乱码是整齐的方块(tofu),不是彩色雪花噪点。
- 缓冲区/传输损坏 → 应该是随机噪点、错位、花屏。
- 整齐的
.notdef方块 → 是字形(glyph)查询失败。
现象一直在说「字体」,只是我前面把「字体文件存在」错当成了「字体系统正常」。直接对比两台的字体匹配行为(而不是字体列表):
for h in dev douhao; do
echo "===== $h ====="
ssh $h "fc-match monospace; fc-match sans-serif; fc-cache -v 2>&1 | grep -iE 'invalid|成功'"
done
fc-match <类别> 才是关键工具——它模拟应用程序实际拿字体的过程,回答「monospace 这个别名最终解析到哪个字体文件」。输出对比:
fc-match monospace | fc-cache 状态 | |
|---|---|---|
| dev | Droid Sans Fallback | ⚠️ invalid cache file: 21a99156...-le64.cache-9 |
| douhao | Noto Sans Mono CJK SC | 正常 |
两个问题同时暴露:
- dev 的 fontconfig 缓存损坏:
invalid cache file,douhao 没有这行。 - dev 默认 monospace 解析到了 CJK 覆盖不全的
Droid Sans Fallback,douhao 是完整的Noto CJK。
修复
# 在 dev 上执行
rm -rf ~/.cache/fontconfig # 删除损坏的缓存
sudo apt install fonts-noto-cjk fonts-noto-mono # 补齐和 douhao 一致的字体
fc-cache -fv # 重建缓存
fc-match monospace # 验证 -> Noto Sans Mono CJK SC
验证:
ssh dev "fc-match monospace; fc-cache -v 2>&1 | grep -iE 'invalid|成功'"
# NotoSansCJK-Regular.ttc: "Noto Sans Mono CJK SC" "Regular"
# fc-cache: 缓存生成成功 <- 不再有 invalid
之后裸命令 waypipe ssh peter@dev weston 恢复正常,不需要任何 --no-gpu / --renderer 参数。
根因解释:为什么连 ASCII 都变方块?
这是最反直觉的一点,也是最值得记住的:
ASCII 字形任何西文字体都有,「monospace 匹配到覆盖不全的字体」根本不该让 ASCII 也变方块。
真正让 ASCII 也乱码的是另一半原因:fontconfig 缓存损坏。渲染链路是:
weston-terminal -> Cairo / Pango -> fontconfig -> FreeType
(查询) (取字形)
- 每渲染一个字符,Pango 都要先问 fontconfig:「这个码点用哪个字体?文件在哪?glyph 索引多少?」
- fontconfig 的这套查询依赖磁盘上的缓存文件。
- 当缓存是
invalid状态时,查询整体失败——不是「某个中文字找不到」,而是连 ASCII 的字形查询都拿不到结果。 - 拿不到 glyph,Pango 对所有字符(
~、$、字母、中文)一律画成.notdef空方块。
所以现象才是「连 ASCII 都是方块」:不是字体文件缺失,是字体查询系统整体挂了。
两个原因各自的作用要分清:
| 原因 | 造成的现象 | 修复手段 |
|---|---|---|
| fontconfig 缓存损坏 | 连 ASCII 都变方块(根因) | rm -rf ~/.cache/fontconfig && fc-cache -fv |
| 默认字体是 Droid Sans Fallback | 中文提示符覆盖不全 | 安装 fonts-noto-cjk |
真正治好 ASCII 方块的是删缓存重建那一步;装 Noto CJK 只是顺带把中文显示也补全了。
为什么直接 ssh dev 就没事?
因为渲染位置不同:
- 直接
ssh dev:文字在本地终端仿真器里渲染,用的是本地机器的字体系统,dev 的 fontconfig 完全没参与。 waypipe ssh dev weston:Weston 及其终端跑在 dev 上,所有字形渲染都在 dev 完成,然后把画好的像素缓冲通过 waypipe 传回本地。这条路径才会踩到 dev 上损坏的 fontconfig。
推论:本地直连 dev 跑 GUI 也会乱码吗?
会。判断标准始终是**「谁在画字、用哪台机器的 fontconfig」**,跟 waypipe / 网络无关。
如果直接坐在 dev 前(或本地登录 dev)用它的显示器跑 GUI 程序(weston、weston-terminal 等),字形渲染就在 dev 上、走 dev 的 fontconfig 完成,同样会命中那份损坏的缓存 → 一样的方块。
有一个前提要注意:损坏的是用户 peter 的 ~/.cache/fontconfig(用户级缓存)。所以精确说法是:
- 以 peter 身份在 dev 上跑 GUI → 乱码。
- 换成缓存正常的另一个用户(或 root 用另一份缓存)→ 未必乱码。
这也反过来印证了前一节:ssh dev 用本地终端不乱码,是因为渲染在本机、用本机 fontconfig,dev 的坏缓存根本没参与。
经验总结
- 让现象说话,别让「显著差异」抢戏。 整齐方块(tofu)= 字形缺失,彩色噪点 = 缓冲区损坏。一开始就该抓住这个区别,而不是先盯着 GPU。
- 对照组是双刃剑。 「douhao 正常」帮我推翻了版本、CPU、GPU 理论,但也一度让我忽略「两台都有字体文件、差异只在匹配结果」这个更细的层面。用对照时要对比到行为(
fc-match),而不只是存在性(fc-list)。 --debug尽早上。 waypipe/weston 的调试日志一句话就暴露了版本号、dmabuf 权限(renderD128: Permission denied,说明 dmabuf 早已被禁用)等关键事实,省去大量猜测。- 区分「文件存在」和「系统可用」。
fc-list能列出字体 ≠ fontconfig 能正常查询。缓存损坏是「文件都在但系统坏了」的典型。
工具速查
| 工具 | 用途 | 本次关键输出 |
|---|---|---|
waypipe --version | 确认两端版本 | 排除/确认版本不匹配 |
waypipe --debug ... | 打印协议级日志 | 暴露版本号、dmabuf 权限被拒 |
waypipe --help | 查 --no-gpu / --allow-tiled / --renderer | 尝试绕过 GPU |
fc-list | 列出已安装字体(存在性) | 168 个,文件不缺 |
fc-match <类别> | 模拟应用取字体(匹配行为) | 定位到 Droid Sans Fallback / 缓存 invalid |
fc-cache -fv | 重建字体缓存 | 修复根因 |
lspci -nn / ls /dev/dri | 查 GPU 类型 | 发现 Intel vs virtio(最终无关) |
/proc/cpuinfo flags | 查 SIMD 指令集 | 排除 AVX 差异 |