Skip to content

Commit 81e0795

Browse files
committed
fix(runtime): 「可证」要真的可证 —— 三处收窄,两处由 CI 抓到
上一 commit 把 hermetic 下解析不到的 DT_NEEDED 判为可证失败。方向对,范围过宽。 ① 产物的格式由产物决定,不由 binding 决定。 Linux→Windows 交叉构建用的是宿主 binding(Linux/glibc/私有加载器 ⇒ hermetic), 产物却是 PE。「不是 ELF」这条解析错误落进 unresolved,于是 crosswin.exe 被判成 「缺一个共享库」而构建失败 —— 一个连 DT_NEEDED 都没有的文件。 validate_runtime_artifact 现在先看 resolution.artifactIsElf。 ② unresolved 混装了三种东西,只有一种可证。 「找不到的 SONAME」「读不了的对象」「512 对象上限」此前同住一个 vector。后两种是 关于检查本身的陈述 —— 没能看的检查什么都没证明。拆出 unresolvedSonames,升级只看它。 ③ allow_host_libs 必须同时退出两个阶段,否则它意味着两件事。 它本就关掉链接期 hermeticity 检查。既然用户已声明「我有意伸到沙箱外」,mcpp 就不能 再断言产物起不来 —— 他们可能用 LD_LIBRARY_PATH 跑,或装在私有加载器确实会看的地方。 该档下未解析的 NEEDED 报 inconclusive 并指名,不阻断。谓词与 hermetic.cppm 逐字同源 (manifest 键 OR 环境变量),否则「链接放行、闭包判死」是两者中最坏的组合。 顺带把 e2e 206 的一条假绿改成真话。它的「安全宿主 DSO 对照」断言 status==pass, 而 LD_DEBUG=libs 实测:私有加载器的内建默认路径是 glibc 载荷自己的构建期前缀 (…/fromsource-x-glibc/2.39/lib),本机根本不存在,/usr/lib 从不被查,产物 exit 127。 它此前报 pass 只是因为闭包模型回落到宿主目录 —— 与那个被当成 pass 的 GL 程序同一形状。 而这份文件自己的注释「execution is not part of this link-physics control」正是让这条 假绿站住的理由:从来没人运行过它。改为断言 inconclusive + 诊断里指名 libtinfo 与 allow_host_libs。 219 的断言也放松到正确形状:farm 必须是最后一个「绝对路径」条目,而不是字面最后一个。 $ORIGIN 相对条目是另一类东西(它随产物走,不指本机),真实 GLFW 工程的 DT_RPATH 就以 … : <subos>/lib : $ORIGIN 结尾。
1 parent cf38941 commit 81e0795

8 files changed

Lines changed: 287 additions & 26 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@
4747
现在宿主默认目录只在**非 hermetic** binding 下参与;hermetic 产物上一个谁都提供
4848
不了的 `DT_NEEDED`**可证的**失败(新判决 `unresolvable`),会让构建变红并指名。
4949

50+
实测佐证(`LD_DEBUG=libs`):私有加载器的内建默认路径是 glibc 载荷**自己的构建期
51+
前缀**(`…/fromsource-x-glibc/2.39/lib`),这台机器上根本不存在;`/usr/lib` **从不**
52+
被查。因此 e2e `206` 里那条「安全的宿主 DSO 对照」此前报 `pass` 也是假绿 —— 它
53+
刻意不运行产物,而产物其实 127。已改为断言 `inconclusive` 并说明原因。
54+
55+
**`[build] allow_host_libs` 同时退出两个阶段。** 它本就关掉链接期 hermeticity 检查;
56+
既然用户已声明「我有意伸到沙箱外」,mcpp 就不能再断言产物起不来(他们可能用
57+
`LD_LIBRARY_PATH` 跑,或装在私有加载器会看的地方)。⇒ 该档下未解析的 `NEEDED`
58+
`inconclusive` 并指名,而不是变红。一条声明,一个含义。
59+
60+
另两处精度修正(CI 抓到的):`unresolved` 此前混装三种东西 ——「找不到的 SONAME」
61+
「读不了的对象」「512 上限」。只有第一种可证,故拆出 `unresolvedSonames`;
62+
**产物的格式由产物决定,不由 binding 决定** —— Linux→Windows 交叉构建拿的是宿主
63+
binding(hermetic),产物却是 PE,「不是 ELF」落进 `unresolved` 后被判成「缺库」,
64+
`crosswin.exe` 构建失败。
65+
5066
### 变更
5167

