Repository navigation
Linux 下 GLFW/OpenGL 程序运行时无法创建窗口:mcpp 自带 glibc(2.39)低于宿主 Mesa 要求(2.43),且 compat-x-glx-runtime 指向 32 位库 #352
Description
环境
| 项 | 值 |
|---|---|
| 发行版 | Fedora 44 (x86_64),Wayland 会话 + XWayland(DISPLAY=:0) |
| mcpp | 2026.8.4.1(经 xlings 安装) |
| 工具链 | xim-x-llvm 22.1.8(默认) |
| 运行时 glibc | xim-x-glibc 2.39 |
| 相关包 | compat-x-glfw 3.4、compat-x-eui-neo 0.5.3(app-main)、compat-x-glx-runtime 2026.06.03 |
| 宿主图形栈 | 系统 Mesa 26.1.6(libGL/libGLX/libEGL,要求 GLIBC_2.43) |
现象
mcpp build 完全正常;mcpp run 后进程立即退出、返回码 255(即 -1)、stdout/stderr 无任何输出,窗口不出现。
最小复现
任何用到 GLFW + OpenGL 的 mcpp 项目都可复现,例如:
# mcpp.toml
[package]
name = "glfw-repro"
version = "0.1.0"
[targets.glfw-repro]
kind = "bin"
main = "src/main.cpp"
[dependencies.compat]
eui-neo = { version = "0.5.3", features = ["app-main"] }src/app.cpp 只需定义 app::dslAppConfig() 和 app::compose()(eui-neo 的 GLFW 入口 core/app/glfw_app_main.cpp 提供 main())。然后:
mcpp build # 成功
mcpp run # Running `target/.../bin/xxx` → 静默退出,exit code 255诊断过程与根因
用项目 target/ 里已编译的 glfw 对象文件链接一个带 glfwSetErrorCallback 的最小测试,逐层定位,发现三个互相独立的问题叠加:
问题 1:eui-neo/GLFW 初始化失败时完全静默(可观测性)
glfw_app_main.cpp 的 main() 在 glfwInit()、createWindow()、createRenderBackend()、app::initialize() 任一处失败都只 return -1,没有安装 GLFW error callback,也没有 stderr 输出,用户完全无法判断失败原因。
问题 2:compat-x-glx-runtime 包在 Fedora 系发行版上损坏(32 位符号链接)
GLFW 报 GLX: Failed to load GLX(65542)。LD_DEBUG=libs 显示 dlopen libGLX.so.0 / libGL.so.1 时只搜索了 mcpp 自己的目录(二进制 RUNPATH + xlings 私有 ld.so.cache),不含 /usr/lib64,因此宿主 GL 库天然找不到。
mcpp 显然预见到了这一点 —— 二进制的 RUNPATH 里已经包含
compat-x-glx-runtime/2026.06.03/mcpp_generated/glx_runtime/lib。
但该包的内容是指向 /usr/lib/* 的符号链接:
libGLX.so.0 -> /usr/lib/libGLX.so.0 # Fedora 上这是 32 位库!
libGL.so.1 -> /usr/lib/libGL.so.1
libGLdispatch.so.0 -> /usr/lib/libGLdispatch.so.0
libGLX_mesa.so.0 -> /usr/lib/libGLX_mesa.so.0
libOpenGL.so.0 -> /usr/lib64/libOpenGL.so.0 # 只有这个是 64 位
Debian/Ubuntu 的 64 位库在 /usr/lib/x86_64-linux-gnu 或 /usr/lib,而 Fedora/RHEL/openSUSE 的 /usr/lib 是 32 位库目录,64 位库在 /usr/lib64。于是 dlopen 直接报:
libGLX.so.0: wrong ELF class: ELFCLASS32
即该包按 Debian 布局假设生成,在 Fedora 布局下整体失效。
问题 3(核心):mcpp 自带 glibc 2.39 与宿主图形栈的 glibc 要求不兼容
绕过问题 2(手工把 /usr/lib64 的 64 位库放进搜索路径)之后,所有 GL 库都能生成 link map,但 Mesa 在初始化时崩溃于:
libm.so.6: error: version lookup error: version `GLIBC_2.43' not found
(required by libgallium-26.1.6.so) (fatal)
mcpp 二进制的 PT_INTERP 是 xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2,进程内 glibc 固定为 2.39;而宿主的 Mesa/GLX/EGL 栈是按系统 glibc 2.43 编译的。Linux 桌面 GUI 程序必须使用宿主机的 GL 驱动栈(它还要和宿主 X/Wayland、DRM 设备通信),因此"自带 glibc 沙盒运行时"这个对 CLI 程序很友好的设计,在 GUI 场景下与宿主图形栈产生硬性版本冲突:宿主 Mesa 只会随发行版越来越新,自带 glibc 不升级就必然断。
另外实测 LD_LIBRARY_PATH=/usr/lib64 也不能作为变通:进程仍由 2.39 的 ld.so 加载,ld.so 与 libc.so.6 版本不匹配会直接段错误。
临时解决方案(workaround)
用系统动态链接器启动,让进程整体跑在系统 glibc 2.43 上(mcpp 编译产物按 2.39 编译,在 2.43 上向前兼容;自带的 X11 库也照常可用):
#!/usr/bin/env bash
# run.sh — 用系统 glibc 运行 mcpp 构建的 GUI 程序
set -euo pipefail
cd "$(dirname "$0")"
BIN=$(ls -d target/x86_64-linux-gnu/*/bin | head -1)
exec /lib64/ld-linux-x86-64.so.2 \
--library-path "/usr/lib64:$PWD/$BIN" \
"$PWD/$BIN/tinynext" "$@"效果:GLX 初始化成功、窗口正常创建、中文渲染正常(已截图验证)。
修复建议
- compat-x-glx-runtime 包:生成符号链接时不要假设
/usr/lib是 64 位目录。建议改为探测ldconfig -p输出中的 64 位库实际路径,或按发行版布局选择/usr/lib64//usr/lib/x86_64-linux-gnu。 - glibc 版本策略(更根本):GUI 程序依赖宿主驱动栈,建议二选一:
- 提供与主流发行版同步的新版
xim-x-glibc(≥ 2.43);或 - 对 GUI 类目标提供"使用宿主 glibc 运行"的模式(例如
mcpp run --system-glibc,本质上就是上面的 workaround:用/lib64/ld-linux-x86-64.so.2 --library-path /usr/lib64:...启动,并把/usr/lib64注入 dlopen 搜索路径)。
- 提供与主流发行版同步的新版
- 可观测性:建议 compat-x-eui-neo 的
glfw_app_main.cpp在main()开头安装glfwSetErrorCallback并向 stderr 打印错误;各初始化失败分支return -1前输出失败阶段名称。这一条能省掉用户 80% 的定位时间。
附:诊断中用到的关键命令
# 用项目已编译的 glfw 对象文件链接最小复现(带 error callback)
clang repro.c target/*/obj/compat_glfw/src/*.o -L$BINDIR -lX11 ...
# → GLFW error 65542: GLX: Failed to load GLX
# 观察 dlopen 搜索路径(只有 mcpp 目录,没有 /usr/lib64)
LD_DEBUG=libs ./repro
# 验证 glx-runtime 包链接目标错误
ls -la ~/.mcpp/registry/data/xpkgs/compat-x-glx-runtime/*/mcpp_generated/glx_runtime/lib/
# → libGLX.so.0 -> /usr/lib/libGLX.so.0(32 位)
# 验证 glibc 版本鸿沟
llvm-readelf -V /usr/lib64/libgallium-26.1.6.so | grep GLIBC_ # 需要 2.43
strings ~/.mcpp/.../xim-x-glibc/2.39/lib64/libc.so.6 | grep GLIBC_ | tail -1 # 只有 2.39
Activity
feat(subos): a subos describes itself, and packages can declare its e…
fix(compat.glx-runtime): `ln -sf` was last-wins, and nothing checked …
fix(compat.glx-runtime): `ln -sf` was last-wins, and nothing checked …
feat(graphics): the discovery layer moves to the program, and WSL2 ge…
feat: xlings 作为运行时底座 —— subos 环境到达程序 + self-contained 的 /proc/self/ex…
fix(xlings): read the subos_info wire format xlings actually writes
feat: 编译器是能力,不是配置 —— payload 版本权威 + specs 污染 + xlings pin (2026.8.8.2) (
speak-agent commentedon Aug 8, 2026
进展与剩余缺口(2026-08-08,实测)
这条 issue 的两半里,一半已解决,另一半定位到了确切的一步。issue 保持打开。
已解决
① mcpp 侧:subos 声明的环境变量到达被运行的程序(2026.8.8.1)。此前 mcpp 读的是自己想象出来的
subos_info.envs线格式,整条修复是空转的;现按 xlings 实际写出的格式解析,并用逐字取自真实 xlings 输出的 fixture 钉住。② 索引侧:GL 来自生态而非
/usr/lib(mcpp-index #181)。compat.glx-runtime不再从宿主符号链接 libGL/libEGL。③ 引擎侧:产物绑哪个 glibc 不再靠猜(2026.8.8.2)。这其实是 #181 第一次落地(#179)被 revert 的原因:mesa 的
xim:glibc@>=2.38会在 2.39 旁边装进 2.44,而当时 mcpp 用readdir第一项选 payload,于是编译侧取 2.44、产物 interpreter 仍是 2.39,报错落在asio-module/core这些与图形无关的成员上。现由权威决定并计入指纹。密闭性实测(NVIDIA RTX 4080 宿主,CN 与 GLOBAL 镜像各一次):
剩余缺口:GLX vendor 选择
EGL 路径已通,GLX 路径不通。 而 GLFW 在 X11 上默认走 GLX —— 这就是用户看到「无法创建窗口」的直接原因。
用一个直接询问 glvnd 的探针测到:
__GLX_VENDOR_LIBRARY_NAMEglXGetFBConfigsglXChooseFBConfigmesanvidia两条结论:
libnvidia-glsi/libnvidia-tls/libnvidia-glcore/libX11/libc全部落在 subos lib)。失败原因被 glvnd 吞掉,__GLVND_DEBUG=1也不吐。顺带记一条会误导后来人的观察:共享 registry 里生成的
libGL.so.1,其 RUNPATH 末项烙的是首个生成它的项目的 subos 路径。另一个项目的产物会沿着别人的 subos 去找 vendor —— 在本机恰好都指向同一份宿主驱动的软链,所以不是本次失败的原因,但它是一个真实的跨项目耦合。下一步
从「找到了却装不成」这一步继续:需要拿到 glvnd 吞掉的那个 dlopen 失败原因(glvnd 的 GLX vendor 装载路径里有 ABI 版本检查 ——
__glXMappingInit/ vendor 的__glx_Main入口点版本协商,不匹配时它会静默拒绝并回落)。这是本 issue 唯一还没被排除的环节。speak-agent commentedon Aug 8, 2026
根因定位完成:转发桩把依赖解析交给了一个在载荷里不存在的搜索路径
接上条。「nvidia vendor 被找到却装载不成」这一步查清了,是
xim:nvidia-gl-host-link的问题,不在 mcpp 也不在 mcpp-index。链条
libGLX_nvidia.so.0在载荷里是一个转发桩,它唯一的NEEDED是宿主的绝对路径:载入器顺着它加载宿主真库,然后去找那个库的依赖 ——
LD_DEBUG=libs原文:这次查找不带任何 RUNPATH。 三个事实叠在一起:
libGLX_nvidia.so.550.144.03host-link包里那份 550 库/home/xlings/.xlings_data/...,构建机路径,本机不存在于是
libnvidia-glsi无处可找 → 宿主真库装不进来 → 转发桩失败 → glvnd 静默回落 →glXGetFBConfigs返回 0 → GLFW 报No GLXFBConfigs returned。为什么之前的排查一路指错方向
dlopen+dlsym("__glx_Main")成功 —— 所以「桩坏了」被排除libnvidia-glsi确实在 subos lib 里,单独用载荷 loader + 正确LD_LIBRARY_PATH解析时,它和全部依赖都在载荷内解析成功 —— 所以「缺件」也被排除失败只发生在转发进宿主库之后的那一跳,因为那一跳换了对象、也就换了 RUNPATH 上下文。glvnd 吞掉 dlopen 错误(
__GLVND_DEBUG=1也不吐),所以从表面完全看不出来。可能的修法(在
nvidia-gl-host-link)$ORIGINRUNPATH —— 让它们的同目录兄弟能互相找到,这与该包已经在做的「重链」是同一类操作NEEDED(soname)链接,让载荷搜索路径生效顺带:载荷 glibc 烙进去的
ld.so.cache路径指向构建机,这本身是个独立的小问题 —— 任何依赖 cache 回落的库在载荷里都会踩到。现状
exit=0,窗口创建并渲染)__GLX_VENDOR_LIBRARY_NAME=mesa下拿到 18 个 FBConfigspeak-agent commentedon Aug 8, 2026
修法已通过实验确定(未实施 —— 需要改
xim:nvidia-gl-host-link)接上条。把链条一路走到 vendor 真的装载成功,确认了修法形状,也排除了两条看似合理但行不通的路。
缺的到底是什么
__nvidia_entries()只链 NVIDIA 名字的库:而宿主
libGLX_nvidia.so.550.144.03的DT_NEEDED还包括libX11.so.6、libXext.so.6,以及 glibc 侧的libdl.so.2/libpthread.so.0/libc.so.6。描述符自己的注释写着:
代码没有这个行为。 与我这轮在 mcpp 侧修过的是同一种病:注释描述了一件代码不做的事。
逐步验证(每一步都推进了一层)
但「补软链」不是正确修法 —— 两条排除
① 把 glibc 软链进该目录很危险。 我这样做并把目录放上
LD_LIBRARY_PATH后,宿主的timeout进程也去用了载荷 libc:这就是 GLIBC_PRIVATE 版本锁。载荷 glibc 不能以裸 soname 出现在任何可能被宿主进程搜到的目录里。(我已把这些软链全部删除,环境已还原;宿主驱动文件全程未被改动。)
② 光让依赖「可被找到」不够 ——
DT_RUNPATH不向下继承。 真实程序里做查找的是宿主真库(它自己 RUNPATH 条目数为 0),而不是有完整 RUNPATH 的转发桩。所以即使把目录喂给LD_LIBRARY_PATH能让 dlprobe 通过,真实程序仍然不通 —— 除非污染全局LD_LIBRARY_PATH,而那正是 ① 的坑。因此:让桩把这些依赖也 NEED 上
libGLX_nvidia.so.0是本包生成的转发桩(21KB 实体文件,不是软链),它已经有覆盖libX11/libXext/glibc 载荷目录的完整 RUNPATH。若生成时把宿主 vendor 的DT_NEEDED一并声明为桩自己的 NEEDED,则:这与该包已有的「重链」是同一类操作,且不违反「不复制 NVIDIA 库」——桩里只有名字,没有代码。
现状小结
exit=0,窗口创建并渲染)__GLX_VENDOR_LIBRARY_NAME=mesa下 18 个 FBConfigdocs: 计划文档标记为已完成 (#381)
fix(nvidia-gl-host-link): 桩要的是 DT_RPATH,拿到的是 DT_RUNPATH (#564)
fix(wsl-gl-host-link): force GALLIUM_DRIVER=d3d12 only when the drive…
speak-agent commentedon Aug 8, 2026
已修复并合入
根因是一个词:转发桩携带的是
DT_RUNPATH,而设计要的是DT_RPATH。nvidia-gl-host-link.lua的注释早就写明了这一点 —— 「RPATH 沿加载链传递,RUNPATH 不会」。桩用绝对路径 NEED 宿主真 vendor,于是随后去查libnvidia-glsi/libX11/libc的那个对象是宿主的库,而它自身 RPATH/RUNPATH 条目为 0。RUNPATH 不传递,桩算出的闭包对那次查找完全不可见,glibc 回落到一个用构建机前缀烙成的 ld.so.cache 加一条空 system path。什么都找不到,glvnd 吞掉 dlopen 错误,用户只看到GLX: No GLXFBConfigs returned。合入
DT_RPATH(值从--print-rpath读回,不手写第二份闭包)try里、一步依赖 PATH 上有 patchelf,静默跳过会完整重现本缺陷prepend把调用方的值整个丢掉(对 PATH 形状变量即用户的整条搜索路径)验证
A/B,同一桩文件只换 tag,其余无任何差别:
产出 tag 的那段 Lua 也在
xlings script里单独实跑过:读到 494 字符 rpath,patch 后 tag 变RPATH。没有验证到的那一环,说清楚: 本机上「全新安装跑一遍 install()」没能验成 —— 已有 payload 让重装成为 no-op,而全新隔离 home 撞到
same-owner registration omits an existing member of group这个与本改动无关的 xvm 注册问题。两个 PR 的 CI(含linux-install-test)全绿。排查中被否掉的两条路(记下来免得重走)
timeout进程因此拿到载荷 libc,报__pointer_chk_guard, version GLIBC_PRIVATE。这条 issue 的三半
/usr/lib(mcpp-index#181)全部完成。宿主驱动文件全程未被改动(root 所有,原始 mtime,已核)。