Skip to content

Commit 4f4cff0

Browse files
committed
fix(runtime, build): the dlopen surface is walked, and one unwinder per process (2026.9.10.1)
A SYCL project built cleanly and then terminated with exit code 134 and no exception text (#596). The trigger was in the ecosystem -- an adapter's farm was missing one driver library -- but the reasons it presented as a silent abort are two independent gaps in mcpp, and both are repaired here. THE SURFACE NOTHING WALKED. `resolve_runtime_closure` is seeded with the artifact and follows DT_NEEDED. A library a package publishes through `runtime.library_dirs` exists precisely because something will dlopen it, so no link edge names it and it is outside that closure BY CONSTRUCTION. Measured: a farm of twenty-five libraries, two of which could not load at all, while the build reported nothing -- because nothing had asked. `inspect_dlopen_surface` reads each such library's own DT_NEEDED and resolves it against the search path the artifact actually carries, separating three states: resolved, present as a dangling link (the machine has no driver), and absent (a packaging gap). Only the third is reported, and it is a warning: a dangling link is the documented shape of a host driver that is not installed, and failing there would turn a supported configuration into a failed build. `runtime.dlopen_surface` in resolution.json carries the findings and both denominators -- a surface that failed to build enumerates nothing, and "no findings" must not read like "nothing was examined". WHAT "THE SEARCH PATH THE ARTIFACT ACTUALLY CARRIES" TURNED OUT TO MEAN. Three corrections, all of them false positives, none visible until a project with a shared dependency was measured: * `$ORIGIN` leads every artifact's DT_RPATH and a shared dependency is deployed BESIDE the executable. `runtime_search_dirs` cannot carry that -- `$ORIGIN` is a property of each artifact, not of the plan -- so the artifacts' own directories are added in `check_dlopen_surface`. * A plan that produces no program has no surface to judge. The adapter package is `kind = "lib"`; reporting a consumer's surface against an archive's non-existent search path named a library the consumer resolves. * A SONAME is not a filename. mcpp links `bin/libopencl.so` whose SONAME is `libOpenCL.so.1`, and the alias appears later; `mcpp test` calls the check twice and only the second call saw it. The SONAMEs this build produces are read from the objects and passed in. TWO UNWINDERS IN ONE PROCESS. A lane whose device compiler is configured against libstdc++ puts libstdc++ on the link line while the artifact links libc++ statically. `hide_static_cxx_runtime` skipped executables on the premise that "ld exports only what a loaded object references, and mcpp passes no -rdynamic" -- a correct premise with a wrong conclusion, because a loaded libstdc++ DOES reference them. Measured: 89 exported symbols, 68 of them also defined by libstdc++ or libgcc_s. Ten of libgcc's eighteen `_Unwind_*` entry points came from the artifact and eight stayed in libgcc_s, including the accessors libstdc++'s personality routine calls. It read an LLVM libunwind context through libgcc's accessors, recovered a meaningless IP, found no landing pad, and `__cxa_call_terminate` ran past a handler three frames up; the verbose terminate handler's rethrow then terminated as well, which is why nothing was printed. Such a link now takes `--unwindlib=libgcc` and hides the static archives' symbols. libgcc_s is in the process either way -- libstdc++ needs it -- so this names a library rather than adding one, and the C++ runtime stays embedded. A link with no second runtime on it is byte-for-byte unchanged. Measured on one machine, same source, both situations: before exit 134, no output after `sycl: no usable device: ...` / `device unavailable`, exit 1 device `12 24 36 48` in both `-Wl,--exclude-libs` alone was written here as a prediction from the mechanism and then measured: exit 139 instead of 134. Hiding the exports makes libstdc++ bind to libgcc while the artifact's own libc++abi still calls its statically linked libunwind, so the mismatch reproduces in the other direction. The reason is recorded in the design record so the flag is not proposed again. The duplicate-symbol warning now states that consequence when the conflicting set includes the unwinder family, instead of describing it as one more copy that is never called. One documented claim was refuted along the way: the SYCL example states that a missing device image is the one failure its island cannot turn into a return code. Measured by compiling it for sm_90 and running on an sm_89 device -- it was not outside the catches; no catch worked. The example's comment and README are corrected. Design record: .agents/docs/2026-09-09-dlopen-surface-and-two-unwinders.md Ecosystem halves: openxlings/xim-pkgindex#796 #798, mcpplibs/mcpp-index#375 #377 #378
1 parent 07086dd commit 4f4cff0

21 files changed

Lines changed: 1610 additions & 29 deletions

.agents/docs/2026-09-09-dlopen-surface-and-two-unwinders.md

Lines changed: 639 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/usr/bin/env bash
2+
# Ecosystem verification for mcpp#596: the driver farm mirrors the sentinel,
3+
# and mcpp reports a dlopen surface it cannot satisfy. Against a PUBLISHED
4+
# mcpp, a PUBLISHED xim:libcuda-host-link and a PUBLISHED compat:sycl-runtime.
5+
#
6+
# # The sandbox has an EMPTY $HOME and a fresh /tmp, so this file is not
7+
# # visible from inside it. Pass the script itself in:
8+
# B64=$(base64 -w0 <this file>)
9+
# xlings subos use verify-596 --sandbox --cmd \
10+
# "echo $B64 | base64 -d > /tmp/v.sh && MCPP_VERIFY_VERSION=2026.9.10.1 bash /tmp/v.sh"
11+
#
12+
# mcpp is addressed by its STORE path, which is the one thing the sandbox does
13+
# share: the xlings data directory. A bare `mcpp` is not on PATH in there.
14+
#
15+
# WHAT A SANDBOX CAN AND CANNOT DECIDE HERE.
16+
#
17+
# It decides everything about the PACKAGING, which is where the defect was: how
18+
# many driver sonames the sentinel publishes, and whether the farm carries all
19+
# of them. Those are properties of what gets installed, and a machine that has
20+
# built this before answers them from a directory that already existed.
21+
#
22+
# It cannot decide anything about a DEVICE. The sandbox's /dev has fourteen
23+
# entries and no NVIDIA node, so `libcuda.so.1` resolves to a dangling link
24+
# there exactly as it does on any machine without a driver -- which is a
25+
# supported configuration and is asserted as such below, not worked around. The
26+
# device side is measured on the host and recorded in the design record.
27+
#
28+
# EVERY CRITERION NAMES THE OBJECT IT SELECTED, and every section that did not
29+
# run is listed again in the summary. "0 assertions failed" printed by a script
30+
# that skipped three sections is the failure mode this shape exists to prevent.
31+
set -u
32+
33+
VER="${MCPP_VERIFY_VERSION:?set MCPP_VERIFY_VERSION}"
34+
STORE="${MCPP_VERIFY_BIN:-$HOME/.xlings/data/xpkgs/xim-x-mcpp/$VER/bin/mcpp}"
35+
SENTINEL_VER="${MCPP_VERIFY_SENTINEL:-0.0.2}"
36+
COMPAT_VER="${MCPP_VERIFY_COMPAT:-2026.09.10}"
37+
38+
fails=0
39+
skipped=""
40+
fail() { printf 'ASSERT-FAIL: %s\n' "$1"; fails=$((fails + 1)); }
41+
ok() { printf 'ok: %s\n' "$1"; }
42+
section() { printf '\n== %s ==\n' "$1"; }
43+
skip() { printf 'NOT RUN: %s\n' "$1"; skipped="$skipped
44+
- $1"; }
45+
46+
work=$(mktemp -d)
47+
trap 'rm -rf "$work"' EXIT
48+
49+
section "A. the published mcpp answers for itself"
50+
if [ ! -x "$STORE" ]; then
51+
fail "no mcpp at $STORE"
52+
printf '\nfails=%d (nothing else can run)\n' "$fails"
53+
exit 1
54+
fi
55+
got=$("$STORE" --version 2>&1 | head -1)
56+
case "$got" in
57+
*"$VER"*) ok "mcpp --version says $got" ;;
58+
*) fail "mcpp --version says '$got', expected $VER" ;;
59+
esac
60+
61+
# ---------------------------------------------------------------------------
62+
section "B. the sentinel publishes a SET, and the size is the assertion"
63+
#
64+
# The count is the criterion rather than the presence of one name: a list that
65+
# lost an entry passes every per-name test that only asks about the names it
66+
# still has.
67+
if "$STORE" self >/dev/null 2>&1 || true; then :; fi
68+
xl="$HOME/.xlings/data/xpkgs/xim-x-libcuda-host-link/$SENTINEL_VER/lib"
69+
if command -v xlings >/dev/null 2>&1; then
70+
xlings install "libcuda-host-link@$SENTINEL_VER" -y >"$work/sentinel.log" 2>&1 || true
71+
fi
72+
if [ -d "$xl" ]; then
73+
n=$(ls -1 "$xl" | wc -l)
74+
[ "$n" -ge 2 ] && ok "sentinel $SENTINEL_VER publishes $n sonames" \
75+
|| fail "sentinel publishes $n soname(s); the set is at least 2"
76+
for s in libcuda.so.1 libnvidia-ml.so.1; do
77+
# islink, not exists: a dangling link is the documented self-heal shape
78+
# on a machine with no driver, and the sandbox is such a machine.
79+
[ -L "$xl/$s" ] && ok "sentinel carries $s" || fail "sentinel has no $s"
80+
done
81+
else
82+
skip "B: the sentinel is not installed in this environment ($xl)"
83+
fi
84+
85+
# ---------------------------------------------------------------------------
86+
section "C. the farm mirrors the sentinel"
87+
#
88+
# THE DEFECT ITSELF. The farm linked one hand-written name while enumerating
89+
# every other directory it draws from, so it carried libcuda.so.1 and not
90+
# libnvidia-ml.so.1, the CUDA adapter did not load, and the program aborted
91+
# with no message.
92+
cat >"$work/mcpp.toml" <<TOML
93+
[package]
94+
name = "farm-probe"
95+
version = "0.1.0"
96+
97+
[dependencies.compat]
98+
sycl-runtime = "$COMPAT_VER"
99+
TOML
100+
mkdir -p "$work/src"
101+
cat >"$work/src/main.cpp" <<'CPP'
102+
int main() { return 0; }
103+
CPP
104+
if (cd "$work" && "$STORE" build >"$work/build.log" 2>&1); then
105+
# Searched under the REGISTRY roots rather than all of $HOME: a developer
106+
# home holds tens of gigabytes of packages and the walk costs minutes,
107+
# which reads exactly like a hung verification.
108+
farm=""
109+
for root in "${MCPP_HOME:-$HOME/.mcpp}" "$work/.mcpp" "$HOME/.xlings"; do
110+
[ -d "$root" ] || continue
111+
farm=$(find "$root" -path "*compat-x-sycl-runtime/$COMPAT_VER*/sycl_runtime/lib" \
112+
-type d 2>/dev/null | head -1)
113+
[ -n "$farm" ] && break
114+
done
115+
if [ -n "$farm" ]; then
116+
for s in libcuda.so.1 libnvidia-ml.so.1; do
117+
[ -L "$farm/$s" ] && ok "farm carries $s" \
118+
|| fail "farm has no $s -- this is mcpp#596"
119+
done
120+
# The denominator: a farm that failed to build is empty, and every
121+
# per-name test above would then have failed for the wrong reason.
122+
n=$(ls -1 "$farm" | wc -l)
123+
[ "$n" -ge 20 ] && ok "farm has $n entries" \
124+
|| fail "farm has only $n entries; the payload half did not build"
125+
else
126+
fail "C: no farm directory for compat:sycl-runtime@$COMPAT_VER"
127+
fi
128+
else
129+
skip "C: the probe project did not build (see $work/build.log; the dpcpp payload is over a gigabyte)"
130+
fi
131+
132+
# ---------------------------------------------------------------------------
133+
section "D. mcpp reports a dlopen surface it cannot satisfy"
134+
#
135+
# The record rather than the message: a test that greps a warning's wording
136+
# fails the next time the wording improves. Both denominators are asserted for
137+
# the reason they exist -- "no findings" and "nothing was examined" must not
138+
# read the same.
139+
res=$(find "$work" -name resolution.json 2>/dev/null | head -1)
140+
if [ -n "$res" ] && command -v python3 >/dev/null 2>&1; then
141+
python3 - "$res" <<'PY'
142+
import json, sys
143+
doc = json.load(open(sys.argv[1]))
144+
rec = doc.get("runtime", {}).get("dlopen_surface")
145+
if rec is None:
146+
print("ASSERT-FAIL: resolution.json has no runtime.dlopen_surface")
147+
sys.exit(1)
148+
members, walked = rec.get("members", 0), rec.get("walked", 0)
149+
if members <= 0:
150+
print(f"ASSERT-FAIL: dlopen_surface examined {members} members")
151+
sys.exit(1)
152+
print(f"ok: dlopen_surface examined {walked} of {members} members")
153+
missing = [f for f in rec.get("findings", []) if f.get("kind") == "missing"]
154+
for f in missing:
155+
print(f"note: {f['library']} needs {f['soname']} (declared unserved: libOpenCL.so.1)")
156+
bad = [f for f in missing if f.get("soname") == "libnvidia-ml.so.1"]
157+
if bad:
158+
print("ASSERT-FAIL: NVML is still missing from the farm -- mcpp#596")
159+
sys.exit(1)
160+
print("ok: no driver soname is missing from the farm")
161+
PY
162+
[ $? -eq 0 ] || fails=$((fails + 1))
163+
else
164+
skip "D: no resolution.json (section C did not build) or no python3"
165+
fi
166+
167+
# ---------------------------------------------------------------------------
168+
printf '\n== summary ==\n'
169+
printf 'assertions failed: %d\n' "$fails"
170+
if [ -n "$skipped" ]; then
171+
printf 'sections NOT RUN:%s\n' "$skipped"
172+
printf 'A pass with sections not run is not a pass for those sections.\n'
173+
fi
174+
exit $((fails > 0))