5268
- xlings 强相关模块归入 `src/platform/xlings/`:`mcpp.platform.xlings`

docs/08-toolchain-internals.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,24 @@ so reported "resolved" for binaries that exited 127.
225225
The closure is recorded in `resolution.json` under `runtime.search.closure` and
226226
printed by `mcpp why runtime`. A `DT_NEEDED` that nothing on a hermetic
227227
artifact's path can satisfy is a **proven** failure (`unresolvable`), not an
228-
inconclusive one, and it fails the build.
228+
inconclusive one, and it fails the build. Measured with `LD_DEBUG=libs`: the
229+
private loader's built-in default path is the glibc payload's own *build-time*
230+
prefix, a directory that does not exist on the machine — `/usr/lib` is never
231+
consulted.
232+
233+
Three things narrow that proof, and each of them is a case where mcpp knows less
234+
than the wording suggests:
235+
236+
- **The artifact's format decides, not the binding's.** A cross build runs with
237+
this host's binding while producing a PE or Mach-O. ELF rules do not apply to
238+
the artifact whatever the binding says.
239+
- **Only an unfindable SONAME proves anything.** "I could not read this object"
240+
and "I stopped after 512 objects" are statements about the *check*; a check
241+
that could not look has proven nothing, so those stay inconclusive.
242+
- **`[build] allow_host_libs` opts out of both phases.** It already switches off
243+
the link-time hermeticity check; once resolution is the user's responsibility
244+
mcpp reports rather than blocks, because they may run under `LD_LIBRARY_PATH`
245+
or on a machine where the library sits where the private loader looks.
229246

230247
mcpp also declares `XLINGS_SUBOS_LD_PATHS=0` for every process it spawns. That
231248
is the opt-out from xlings' linker-wrapper path injection

docs/zh/08-toolchain-internals.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,19 @@ farm 在前的话,一次 `xlings install` 就能在事后悄悄换掉一个**已
185185

186186
闭包记录在 `resolution.json``runtime.search.closure`,并由 `mcpp why runtime`
187187
打印。hermetic 产物上一个谁都提供不了的 `DT_NEEDED`**可证的**失败
188-
(`unresolvable`),不是「没查过」,它会让构建变红。
188+
(`unresolvable`),不是「没查过」,它会让构建变红。`LD_DEBUG=libs` 实测:私有加载器的
189+
内建默认路径是 glibc 载荷**自己的构建期前缀**,该目录在本机并不存在 —— `/usr/lib`
190+
从不被查。
191+
192+
有三件事收窄这个「可证」,每一件都是 mcpp 知道的比措辞暗示的少:
193+
194+
- **产物的格式由产物决定,不由 binding 决定。** 交叉构建用的是本宿主的 binding,
195+
产物却是 PE / Mach-O;无论 binding 怎么说,ELF 规则都不适用于它。
196+
- **只有「找不到的 SONAME」能证明什么。**「这个对象读不了」「我在 512 个之后停了」
197+
都是关于**检查**的陈述;没能看的检查什么都没证明,故仍报 inconclusive。
198+
- **`[build] allow_host_libs` 同时退出两个阶段。** 它本就关掉链接期 hermeticity 检查;
199+
既然解析责任已归用户,mcpp 就只报告不阻断 —— 他们可能用 `LD_LIBRARY_PATH` 跑,
200+
或装在私有加载器确实会看的地方。
189201

