Skip to content

Linux 下 GLFW/OpenGL 程序运行时无法创建窗口:mcpp 自带 glibc(2.39)低于宿主 Mesa 要求(2.43),且 compat-x-glx-runtime 指向 32 位库 #352

Description

@FarnaHerry

环境

项 值
发行版 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 初始化成功、窗口正常创建、中文渲染正常(已截图验证)。

修复建议

  1. compat-x-glx-runtime 包:生成符号链接时不要假设 /usr/lib 是 64 位目录。建议改为探测 ldconfig -p 输出中的 64 位库实际路径,或按发行版布局选择 /usr/lib64 / /usr/lib/x86_64-linux-gnu。
  2. 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 搜索路径)。
  3. 可观测性:建议 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

  1. speak-agent commented on Aug 8, 2026

    @speak-agent
    Member

    进展与剩余缺口(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 镜像各一次):

    产物 interp        → payload glibc 2.44
    强制用宿主库       → libm.so.6: GLIBC_2.43 not found   ← 反证确实绑载荷
    NVIDIA vendor 供给 → 50 个 nvidia 文件在 subos lib,RUNPATH 完整
    

    剩余缺口:GLX vendor 选择

    EGL 路径已通,GLX 路径不通。 而 GLFW 在 X11 上默认走 GLX —— 这就是用户看到「无法创建窗口」的直接原因。

    用一个直接询问 glvnd 的探针测到:

    __GLX_VENDOR_LIBRARY_NAME glXGetFBConfigs glXChooseFBConfig
    mesa 18 9
    nvidia 0 0
    不设 0 0
    glXQueryVersion      : ok (1.4)      ← 服务端有 GLX 扩展
    has GLX_EXT_libglvnd : NO            ← 服务端不广告它,glvnd 无从自动选 vendor
    client GLX_VENDOR    : <null>        ← 一个 vendor 都没加载
    

    两条结论:

    1. 载荷 GLX 栈本身是好的 —— mesa vendor 下拿到 18 个 FBConfig。
    2. nvidia vendor 被找到却装载不成 —— 载入器在真实进程中尝试了正确路径 29 次,而单独用载荷 loader + 正确搜索路径解析该库时,它和全部依赖都在载荷内解析成功(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 唯一还没被排除的环节。

  2. speak-agent commented on Aug 8, 2026

    @speak-agent
    Member

    根因定位完成:转发桩把依赖解析交给了一个在载荷里不存在的搜索路径

    接上条。「nvidia vendor 被找到却装载不成」这一步查清了,是 xim:nvidia-gl-host-link 的问题,不在 mcpp 也不在 mcpp-index。

    链条

    libGLX_nvidia.so.0 在载荷里是一个转发桩,它唯一的 NEEDED 是宿主的绝对路径:

    NEEDED  /lib/x86_64-linux-gnu/libGLX_nvidia.so.0
    

    载入器顺着它加载宿主真库,然后去找那个库的依赖 —— LD_DEBUG=libs 原文:

    find library=libnvidia-glsi.so.550.144.03 [0]; searching
     search cache=/home/xlings/.xlings_data/xim/xpkgs/fromsource-x-glibc/2.44/etc/ld.so.cache
     search path=            (system search path)
    

    这次查找不带任何 RUNPATH。 三个事实叠在一起:

    宿主 libGLX_nvidia.so.550.144.03 RUNPATH 条目数 0 —— 它依赖发行版的 ld.so.cache
    host-link 包里那份 550 库 同样没有 RUNPATH
    载荷 glibc 的 ld.so.cache 指向 /home/xlings/.xlings_data/...,构建机路径,本机不存在

    于是 libnvidia-glsi 无处可找 → 宿主真库装不进来 → 转发桩失败 → glvnd 静默回落 → glXGetFBConfigs 返回 0 → GLFW 报 No GLXFBConfigs returned。

    为什么之前的排查一路指错方向

    • 桩自己有完整 RUNPATH,且单独 dlopen + dlsym("__glx_Main") 成功 —— 所以「桩坏了」被排除
    • libnvidia-glsi 确实在 subos lib 里,单独用载荷 loader + 正确 LD_LIBRARY_PATH 解析时,它和全部依赖都在载荷内解析成功 —— 所以「缺件」也被排除

    失败只发生在转发进宿主库之后的那一跳,因为那一跳换了对象、也就换了 RUNPATH 上下文。glvnd 吞掉 dlopen 错误(__GLVND_DEBUG=1 也不吐),所以从表面完全看不出来。

    可能的修法(在 nvidia-gl-host-link)

    1. 给被链接的驱动库补 $ORIGIN RUNPATH —— 让它们的同目录兄弟能互相找到,这与该包已经在做的「重链」是同一类操作
    2. 或不走绝对路径转发,直接把宿主驱动库以正常 NEEDED(soname)链接,让载荷搜索路径生效

    顺带:载荷 glibc 烙进去的 ld.so.cache 路径指向构建机,这本身是个独立的小问题 —— 任何依赖 cache 回落的库在载荷里都会踩到。

    现状

    • EGL 路径可用(实测 exit=0,窗口创建并渲染)
    • GLX 路径不可用,原因如上;GLFW 在 X11 默认走 GLX
    • 载荷 GLX 栈本身是好的:__GLX_VENDOR_LIBRARY_NAME=mesa 下拿到 18 个 FBConfig
  3. speak-agent commented on Aug 8, 2026

    @speak-agent
    Member

    修法已通过实验确定(未实施 —— 需要改 xim:nvidia-gl-host-link)

    接上条。把链条一路走到 vendor 真的装载成功,确认了修法形状,也排除了两条看似合理但行不通的路。

    缺的到底是什么

    __nvidia_entries() 只链 NVIDIA 名字的库:

    "^libnvidia%-", "^libGLX_nvidia%.", "^libEGL_nvidia%.",
    "^libGLESv1_CM_nvidia%.", "^libGLESv2_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。

    描述符自己的注释写着:

    install() links each of these into this package's own lib dir, so declaring them is not decoration — it is where they come from.

    代码没有这个行为。 与我这轮在 mcpp 侧修过的是同一种病:注释描述了一件代码不做的事。

    逐步验证(每一步都推进了一层)

    起点                        dlopen 失败: libXext.so.6 not found
    补 libX11/libXext 软链  →   dlopen 失败: libdl.so.2 not found
    再补 glibc 侧 5 个      →   dlopen ok; __glx_Main=found     ← vendor 装载成功
    

    但「补软链」不是正确修法 —— 两条排除

    ① 把 glibc 软链进该目录很危险。 我这样做并把目录放上 LD_LIBRARY_PATH 后,宿主的 timeout 进程也去用了载荷 libc:

    symbol lookup error: .../libc.so.6: undefined symbol: __pointer_chk_guard, version GLIBC_PRIVATE
    

    这就是 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,则:

    • 这些依赖经桩的 RUNPATH 解析(nvidia 那几个就在同目录,是 RUNPATH 首项)
    • 等宿主真库被拉进来时,它的 NEEDED 已在 link map 中被满足
    • 不需要任何环境变量,也不需要把 glibc 软链散到别处

    这与该包已有的「重链」是同一类操作,且不违反「不复制 NVIDIA 库」——桩里只有名字,没有代码。

    现状小结

    EGL 路径 ✅ 可用(exit=0,窗口创建并渲染)
    GLX 路径 ❌ vendor 装不进(原因如上)
    载荷 GLX 栈本身 ✅ __GLX_VENDOR_LIBRARY_NAME=mesa 下 18 个 FBConfig
    宿主驱动文件 ✅ 全程未被改动(root 所有,原始 mtime)
  4. speak-agent commented on Aug 8, 2026

    @speak-agent
    Member

    已修复并合入

    根因是一个词:转发桩携带的是 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。

    合入

    PR 内容
    openxlings/xim-pkgindex#564 装完把 tag 改回 DT_RPATH(值从 --print-rpath 读回,不手写第二份闭包)
    openxlings/xim-pkgindex#566 补丁没打上时告警 —— 两步一步在 try 里、一步依赖 PATH 上有 patchelf,静默跳过会完整重现本缺陷
    #383 顺带修掉的:prepend 把调用方的值整个丢掉(对 PATH 形状变量即用户的整条搜索路径)

    验证

    A/B,同一桩文件只换 tag,其余无任何差别:

    RUNPATH → GLFW error 65542,exit 11
    RPATH   → exit 0,libGLX_nvidia 初始化 2 次,libnvidia-glcore 初始化 1 次
    

    产出 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)全绿。

    排查中被否掉的两条路(记下来免得重走)

    • 把缺的库软链进包的 lib 目录:能让隔离的 dlopen 通过,真实程序仍然不通(还是 RUNPATH 不传递);而且把载荷 glibc 放进那个目录很危险 —— 实测宿主 timeout 进程因此拿到载荷 libc,报 __pointer_chk_guard, version GLIBC_PRIVATE。
    • 给桩补 DT_NEEDED:可行,但要算传递闭包,且必须把宿主 X11 链(libbsd/libmd)排除在外——那些在载荷里不存在。能跑通,但远比换一个 tag 复杂,也更容易漂移。

    这条 issue 的三半

    1. subos 声明的环境变量到达程序(mcpp 2026.8.8.1)
    2. GL 来自生态而非 /usr/lib(mcpp-index#181)
    3. NVIDIA vendor 装得进(本次)

    全部完成。宿主驱动文件全程未被改动(root 所有,原始 mtime,已核)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions