@@ -55,6 +55,13 @@ struct ElfFixtureSpec {
5555 std::string interp = " /store/glibc/2.44/lib64/ld-linux-x86-64.so.2" ;
5656 std::vector<std::string> needed = {" libc.so.6" };
5757 std::string runpath = " /host/z:/host/a" ;
58+ // Emit the search path as DT_RPATH and nothing else. The default image
59+ // carries both tags, which is the common shape and the one glibc reads as
60+ // DT_RUNPATH -- so a test about DT_RPATH's reach cannot use it.
61+ bool rpathOnly = false ;
62+ // No search path at all: the object that has to reach its dependencies
63+ // through someone else's DT_RPATH.
64+ bool noSearchPath = false ;
5865};
5966
6067// One deliberately tiny ELF64-LE image. It has no executable code; the test
@@ -108,7 +115,8 @@ std::filesystem::path write_elf_fixture(
108115 auto versionOwner = needed.empty ()
109116 ? append_string (b, kDynstr , cursor, " libc.so.6" )
110117 : needed.front ();
111- auto rpath = append_string (b, kDynstr , cursor, " /legacy/ignored" );
118+ auto rpath = append_string (b, kDynstr , cursor,
119+ spec.rpathOnly ? spec.runpath : " /legacy/ignored" );
112120 auto runpath = append_string (b, kDynstr , cursor, spec.runpath );
113121 auto needVersion = append_string (b, kDynstr , cursor, " GLIBC_2.40" );
114122 auto defVersion = append_string (b, kDynstr , cursor, " GLIBC_2.44" );
@@ -123,8 +131,10 @@ std::filesystem::path write_elf_fixture(
123131 dyn (5 , kVaddr + kDynstr ); // DT_STRTAB
124132 dyn (10 , dynstrSize); // DT_STRSZ
125133 for (auto offset : needed) dyn (1 , offset); // DT_NEEDED
126- dyn (15 , rpath); // DT_RPATH (ignored when RUNPATH exists)
127- dyn (29 , runpath); // DT_RUNPATH
134+ if (!spec.noSearchPath ) {
135+ dyn (15 , rpath); // DT_RPATH (ignored when RUNPATH exists)
136+ if (!spec.rpathOnly ) dyn (29 , runpath); // DT_RUNPATH
137+ }
128138 dyn (0x6ffffffe , kVaddr + kVerneed ); // DT_VERNEED
129139 dyn (0x6fffffff , 1 ); // DT_VERNEEDNUM
130140 dyn (0x6ffffffc , kVaddr + kVerdef ); // DT_VERDEF
@@ -283,6 +293,80 @@ TEST(ElfRuntime, ReusesAnAlreadyLoadedSonameAcrossDependencyRunpaths) {
283293 std::filesystem::weakly_canonical (glibc44 / " libc.so.6" ));
284294}
285295
296+ // DT_RPATH REACHES THE WHOLE CHAIN; DT_RUNPATH REACHES ONE OBJECT.
297+ //
298+ // A vendor toolkit's shared libraries depend on each other by bare SONAME and
299+ // carry no search path of their own; the directory holding them is named once,
300+ // in the EXECUTABLE's DT_RPATH. The model searched only the requesting
301+ // object's own list, so every such library read as unfindable.
302+ //
303+ // Measured on examples/09-heterogeneous/cann before this: mcpp refused the
304+ // build naming eight libraries, and the artifact it had just linked resolved
305+ // seven of them -- failing only on the one that belongs to a driver the
306+ // machine does not have.
307+ TEST (ElfRuntime, ADependencyInheritsTheExecutablesRpath) {
308+ if constexpr (!mcpp::platform::is_linux)
309+ GTEST_SKIP () << " ELF/glibc runtime physics only apply on Linux" ;
310+ Tmp t;
311+ auto payload = t.path / " store" ;
312+ auto glibc = payload / " 2.44" / " lib64" ;
313+ auto toolkit = t.path / " toolkit" / " lib64" ;
314+ std::filesystem::create_directories (glibc);
315+ std::filesystem::create_directories (toolkit);
316+
317+ write_elf_fixture (glibc / " libc.so.6" , { .needed = {}, .runpath = glibc.string () });
318+ // The leaf, reachable only through the executable's DT_RPATH.
319+ write_elf_fixture (toolkit / " libdeep.so" , { .needed = {}, .noSearchPath = true });
320+ // The middle object: names its dependency and says nothing about where it
321+ // lives, which is what a vendor library does.
322+ write_elf_fixture (toolkit / " libtop.so" ,
323+ { .needed = {" libdeep.so" }, .noSearchPath = true });
324+ auto app = write_elf_fixture (t.path / " app" , {
325+ .needed = {" libtop.so" , " libc.so.6" },
326+ .runpath = toolkit.string () + " :" + glibc.string (),
327+ .rpathOnly = true ,
328+ });
329+
330+ auto resolution = elf::resolve_runtime_closure (app, binding_for (payload));
331+ EXPECT_TRUE (resolution.unresolvedSonames .empty ())
332+ << " unresolved: " << (resolution.unresolvedSonames .empty ()
333+ ? std::string{} : resolution.unresolvedSonames .front ());
334+ }
335+
336+ // …and the suppression, which is the half that makes the rule a rule. An
337+ // object carrying DT_RUNPATH uses no RPATH at all -- its own or inherited --
338+ // so a test with only the leg above would also pass on an implementation that
339+ // inherited unconditionally.
340+ TEST (ElfRuntime, ADependencyWithItsOwnRunpathDoesNotInheritOne) {
341+ if constexpr (!mcpp::platform::is_linux)
342+ GTEST_SKIP () << " ELF/glibc runtime physics only apply on Linux" ;
343+ Tmp t;
344+ auto payload = t.path / " store" ;
345+ auto glibc = payload / " 2.44" / " lib64" ;
346+ auto toolkit = t.path / " toolkit" / " lib64" ;
347+ auto elsewhere = t.path / " elsewhere" ;
348+ std::filesystem::create_directories (glibc);
349+ std::filesystem::create_directories (toolkit);
350+ std::filesystem::create_directories (elsewhere);
351+
352+ write_elf_fixture (glibc / " libc.so.6" , { .needed = {}, .runpath = glibc.string () });
353+ write_elf_fixture (toolkit / " libdeep.so" , { .needed = {}, .noSearchPath = true });
354+ // Same graph as above, except this middle object carries DT_RUNPATH. It
355+ // names a directory that does not hold `libdeep.so`, and glibc will not
356+ // fall back to the executable's DT_RPATH for it.
357+ write_elf_fixture (toolkit / " libtop.so" ,
358+ { .needed = {" libdeep.so" }, .runpath = elsewhere.string () });
359+ auto app = write_elf_fixture (t.path / " app" , {
360+ .needed = {" libtop.so" , " libc.so.6" },
361+ .runpath = toolkit.string () + " :" + glibc.string (),
362+ .rpathOnly = true ,
363+ });
364+
365+ auto resolution = elf::resolve_runtime_closure (app, binding_for (payload));
366+ ASSERT_EQ (resolution.unresolvedSonames .size (), 1u );
367+ EXPECT_EQ (resolution.unresolvedSonames .front (), " libdeep.so" );
368+ }
369+
286370TEST (RuntimePhysics, RuleBRejectsInterpreterAndLibcFromDifferentPayloads) {
287371 if constexpr (!mcpp::platform::is_linux)
288372 GTEST_SKIP () << " ELF/glibc runtime physics only apply on Linux" ;
0 commit comments