190202
mcpp 还会给它启动的每个进程声明 `XLINGS_SUBOS_LD_PATHS=0` —— 这是 xlings 链接器
191203
包装器路径注入的退出声明(openxlings/xlings#540):mcpp 要它的

src/build/runtime_validation.cppm

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,18 @@ std::string status_name(mcpp::platform::elf::RuntimeVerdict::Status status) {
159159
return "inconclusive";
160160
}
161161

162+
// Did this build declare that it reaches outside the sandbox?
163+
//
164+
// Spelled here exactly as `mcpp.build.hermetic` spells it — manifest key OR
165+
// environment variable — because the two checks must agree. A build whose link
166+
// was allowed to resolve host libraries and whose closure was then judged as if
167+
// it had not is the worst of both: it links, and mcpp calls it broken.
168+
bool host_libs_allowed(const mcpp::build::BuildPlan& plan) {
169+
if (plan.manifest.buildConfig.allowHostLibs) return true;
170+
const char* e = std::getenv("MCPP_ALLOW_HOST_LIBS");
171+
return e && *e && *e != '0';
172+
}
173+
162174
// How bad each state is, for rolling many artifacts into one summary.
163175
// `Unresolvable` sits above `Inconclusive` (it is proven, not unknown) and
164176
// below `ProvenMismatch` (mixing payloads is the more fundamental error, and
@@ -406,8 +418,12 @@ ValidationReport validate_changed_artifacts(
406418
validated.artifact = artifact;
407419
auto resolution = mcpp::platform::elf::resolve_runtime_closure(
408420
artifact, plan.runtimeBinding, searchDirs);
421+
// The SAME opt-out the link-time hermeticity check honours, read the
422+
// same way (manifest key or environment). A build that declared it is
423+
// reaching outside the sandbox on purpose has taken responsibility for
424+
// run-time resolution, so mcpp reports rather than blocks.
409425
validated.verdict = mcpp::platform::elf::validate_runtime_artifact(
410-
artifact, plan.runtimeBinding, resolution);
426+
artifact, plan.runtimeBinding, resolution, host_libs_allowed(plan));
411427
store_artifact(doc, key, fp, validated);
412428
changedCache = true;
413429
report.artifacts.push_back(std::move(validated));

src/platform/elf_runtime.cppm

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,27 @@ struct RuntimeResolution {
6868
ElfRuntimeFacts artifact;
6969
std::vector<ElfRuntimeFacts> objects;
7070
std::vector<std::filesystem::path> resolvedLibcs;
71+
72+
// Everything that stopped the walk, as human-readable text. A mixed bag on
73+
// purpose: an object that could not be parsed, a closure that hit the size
74+
// cap, and a SONAME nothing provides all belong in the report.
7175
std::vector<std::string> unresolved;
76+
77+
// The strict subset that means "a DT_NEEDED nothing on the search path
78+
// provides". SEPARATE because only this one is PROVABLE.
79+
//
80+
// `unresolved` also collects "I could not read this file" and "I stopped
81+
// after 512 objects", which are statements about the CHECK, not about the
82+
// artifact. Treating the whole bag as proof made a cross-built PE fail its
83+
// build: `crosswin.exe` is not ELF, that fact landed in `unresolved`, and a
84+
// "you are missing a library" verdict was issued for a file with no
85+
// DT_NEEDED at all. Caught by CI, not by reading.
86+
std::vector<std::string> unresolvedSonames;
87+
88+
// Did the artifact itself parse as ELF? False ⇒ the ELF rules do not apply
89+
// to it, whatever the binding says. The binding describes the HOST; a cross
90+
// build's artifact is a different format entirely.
91+
bool artifactIsElf = false;
7292
};
7393

7494
struct RuntimeVerdict {
@@ -113,10 +133,19 @@ RuntimeResolution resolve_runtime_closure(
113133
const mcpp::platform::runtime::RuntimeBinding& binding,
114134
std::span<const std::filesystem::path> additionalSearchDirs = {});
115135

136+
// `hostLibsAllowed` mirrors `[build] allow_host_libs` (and
137+
// `MCPP_ALLOW_HOST_LIBS`). It is the user's explicit statement that this build
138+
// reaches outside the sandbox on purpose, and it already switches off the
139+
// link-time hermeticity check. It has to switch off the RUN-time proof for the
140+
// same reason: once resolution is the user's responsibility, mcpp can no longer
141+
// claim the artifact is unstartable — they may run it under LD_LIBRARY_PATH, or
142+
// on a machine where the library is installed where the private loader looks.
143+
// One declaration, one meaning, both phases.
116144
RuntimeVerdict validate_runtime_artifact(
117145
const std::filesystem::path& artifact,
118146
const mcpp::platform::runtime::RuntimeBinding& binding,
119-
const RuntimeResolution& resolution);
147+
const RuntimeResolution& resolution,
148+
bool hostLibsAllowed = false);
120149

121150
} // namespace mcpp::platform::elf
122151

