feat(elfpatch): stamp DT_RPATH on executables — DT_RUNPATH cannot reach what they dlopen (0.0.57) - #41
Merged
Merged
Conversation
…ch what they dlopen (0.0.57) `patchelf --set-rpath` writes DT_RUNPATH by default. DT_RUNPATH is consulted only for the object that carries it; DT_RPATH is consulted for every dlopen anywhere in the process, however deep. The graphics stack's load chain is three to four dlopens deep — glvnd dlopens a vendor, the vendor dlopens its EGL external platform modules, those dlopen their own dependencies — so only the transitive tag reaches the bottom. Measured on an NVIDIA 550.144.03 host: identical path content, stamped DT_RPATH, puts GLX, EGL, GLESv2 and headless surfaceless EGL all on the GPU with no environment variable, no index change and no change to the interposer; stamped DT_RUNPATH it renders on llvmpipe. The path was always right and the tag was always wrong. Of 73 installed executables on a real home: 1 carried DT_RPATH, 68 carried DT_RUNPATH, and 55 of those 68 already had the subos farm on that path. The single DT_RPATH binary was godot — the only program in the stack ever observed to render on the GPU — because its recipe calls `patchelf --force-rpath` by hand and documents exactly why. That knowledge had no way to reach the other 68. EXECUTABLES ONLY, and that is the load-bearing half. Forcing DT_RPATH on a LIBRARY is measured harmful (xim-pkgindex#593): transitivity runs downward too, so a library's RPATH enters every lookup made beneath it, and the NVIDIA EGL interposer stamped that way fails eglInitialize outright. PT_INTERP is the predicate, and it is the same one that already decides whether to set an interpreter — computed once now and used for both, so it costs nothing where a loader is set. The flag goes through one `_rpath_flags()` rather than being inlined at the two call sites, because a per-site decision is how this became a per-package decision in the first place. Behaviour change worth stating plainly: DT_RPATH outranks LD_LIBRARY_PATH while DT_RUNPATH loses to it, so a user can no longer override these payloads with LD_LIBRARY_PATH. Measured: LD_LIBRARY_PATH is not set inside a subos at all, and the recipes' own comments state the design goal as "what makes the stack resolve without anyone setting LD_LIBRARY_PATH" — so nothing that exists today relies on it. DT_RPATH also does not propagate to child processes, which is precisely why it is the safe mechanism here. Existing payloads keep their old tag until reinstalled; this stamps at install time. Design: xlings/.agents/docs/2026-08-10-graphics-stack-design.md §E1a. Test drives the real predicate through a recording patchelf stub that answers --print-interpreter by filename, and asserts the flag lands on the executable and NOT on the library. Falsified both ways before being trusted: never stamping fails it, and stamping libraries too fails it.
Sunrisepeak
added a commit
to mcpplibs/mcpp-index
that referenced
this pull request
Aug 10, 2026
elfpatch now stamps DT_RPATH on executables instead of DT_RUNPATH. DT_RUNPATH is consulted only for the object carrying it; DT_RPATH is consulted for every dlopen anywhere in the process. The graphics stack's load chain is three to four dlopens deep, so only the transitive tag reaches the bottom — measured, the same path content stamped DT_RPATH puts GLX, EGL, GLESv2 and headless surfaceless EGL all on the GPU, and stamped DT_RUNPATH renders on llvmpipe. Executables only. Forcing DT_RPATH on a library is measured harmful (xim-pkgindex#593) because transitivity runs downward too. All THREE platform sections, verified by PARSING the recipe rather than reading the diff — a regex edit to a .lua applies cleanly and can mean something else: linux 0.0.57=yes sha=7b24c2f156ce versions=18 macosx 0.0.57=yes sha=7b24c2f156ce versions=18 windows 0.0.57=yes sha=7b24c2f156ce versions=18 CN mirror uploaded and verified by SIZE (gitcode returns 200 with an HTML landing page for an asset that is not there, so the status code proves nothing): 147212 bytes, matching the GitHub archive. Upstream: openxlings/libxpkg#41
This was referenced Aug 10, 2026
Sunrisepeak
added a commit
to openxlings/xim-pkgindex
that referenced
this pull request
Aug 10, 2026
The loader tag now has one owner. elfpatch (libxpkg 0.0.57) stamps DT_RPATH on executables instead of DT_RUNPATH, and closure_check gains rule E to say so at install time on the user's machine when a payload arrives with the old tag. DT_RUNPATH is consulted only for the object carrying it; DT_RPATH is consulted for every dlopen anywhere in the process. A GL program reaches its driver through three levels of dlopen, so the tag — not the path — decided whether it rendered on the GPU. Of 73 installed executables on a real home, 1 carried DT_RPATH and 55 of the other 68 already carried the correct path. Also: `subos info` reports host driver drift (the detector existed as `xlings-gl-doctor` and lacked a channel), and `subos use --sandbox` without `--gpu` says the sandbox exposes no GPU — under three conditions, so it can only fire where acting on it changes the outcome. All THREE platform sections, verified by PARSING rather than reading the diff: linux latest=2026.8.10.4 arch=[aarch64,x86_64] macosx latest=2026.8.10.4 arch=[aarch64] windows latest=2026.8.10.4 arch=[x86_64] Hashes taken from the assets in xlings-res — what `XLINGS_RES` actually resolves to — not from the upstream release they were mirrored from. CN mirror uploaded from a local shell and verified by SIZE on all four platforms; before the upload all four returned 127 bytes, so the status code would have said 200 for assets that were not there. Upstream: openxlings/xlings#536, openxlings/libxpkg#41, mcpplibs/mcpp-index#202 Co-authored-by: sunrisepeak <x.d2learn.org@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
patchelf --set-rpath默认写 DT_RUNPATH。DT_RUNPATH 只对携带它的对象生效;DT_RPATH 对进程内任意深度的 dlopen 都生效。
图形栈的加载链有三到四层 dlopen(glvnd → vendor → EGL 外部平台模块 → 它自己的依赖),
只有传递性标签能到底。
实测
同样的路径内容,只改标签类型,无环境变量、索引零改动、interposer 零改动:
真机上抽样 73 个已安装可执行文件:1 个 DT_RPATH,68 个 DT_RUNPATH,其中 55 个路径里
已经有 subos farm。唯一那个是 godot —— 这套栈里唯一被观察到图形真能用的程序 ——
因为它的 recipe 自己调
patchelf --force-rpath并写明了原因。那份知识没有任何机制能到达另外 68 个包。
只改可执行文件,这是关键的一半
在库上强制 DT_RPATH 是实测有害的(xim-pkgindex#593):传递性向下也成立,
库的 RPATH 会进入它下方每一次查找,NVIDIA 的 EGL interposer 被这样打标签后
eglInitialize直接失败。PT_INTERP 是判据 —— 而它本来就在被计算(用于决定是否
--set-interpreter),现在算一次两处用,在有 loader 的路径上零成本。
标志经由一个
_rpath_flags()而不是在两个调用点各写一遍 —— 逐点决定正是这件事一开始变成逐包决定的原因。
行为变更(明说)
DT_RPATH 优先级高于
LD_LIBRARY_PATH,DT_RUNPATH 低于它。所以用户不能再用LD_LIBRARY_PATH覆盖这些载荷。实测:subos 里
LD_LIBRARY_PATH根本没被设置,而 recipe 自己的注释把设计目标写成"what makes the stack resolve without anyone setting LD_LIBRARY_PATH" —— 所以今天
没有任何东西依赖它。DT_RPATH 也不传播给子进程,这正是它在这里安全的原因。
已装载荷保持旧标签直到重装;本改动在安装时打标签。
测试
录制式 patchelf 桩按文件名回答
--print-interpreter,驱动真实谓词,断言标志落在可执行文件上而不落在库上。双向证伪后才采信:完全不打标签 → 红;库也打 → 红。
设计:
xlings/.agents/docs/2026-08-10-graphics-stack-design.md§E1a关联:openxlings/xlings#532 #534