.agents/docs/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
1818
---
1919
```
2020

21-
273 records.
21+
274 records.
2222

2323
## By subject
2424

@@ -32,6 +32,7 @@ Records that declare one. Everything else is listed by date below.
3232

3333
### heterogeneous
3434

35+
- [A dlopen surface no closure walks, and a process with two unwinders](2026-09-09-dlopen-surface-and-two-unwinders.md) — active
3536
- [The island boundary's names: one rule for both lanes, and the check that makes it true](2026-09-08-island-boundary-names.md) — active
3637
- [Implementation plan: the island boundary's names](2026-09-08-island-boundary-names-implementation-plan.md) — active
3738

@@ -44,6 +45,7 @@ Records that declare one. Everything else is listed by date below.
4445
### 2026-09
4546

4647
- [Two answers and two silences: the scanner's second grammar, and the manifest keys nothing reads](2026-09-09-two-answers-and-two-silences.md) — active
48+
- [A dlopen surface no closure walks, and a process with two unwinders](2026-09-09-dlopen-surface-and-two-unwinders.md) — active
4749
- [The documentation as a book: a chapter-by-chapter design](2026-09-08-the-documentation-as-a-book.md) — active
4850
- [The island boundary's names: one rule for both lanes, and the check that makes it true](2026-09-08-island-boundary-names.md) — active
4951
- [Implementation plan: the island boundary's names](2026-09-08-island-boundary-names-implementation-plan.md) — active

CHANGELOG.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,62 @@
55
66
## [Unreleased]
77

8+
## [2026.9.10.1] - 2026-09-10
9+
10+
三条改动来自同一次排查(#596):一个 SYCL 工程构建全绿、运行时以退出码 134 终止且
11+
不打印任何异常文本。触发原因在生态侧(适配包的 farm 少了一个驱动库),但**它之所以
12+
以最难排查的形态出现,原因在 mcpp 这一侧**,而且有两条独立的缺口。设计记录见
13+
`.agents/docs/2026-09-09-dlopen-surface-and-two-unwinders.md`
14+
15+
### 依赖为 dlopen 发布的那个面,现在会被走一遍
16+
17+
运行期闭包检查从产物出发沿 `DT_NEEDED` 走。一个包通过 `runtime.library_dirs` 发布的
18+
库之所以存在,正是因为有东西要 `dlopen` 它 —— 没有任何链接边指向它,所以它**按构造**
19+
在那条闭包之外,不是被漏掉。实测:一个 25 个成员的 farm 里有两个根本加载不了,而构建
20+
零诊断。
21+
22+
`mcpp build` 之后会单独走这个面,把每个成员自己的 `DT_NEEDED` 按产物真实的搜索路径
23+
解析一遍,并区分三种读数:解析到(静默)、farm 里存在但链接悬空(机器没装驱动,静默)、
24+
到处都不存在(打包缺口,警告)。中间那一行是这条检查是**警告而不是错误**的原因。
25+
26+
完整结果发布在 `resolution.json``runtime.dlopen_surface`,含两个分母
27+
(`members` / `walked`)—— 一个构建失败的 farm 枚举出零个成员,否则「没有发现」与
28+
「什么都没检查」读起来一模一样。
29+
30+
判据要落在**产物真实的搜索路径**上,这一点花了三次修正才对(全部是假阳性,且都要
31+
先有一个带共享依赖的工程才看得见):产物 `DT_RPATH` 首位的 `$ORIGIN` 不在
32+
`runtime_search_dirs` 里,而共享依赖就部署在可执行文件旁边;不产出程序的计划没有
33+
可判的面(适配包自己是 `kind = "lib"`);以及 **SONAME 不是文件名** —— mcpp 链出的是
34+
`bin/libopencl.so` 而它的 SONAME 是 `libOpenCL.so.1`,别名稍后才出现。
35+
36+
### 链接行上出现第二个 C++ 运行时时,进程只保留一个 unwinder
37+
38+
设备编译器按 libstdc++ 配置的 lane(SYCL、HIP)会把 libstdc++ 放上链接行,而产物
39+
自己静态链接 libc++。此前的注释断言「可执行文件的静态运行时已经是 local 的」——
40+
前提对(链接器只导出被加载对象引用到的符号),结论错:**当被加载的对象确实引用它们时,
41+
链接器就把它们放进 `.dynsym`**。实测 89 个导出符号,其中 68 个同时由 libstdc++ 或
42+
libgcc_s 定义。
43+
44+
静态归档只贡献被引用到的成员,所以这种抢占按构造是部分的:libgcc 的 18 个 `_Unwind_*`
45+
入口点里 10 个来自产物、8 个仍在 libgcc_s(含 personality 例程要用的访问器)。
46+
于是一次抛出被两个 unwinder 分着处理,`__gxx_personality_v0` 越过三帧之上一个本应命中
47+
的 handler 调用 `__cxa_call_terminate`,而 `__verbose_terminate_handler` 为了打印
48+
异常类型的那次 rethrow 又终止一次 —— 于是一个字都没印出来。
49+
50+
现在这类链接改用 `--unwindlib=libgcc` 并用 `--exclude-libs` 挡住静态归档的导出。
51+
libgcc_s 本来就在进程里(libstdc++ 需要它),所以这一步只点名一个已有的库。
52+
链接行上没有第二个运行时的构建一字节不变。
53+
54+
实测(同一台机器、同一份源码、两种情形):修改前退出码 134 且无输出;修改后
55+
`sycl: no usable device: ...` / `device unavailable`,退出码 1;设备可用时两者都是
56+
`12 24 36 48`**顺带推翻了示例里一条写下来的结论** —— 设备镜像不匹配的那次抛出
57+
并非拦不住,它拦不住只是因为 unwinder 是坏的。
58+
59+
### 重复符号警告会说出 unwinder 的真实后果
60+
61+
该警告此前把后果一律描述为「库自己的那份副本不会被调用」。对 `_Unwind_*` 这一族,
62+
真实后果是异常处理整体失效。重复集合里含该族时,警告会单独说明这一点。
63+
864
## [2026.9.9.1] - 2026-09-09
965

1066
本次修复的四条缺陷,来源是同一类问题:一个问题被回答了两次,而读答案的地方各读各的;

docs/33-authoring-an-adapter.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,44 @@ is then empty and the program reports what it actually found. An adapter that
8282
errors on a missing host library turns a supported configuration into a build
8383
failure.
8484

85+
## The check mcpp runs on this surface
86+
87+
The libraries an adapter publishes are reached by `dlopen`, so no link edge
88+
names them and the runtime closure check — which walks `DT_NEEDED` from the
89+
artifact — cannot arrive at them. mcpp walks them separately, after the link,
90+
and reports what it finds as a warning:
91+
92+
```
93+
warning: 1 of 13 libraries a dependency published for dlopen cannot be loaded
94+
on this artifact's search path:
95+
libur_adapter_cuda.so.0 needs libnvidia-ml.so.1
96+
```
97+
98+
Three states are separated, and only one of them is reported:
99+
100+
| The library needs a SONAME that is | Meaning | Reported |
101+
|---|---|---|
102+
| on the artifact's search path | nothing to say | no |
103+
| present in the farm as a dangling link | the machine has no such driver | no |
104+
| absent everywhere | the adapter did not carry it | yes |
105+
106+
The middle row is why this is a warning and not an error: a dangling link is
107+
the documented shape of a host driver that is not installed, and a check that
108+
failed there would turn a supported configuration into a build failure.
109+
110+
The full result, including both denominators, is published as
111+
`runtime.dlopen_surface` in `resolution.json`:
112+
113+
```json
114+
{ "members": 13, "walked": 13,
115+
"findings": [ { "library": "libur_adapter_cuda.so.0", "dir": "...",
116+
"soname": "libnvidia-ml.so.1", "kind": "missing" } ] }
117+
```
118+
119+
`members` and `walked` are published even when nothing is reported. A farm that
120+
failed to build enumerates nothing, and "no findings" would otherwise be
121+
indistinguishable from "nothing was examined".
122+
85123
## Current limitations
86124

87125
- **Linux only, by construction.** macOS's dyld and the Windows PE loader have

docs/42-heterogeneous-builds.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,29 @@ Which shape to choose is a property of the program, not of mcpp: a seam that
676676
swaps an implementation wants link-time selection, and a program that ships to
677677
machines it has not seen wants run-time selection.
678678

679+
## One unwinder, when the link carries a second C++ runtime
680+
681+
A lane whose device compiler is configured against libstdc++ puts libstdc++ on
682+
the link line while the artifact links libc++ statically. Both are then in the
683+
image, and mcpp's duplicate-symbol check reports what they share.
684+
685+
For most of those symbols the consequence is that one implementation is called
686+
instead of an interchangeable other. For the unwinder it is not. A static
687+
archive contributes only the members something references, so the interposition
688+
is partial by construction: measured on the SYCL lane, ten of libgcc's eighteen
689+
`_Unwind_*` entry points came from the artifact and eight stayed in libgcc_s,
690+
including the accessors a personality routine uses. libstdc++'s personality
691+
then read an LLVM libunwind context through libgcc's accessors, found no
692+
landing pad, and called `std::terminate` past a handler three frames up. The
693+
program was correct until something threw.
694+
695+
So when the link line names libstdc++ and the toolchain's own library is
696+
libc++, mcpp links the unwinder from libgcc (`--unwindlib=libgcc`) instead of
697+
the payload's `libunwind.a`, and hides the static archives' symbols with
698+
`--exclude-libs`. libgcc_s is already in the process — libstdc++ needs it — so
699+
this names a library rather than adding one, and the C++ runtime stays
700+
embedded. A build with no second runtime on its line is unchanged.
701+
679702
## Two boundaries worth stating
680703

681704
**`--accel` and `--no-accel` are `build`, `run` and `test` options** (run and

docs/zh/33-authoring-an-adapter.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,40 @@ rpath : …/xim-x-glibc/2.39/lib64:…/xim-x-gcc/…/lib64:$ORIGIN
6767
每一台 CI runner 都是这样的机器。此时农场为空,程序报告它实际找到了什么。一个在宿主库
6868
缺失时报错的适配包,会把一种受支持的配置变成构建失败。
6969

70+
## mcpp 对这个面的检查与报告
71+
72+
适配包发布的库是被 `dlopen` 找到的,没有任何链接边指向它们,因此运行期闭包检查
73+
—— 它从产物出发沿 `DT_NEEDED` 走 —— 按构造到不了这些库。mcpp 在链接之后单独走一遍
74+
这个面,并把结果作为警告报出:
75+
76+
```
77+
warning: 1 of 13 libraries a dependency published for dlopen cannot be loaded
78+
on this artifact's search path:
79+
libur_adapter_cuda.so.0 needs libnvidia-ml.so.1
80+
```
81+
82+
三种读数被区分开,只有一种被报告:
83+
84+
| 该库需要的 SONAME | 含义 | 是否报告 |
85+
|---|---|---|
86+
| 在产物的搜索路径上 | 无话可说 ||
87+
| 在农场里存在,但链接悬空 | 这台机器没有这个驱动 ||
88+
| 到处都不存在 | 适配包没有携带它 ||
89+
90+
中间那一行正是这条检查是警告而不是错误的原因:悬空链接是「宿主驱动尚未安装」的
91+
既定形状,一条在那里失败的检查会把受支持的配置变成构建失败。
92+
93+
完整结果(含两个分母)发布在 `resolution.json``runtime.dlopen_surface`:
94+
95+
```json
96+
{ "members": 13, "walked": 13,
97+
"findings": [ { "library": "libur_adapter_cuda.so.0", "dir": "...",
98+
"soname": "libnvidia-ml.so.1", "kind": "missing" } ] }
99+
```
100+
101+
即使没有任何发现,`members``walked` 也会被发布。一个构建失败的农场枚举出零个成员,
102+
否则「没有发现」与「什么都没检查」就读起来一模一样。
103+
70104
## 当前边界
71105

72106
- **按构造只适用于 Linux。** macOS 的 dyld 与 Windows 的 PE 加载器没有对应的这一层,

0 commit comments

Comments
 (0)