@@ -579,6 +608,7 @@ RuntimeResolution resolve_runtime_closure(
579608
return resolution;
580609
}
581610
resolution.artifact = std::move(*root);
611+
resolution.artifactIsElf = true;
582612

583613
std::deque<ElfRuntimeFacts> queue;
584614
queue.push_back(resolution.artifact);
@@ -606,6 +636,7 @@ RuntimeResolution resolve_runtime_closure(
606636
soname, requester, binding, additionalSearchDirs);
607637
if (!path) {
608638
resolution.unresolved.push_back(soname);
639+
resolution.unresolvedSonames.push_back(soname);
609640
continue;
610641
}
611642
loadedBySoname.emplace(soname, *path);
@@ -644,6 +675,7 @@ RuntimeResolution resolve_runtime_closure(
644675
if (!queue.empty())
645676
resolution.unresolved.push_back("runtime closure exceeds 512 ELF objects");
646677
detail::sort_unique(resolution.unresolved);
678+
detail::sort_unique(resolution.unresolvedSonames);
647679
std::sort(resolution.artifact.resolvedObjects.begin(),
648680
resolution.artifact.resolvedObjects.end());
649681
resolution.artifact.resolvedObjects.erase(
@@ -656,7 +688,8 @@ RuntimeResolution resolve_runtime_closure(
656688
RuntimeVerdict validate_runtime_artifact(
657689
const std::filesystem::path& artifact,
658690
const mcpp::platform::runtime::RuntimeBinding& binding,
659-
const RuntimeResolution& resolution) {
691+
const RuntimeResolution& resolution,
692+
bool hostLibsAllowed) {
660693
RuntimeVerdict verdict;
661694
const bool isGlibc = binding.runtimeId.starts_with("glibc@");
662695
if constexpr (!mcpp::platform::is_linux) {
@@ -689,6 +722,22 @@ RuntimeVerdict validate_runtime_artifact(
689722

690723
const auto artifactPath = detail::canonical_text(artifact);
691724
const auto& facts = resolution.artifact;
725+
726+
// THE ARTIFACT'S FORMAT DECIDES, NOT THE BINDING'S.
727+
//
728+
// The binding describes this HOST — Linux, glibc, a private loader. A cross
729+
// build's artifact is a different format entirely, and ELF rules say
730+
// nothing about it. Without this, a Linux→Windows cross build reached the
731+
// ELF validator with `crosswin.exe`, the "not an ELF file" parse error sat
732+
// in `unresolved`, and the build was failed for a missing library on a file
733+
// that has no DT_NEEDED at all.
734+
if (!resolution.artifactIsElf) {
735+
verdict.diagnostics.push_back(std::format(
736+
"runtime physics: {} is not ELF; ELF/glibc rules are not applicable",
737+
artifactPath));
738+
return verdict;
739+
}
740+
692741
// ET_REL and static ET_EXEC/ET_DYN files carry no dynamic closure.
693742
if (facts.interp.empty() && facts.needed.empty() && resolution.unresolved.empty())
694743
return verdict;
@@ -807,11 +856,14 @@ RuntimeVerdict validate_runtime_artifact(
807856

808857
if (verdict.status != RuntimeVerdict::Status::ProvenMismatch
809858
&& !resolution.unresolved.empty()) {
810-
std::string names;
811-
for (auto const& name : resolution.unresolved) {
812-
if (!names.empty()) names += ", ";
813-
names += name;
814-
}
859+
auto join = [](std::span<const std::string> values) {
860+
std::string out;
861+
for (auto const& value : values) {
862+
if (!out.empty()) out += ", ";
863+
out += value;
864+
}
865+
return out;
866+
};
815867
// PROVEN under a hermetic binding, merely UNKNOWN otherwise.
816868
//
817869
// Hermetic means the artifact's PT_INTERP is a private loader whose
@@ -825,7 +877,12 @@ RuntimeVerdict validate_runtime_artifact(
825877
// A non-hermetic artifact runs under the host loader, which also
826878
// consults `ld.so.cache` — something mcpp deliberately does not parse.
827879
// There, unresolved really is unknown.
828-
if (binding.hermetic()) {
880+
//
881+
// And it must be an unfindable SONAME, not merely "something stopped
882+
// the walk": an unreadable object or the 512-object cap are statements
883+
// about the CHECK, and a check that could not look has proven nothing.
884+
if (binding.hermetic() && !resolution.unresolvedSonames.empty()
885+
&& !hostLibsAllowed) {
829886
verdict.status = RuntimeVerdict::Status::Unresolvable;
830887
verdict.diagnostics.push_back(std::format(
831888
"runtime closure for {} cannot be satisfied: {} not found on the "
@@ -836,11 +893,21 @@ RuntimeVerdict validate_runtime_artifact(
836893
" Fix: install the provider into the selected SubOS "
837894
"(`xlings install <pkg>`), or declare the dependency so mcpp "
838895
"resolves it.",
839-
artifactPath, names));
896+
artifactPath, join(resolution.unresolvedSonames)));
897+
} else if (hostLibsAllowed && !resolution.unresolvedSonames.empty()) {
898+
inconclusive(std::format(
899+
"runtime closure for {} is inconclusive: {} is not on the search "
900+
"path this artifact will use, but [build] allow_host_libs is set "
901+
"— resolution at run time is yours to arrange (e.g. "
902+
"LD_LIBRARY_PATH, or installing it where the private loader "
903+
"looks).\n"
904+
" Measured: this loader's built-in default path is the "
905+
"glibc payload's own prefix, NOT /usr/lib.",
906+
artifactPath, join(resolution.unresolvedSonames)));
840907
} else {
841908
inconclusive(std::format(
842909
"runtime closure for {} is inconclusive; unresolved objects: {}",
843-
artifactPath, names));
910+
artifactPath, join(resolution.unresolved)));
844911
}
845912
}
846913
return verdict;

tests/e2e/206_runtime_binding_physics.sh

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env bash
22
# requires: elf gcc
33
# 206_runtime_binding_physics.sh — a selected RuntimeBinding must become the
4-
# physical PT_INTERP/libc pair, and a host DSO whose GLIBC floor is satisfiable
5-
# must remain a supported control. Proven mismatches fail at link completion;
6-
# hot no-ops neither relink nor rewrite/re-probe the stored verdict.
4+
# physical PT_INTERP/libc pair, and a host DSO linked under `allow_host_libs`
5+
# must still BUILD (the declaration is honoured) while being reported honestly
6+
# (the private loader does not consult /usr/lib). Proven mismatches fail at link
7+
# completion; hot no-ops neither relink nor rewrite/re-probe the stored verdict.
78
set -euo pipefail
89

910
TMP=$(mktemp -d)
@@ -45,8 +46,36 @@ verdict="$output_dir/.mcpp-runtime-verdicts.json"
4546
[[ -x "$artifact" && -f "$verdict" ]] || fail "artifact/verdict missing"
4647
readelf -d "$artifact" | grep -q 'libtinfo\.so' \
4748
|| fail "safe control did not retain host libtinfo in DT_NEEDED"
48-
[[ $(jq -r '.artifacts["bin/runtime-physics-safe"].status' "$verdict") == pass ]] \
49-
|| { cat "$verdict"; fail "safe host-DSO closure did not pass"; }
49+
50+
# INCONCLUSIVE, not pass — and the change from `pass` is a correction, not a
51+
# relaxation.
52+
#
53+
# This artifact links a HOST libtinfo while carrying a PRIVATE PT_INTERP, and
54+
# measured with LD_DEBUG=libs the private loader's built-in default path is the
55+
# glibc payload's own build-time prefix (`…/fromsource-x-glibc/2.39/lib`) — a
56+
# directory that does not exist on this machine. /usr/lib is never consulted,
57+
# and the binary exits 127 with "libtinfo.so.6: cannot open shared object file".
58+
#
59+
# It used to report `pass` because the closure model fell back to the HOST's
60+
# default directories, where libtinfo of course is. The model was describing a
61+
# loader the artifact does not use — the same defect that shipped a GL program
62+
# as `validation: pass` while it exited 127. This file's own header note
63+
# ("execution is not part of this link-physics control") is what let the false
64+
# green stand: nothing ever ran it.
65+
#
66+
# `allow_host_libs` is still honoured, and that is the point of the state: the
67+
# user declared they are reaching outside the sandbox, so mcpp REPORTS rather
68+
# than blocks — resolution at run time (LD_LIBRARY_PATH, or installing where the
69+
# private loader looks) is theirs to arrange.
70+
status=$(jq -r '.artifacts["bin/runtime-physics-safe"].status' "$verdict")
71+
[[ "$status" == inconclusive ]] \
72+
|| { cat "$verdict"; fail "host-DSO closure status is '$status', expected inconclusive"; }
73+
jq -r '.artifacts["bin/runtime-physics-safe"].diagnostics[]' "$verdict" \
74+
| grep -q 'libtinfo\.so\.6' \
75+
|| { cat "$verdict"; fail "the verdict does not name the library it could not resolve"; }
76+
jq -r '.artifacts["bin/runtime-physics-safe"].diagnostics[]' "$verdict" \
77+
| grep -q 'allow_host_libs' \
78+
|| { cat "$verdict"; fail "the verdict does not say why it stopped short of a proof"; }
5079

5180
artifact_before=$(stat -c '%y:%s' "$artifact")
5281
verdict_before=$(stat -c '%y:%s' "$verdict")
@@ -59,8 +88,8 @@ noop_out=$($MCPP build 2>&1) || { echo "$noop_out"; fail "hot no-op build"; }
5988
doctor_out=$($MCPP self doctor 2>&1 || true)
6089
grep -q 'last runtime closure verdict' <<<"$doctor_out" \
6190
|| { echo "$doctor_out"; fail "doctor omitted stored runtime verdict"; }
62-
grep -q 'runtime-physics-safe.*pass' <<<"$doctor_out" \
63-
|| { echo "$doctor_out"; fail "doctor did not reuse passing verdict"; }
91+
grep -q 'runtime-physics-safe.*inconclusive' <<<"$doctor_out" \
92+
|| { echo "$doctor_out"; fail "doctor did not reuse the stored verdict"; }
6493

6594
# Force form-X into a proven Rule-B mismatch: mcpp still supplies the selected
6695
# private libc RUNPATH, while the final user flag replaces PT_INTERP with the

tests/e2e/219_runtime_search_farm_is_last.sh

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,24 @@ PY
148148
echo "DT_RPATH: $DT_RPATH"
149149
[[ -n "$DT_RPATH" ]] || { echo "FAIL: executable carries no DT_RPATH"; exit 1; }
150150

151-
RPATH_LAST="${DT_RPATH##*:}"
152-
[[ "$RPATH_LAST" == "$FARM" ]] || {
153-
echo "FAIL: DT_RPATH does not end with the farm"
154-
echo " recorded farm: $FARM"
155-
echo " DT_RPATH last: $RPATH_LAST"
156-
echo " full: $DT_RPATH"
151+
# The farm must be the last ABSOLUTE entry — not literally the last entry.
152+
#
153+
# `$ORIGIN`-relative entries are a different kind: they address the artifact's
154+
# own directory, not this machine, so they travel with it and their position
155+
# says nothing about which machine-local directory wins. A project with a shared
156+
# library dependency gets one appended after everything else, and an assertion
157+
# of "literally last" would fail on every such project while the invariant it
158+
# meant to check still held. (Measured on a real GLFW app, whose DT_RPATH ends
159+
# `… : <subos>/lib : $ORIGIN`.)
160+
RPATH_LAST_ABS="$(python3 -c "
161+
p = [x for x in '''$DT_RPATH'''.split(':') if x.startswith('/')]
162+
print(p[-1] if p else '')
163+
")"
164+
[[ "$RPATH_LAST_ABS" == "$FARM" ]] || {
165+
echo "FAIL: the farm is not the last absolute entry of DT_RPATH"
166+
echo " recorded farm: $FARM"
167+
echo " last absolute entry: $RPATH_LAST_ABS"
168+
echo " full: $DT_RPATH"
157169
exit 1
158170
}
159171

0 commit comments

Comments
 (0)