@@ -50,10 +50,18 @@ std::uint32_t append_string(std::vector<unsigned char>& b,
5050 return offset;
5151}
5252
53+ struct ElfFixtureSpec {
54+ std::string interp = " /store/glibc/2.44/lib64/ld-linux-x86-64.so.2" ;
55+ std::vector<std::string> needed = {" libc.so.6" };
56+ std::string runpath = " /host/z:/host/a" ;
57+ };
58+
5359// One deliberately tiny ELF64-LE image. It has no executable code; the test
5460// exercises the same program/dynamic/version tables real stripped binaries
5561// retain, without depending on readelf, the host compiler or the host libc.
56- std::filesystem::path write_elf_fixture (const std::filesystem::path& path) {
62+ std::filesystem::path write_elf_fixture (
63+ const std::filesystem::path& path,
64+ const ElfFixtureSpec& spec = {}) {
5765 constexpr std::uint64_t kVaddr = 0x400000 ;
5866 constexpr std::size_t kInterp = 0x200 ;
5967 constexpr std::size_t kDynamic = 0x300 ;
@@ -84,18 +92,23 @@ std::filesystem::path write_elf_fixture(const std::filesystem::path& path) {
8492 put64 (b, p + 0x28 , filesz);
8593 };
8694
87- constexpr std::string_view interp = " /store/glibc/2.44/lib64/ld-linux-x86-64.so.2 " ;
95+ const std::string_view interp = spec. interp ;
8896 std::copy (interp.begin (), interp.end (), b.begin () + kInterp );
8997 b[kInterp + interp.size ()] = 0 ;
9098 ph (0 , 1 , 0 , b.size ()); // PT_LOAD
9199 ph (1 , 3 , kInterp , interp.size () + 1 ); // PT_INTERP
92- ph (2 , 2 , kDynamic , 11 * 16 ); // PT_DYNAMIC
100+ ph (2 , 2 , kDynamic , 24 * 16 ); // PT_DYNAMIC
93101
94102 std::size_t cursor = kDynstr ;
95103 b[cursor++] = 0 ;
96- auto libc = append_string (b, kDynstr , cursor, " libc.so.6" );
104+ std::vector<std::uint32_t > needed;
105+ for (auto const & name : spec.needed )
106+ needed.push_back (append_string (b, kDynstr , cursor, name));
107+ auto versionOwner = needed.empty ()
108+ ? append_string (b, kDynstr , cursor, " libc.so.6" )
109+ : needed.front ();
97110 auto rpath = append_string (b, kDynstr , cursor, " /legacy/ignored" );
98- auto runpath = append_string (b, kDynstr , cursor, " /host/z:/host/a " );
111+ auto runpath = append_string (b, kDynstr , cursor, spec. runpath );
99112 auto needVersion = append_string (b, kDynstr , cursor, " GLIBC_2.40" );
100113 auto defVersion = append_string (b, kDynstr , cursor, " GLIBC_2.44" );
101114 auto dynstrSize = cursor - kDynstr ;
@@ -108,7 +121,7 @@ std::filesystem::path write_elf_fixture(const std::filesystem::path& path) {
108121 };
109122 dyn (5 , kVaddr + kDynstr ); // DT_STRTAB
110123 dyn (10 , dynstrSize); // DT_STRSZ
111- dyn (1 , libc); // DT_NEEDED
124+ for ( auto offset : needed) dyn (1 , offset); // DT_NEEDED
112125 dyn (15 , rpath); // DT_RPATH (ignored when RUNPATH exists)
113126 dyn (29 , runpath); // DT_RUNPATH
114127 dyn (0x6ffffffe , kVaddr + kVerneed ); // DT_VERNEED
@@ -120,7 +133,7 @@ std::filesystem::path write_elf_fixture(const std::filesystem::path& path) {
120133 // Elf64_Verneed + one Elf64_Vernaux.
121134 put16 (b, kVerneed , 1 );
122135 put16 (b, kVerneed + 2 , 1 );
123- put32 (b, kVerneed + 4 , libc );
136+ put32 (b, kVerneed + 4 , versionOwner );
124137 put32 (b, kVerneed + 8 , 16 );
125138 put32 (b, kVerneed + 12 , 0 );
126139 put32 (b, kVerneed + 16 , 0 );
@@ -228,6 +241,43 @@ TEST(ElfRuntime, RejectsUnsupportedOrTruncatedElfWithoutGuessing) {
228241 EXPECT_FALSE (truncated.has_value ());
229242}
230243
244+ TEST (ElfRuntime, ReusesAnAlreadyLoadedSonameAcrossDependencyRunpaths) {
245+ if constexpr (!mcpp::platform::is_linux)
246+ GTEST_SKIP () << " ELF/glibc runtime physics only apply on Linux" ;
247+ Tmp t;
248+ auto payload = t.path / " store" ;
249+ auto glibc39 = payload / " 2.39" / " lib64" ;
250+ auto glibc44 = payload / " 2.44" / " lib64" ;
251+ auto shimDir = t.path / " shim" ;
252+ std::filesystem::create_directories (glibc39);
253+ std::filesystem::create_directories (glibc44);
254+ std::filesystem::create_directories (shimDir);
255+
256+ write_elf_fixture (glibc39 / " libc.so.6" , {
257+ .needed = {" libc.so.6" },
258+ .runpath = glibc39.string (),
259+ });
260+ write_elf_fixture (glibc44 / " libc.so.6" , {
261+ .needed = {" libc.so.6" },
262+ .runpath = glibc44.string (),
263+ });
264+ write_elf_fixture (shimDir / " libshim.so" , {
265+ .needed = {" libc.so.6" },
266+ .runpath = glibc39.string (),
267+ });
268+ auto app = write_elf_fixture (t.path / " app" , {
269+ .needed = {" libc.so.6" , " libshim.so" },
270+ .runpath = shimDir.string (),
271+ });
272+
273+ auto resolution = elf::resolve_runtime_closure (app, binding_for (payload));
274+ ASSERT_TRUE (resolution.unresolved .empty ());
275+ ASSERT_EQ (resolution.resolvedLibcs .size (), 1u )
276+ << " a later dependency must reuse the process-global libc SONAME" ;
277+ EXPECT_EQ (resolution.resolvedLibcs .front (),
278+ std::filesystem::weakly_canonical (glibc44 / " libc.so.6" ));
279+ }
280+
231281TEST (RuntimePhysics, RuleBRejectsInterpreterAndLibcFromDifferentPayloads) {
232282 if constexpr (!mcpp::platform::is_linux)
233283 GTEST_SKIP () << " ELF/glibc runtime physics only apply on Linux" ;
0 commit comments