1//===- LibraryScanner.cpp - Provide Library Scanning Implementation ----===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "llvm/ExecutionEngine/Orc/TargetProcess/LibraryScanner.h"
10#include "llvm/ExecutionEngine/Orc/TargetProcess/LibraryResolver.h"
11
12#include "llvm/ADT/StringExtras.h"
13#include "llvm/Object/COFF.h"
14#include "llvm/Object/ELF.h"
15#include "llvm/Object/ELFObjectFile.h"
16#include "llvm/Object/ELFTypes.h"
17#include "llvm/Object/MachO.h"
18#include "llvm/Object/MachOUniversal.h"
19#include "llvm/Object/ObjectFile.h"
20#include "llvm/Support/Error.h"
21#include "llvm/Support/FileSystem.h"
22#include "llvm/Support/MemoryBuffer.h"
23#include "llvm/Support/Path.h"
24#include "llvm/Support/Program.h"
25#include "llvm/TargetParser/Host.h"
26#include "llvm/TargetParser/Triple.h"
27
28#ifdef LLVM_ON_UNIX
29#include <sys/stat.h>
30#include <unistd.h>
31#endif // LLVM_ON_UNIX
32
33#ifdef __APPLE__
34#include <sys/stat.h>
35#undef LC_LOAD_DYLIB
36#undef LC_RPATH
37#endif // __APPLE__
38
39#define DEBUG_TYPE "orc-scanner"
40
41namespace llvm::orc {
42
43void handleError(Error Err, StringRef context = "") {
44 consumeError(Err: handleErrors(E: std::move(Err), Hs: [&](const ErrorInfoBase &EIB) {
45 dbgs() << "LLVM Error";
46 if (!context.empty())
47 dbgs() << " [" << context << "]";
48 dbgs() << ": " << EIB.message() << "\n";
49 }));
50}
51
52bool ObjectFileLoader::isArchitectureCompatible(const object::ObjectFile &Obj) {
53 static const llvm::Triple HostTriple(llvm::sys::getProcessTriple());
54
55 if (HostTriple.getArch() != Obj.getArch())
56 return false;
57
58 if (Obj.getTripleObjectFormat() != HostTriple.getObjectFormat())
59 return false;
60
61 return true;
62}
63
64Expected<object::OwningBinary<object::ObjectFile>>
65ObjectFileLoader::loadObjectFileWithOwnership(StringRef FilePath) {
66 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Attempting to open file " << FilePath
67 << "\n";);
68 if (auto ObjOrErr = object::ObjectFile::createObjectFile(ObjectPath: FilePath)) {
69
70 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Detected object file\n";);
71
72 auto OwningBin = std::move(*ObjOrErr);
73
74 if (!isArchitectureCompatible(Obj: *OwningBin.getBinary())) {
75 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Incompatible architecture: "
76 << FilePath << "\n";);
77 return createStringError(EC: inconvertibleErrorCode(),
78 Fmt: "Incompatible object file: %s",
79 Vals: FilePath.str().c_str());
80 }
81
82 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Object file is compatible\n";);
83
84 return std::move(OwningBin);
85 } else {
86#if defined(__APPLE__)
87 consumeError(ObjOrErr.takeError());
88 auto BinOrErr = object::createBinary(FilePath);
89 if (!BinOrErr) {
90 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Failed to open file " << FilePath
91 << "\n";);
92 return BinOrErr.takeError();
93 }
94
95 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Successfully opened file "
96 << FilePath << "\n";);
97
98 auto OwningBin = BinOrErr->takeBinary();
99 object::Binary *Bin = OwningBin.first.get();
100
101 if (Bin->isArchive()) {
102 LLVM_DEBUG(
103 dbgs() << "ObjectFileLoader: File is an archive, not supported: "
104 << FilePath << "\n";);
105 return createStringError(std::errc::invalid_argument,
106 "Archive files are not supported: %s",
107 FilePath.str().c_str());
108 }
109
110 if (auto *UB = dyn_cast<object::MachOUniversalBinary>(Bin)) {
111 LLVM_DEBUG(
112 dbgs() << "ObjectFileLoader: Detected Mach-O universal binary: "
113 << FilePath << "\n";);
114 for (auto ObjForArch : UB->objects()) {
115 auto ObjOrErr = ObjForArch.getAsObjectFile();
116 if (!ObjOrErr) {
117 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Skipping invalid "
118 "architecture slice\n";);
119
120 consumeError(ObjOrErr.takeError());
121 continue;
122 }
123
124 std::unique_ptr<object::ObjectFile> Obj = std::move(ObjOrErr.get());
125 if (isArchitectureCompatible(*Obj)) {
126 LLVM_DEBUG(
127 dbgs() << "ObjectFileLoader: Found compatible object slice\n";);
128
129 return object::OwningBinary<object::ObjectFile>(
130 std::move(Obj), std::move(OwningBin.second));
131
132 } else {
133 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Incompatible architecture "
134 "slice skipped\n";);
135 }
136 }
137 LLVM_DEBUG(dbgs() << "ObjectFileLoader: No compatible slices found in "
138 "universal binary\n";);
139 return createStringError(inconvertibleErrorCode(),
140 "No compatible object found in fat binary: %s",
141 FilePath.str().c_str());
142 }
143 return ObjOrErr.takeError();
144#else
145 LLVM_DEBUG(dbgs() << "ObjectFileLoader: Failed to open file " << FilePath
146 << "\n";);
147 return ObjOrErr.takeError();
148#endif
149 }
150
151 return createStringError(EC: inconvertibleErrorCode(),
152 Fmt: "Not a compatible object file : %s",
153 Vals: FilePath.str().c_str());
154}
155
156template <class ELFT>
157bool isELFSharedLibrary(const object::ELFFile<ELFT> &ELFObj) {
158 if (ELFObj.getHeader().e_type != ELF::ET_DYN)
159 return false;
160
161 auto PHOrErr = ELFObj.program_headers();
162 if (!PHOrErr) {
163 consumeError(PHOrErr.takeError());
164 return true;
165 }
166
167 for (auto Phdr : *PHOrErr) {
168 if (Phdr.p_type == ELF::PT_INTERP)
169 return false;
170 }
171
172 return true;
173}
174
175bool isSharedLibraryObject(object::ObjectFile &Obj) {
176 if (Obj.isELF()) {
177 if (auto *ELF32LE = dyn_cast<object::ELF32LEObjectFile>(Val: &Obj))
178 return isELFSharedLibrary(ELFObj: ELF32LE->getELFFile());
179 if (auto *ELF64LE = dyn_cast<object::ELF64LEObjectFile>(Val: &Obj))
180 return isELFSharedLibrary(ELFObj: ELF64LE->getELFFile());
181 if (auto *ELF32BE = dyn_cast<object::ELF32BEObjectFile>(Val: &Obj))
182 return isELFSharedLibrary(ELFObj: ELF32BE->getELFFile());
183 if (auto *ELF64BE = dyn_cast<object::ELF64BEObjectFile>(Val: &Obj))
184 return isELFSharedLibrary(ELFObj: ELF64BE->getELFFile());
185 } else if (Obj.isMachO()) {
186 const object::MachOObjectFile *MachO =
187 dyn_cast<object::MachOObjectFile>(Val: &Obj);
188 if (!MachO) {
189 LLVM_DEBUG(dbgs() << "Failed to cast to MachOObjectFile.\n";);
190 return false;
191 }
192 LLVM_DEBUG({
193 bool Result =
194 MachO->getHeader().filetype == MachO::HeaderFileType::MH_DYLIB;
195 dbgs() << "Mach-O filetype: " << MachO->getHeader().filetype
196 << " (MH_DYLIB == " << MachO::HeaderFileType::MH_DYLIB
197 << "), shared: " << Result << "\n";
198 });
199
200 return MachO->getHeader().filetype == MachO::HeaderFileType::MH_DYLIB;
201 } else if (Obj.isCOFF()) {
202 const object::COFFObjectFile *coff = dyn_cast<object::COFFObjectFile>(Val: &Obj);
203 if (!coff)
204 return false;
205 return coff->getCharacteristics() & COFF::IMAGE_FILE_DLL;
206 } else {
207 LLVM_DEBUG(dbgs() << "Binary is not an ObjectFile.\n";);
208 }
209
210 return false;
211}
212
213bool DylibPathValidator::isSharedLibrary(StringRef Path) const {
214 LLVM_DEBUG(dbgs() << "Checking if path is a shared library: " << Path
215 << "\n";);
216
217 auto FileType = sys::fs::get_file_type(Path, /*Follow*/ true);
218 if (FileType != sys::fs::file_type::regular_file) {
219 LLVM_DEBUG(dbgs() << "File type is not a regular file for path: " << Path
220 << "\n";);
221 return false;
222 }
223
224 file_magic MagicCode;
225 identify_magic(path: Path, result&: MagicCode);
226
227 // Skip archives.
228 if (MagicCode == file_magic::archive)
229 return false;
230
231 // Object file inspection for PE/COFF, ELF, and Mach-O
232 bool NeedsObjectInspection =
233#if defined(_WIN32)
234 (MagicCode == file_magic::pecoff_executable);
235#elif defined(__APPLE__)
236 (MagicCode == file_magic::macho_universal_binary ||
237 MagicCode == file_magic::macho_fixed_virtual_memory_shared_lib ||
238 MagicCode == file_magic::macho_dynamically_linked_shared_lib ||
239 MagicCode == file_magic::macho_dynamically_linked_shared_lib_stub);
240#elif defined(LLVM_ON_UNIX)
241#ifdef __CYGWIN__
242 (MagicCode == file_magic::pecoff_executable);
243#else
244 (MagicCode == file_magic::elf_shared_object);
245#endif
246#else
247#error "Unsupported platform."
248#endif
249
250 if (!NeedsObjectInspection) {
251 LLVM_DEBUG(dbgs() << "Path is not identified as a shared library: " << Path
252 << "\n";);
253 return false;
254 }
255
256 ObjectFileLoader ObjLoader(Path);
257 auto ObjOrErr = ObjLoader.getObjectFile();
258 if (!ObjOrErr) {
259 consumeError(Err: ObjOrErr.takeError());
260 return false;
261 }
262
263 bool IsShared = isSharedLibraryObject(Obj&: ObjOrErr.get());
264
265 if (IsShared && ObjCache)
266 ObjCache->insert(Path, Loader: std::move(ObjLoader));
267
268 return IsShared;
269}
270
271void DylibSubstitutor::configure(StringRef LoaderPath) {
272 SmallString<512> ExecPath(sys::fs::getMainExecutable(argv0: nullptr, MainExecAddr: nullptr));
273 sys::path::remove_filename(path&: ExecPath);
274
275 SmallString<512> LoaderDir;
276 if (LoaderPath.empty()) {
277 LoaderDir = ExecPath;
278 } else {
279 LoaderDir = LoaderPath.str();
280 if (!sys::fs::is_directory(Path: LoaderPath))
281 sys::path::remove_filename(path&: LoaderDir);
282 }
283
284#ifdef __APPLE__
285 Placeholders.push_back({"@loader_path", std::string(LoaderDir)});
286 Placeholders.push_back({"@executable_path", std::string(ExecPath)});
287#else
288 Placeholders.push_back(Elt: {"$origin", std::string(LoaderDir)});
289#endif
290}
291
292std::optional<std::string>
293SearchPathResolver::resolve(StringRef Stem, const DylibSubstitutor &Subst,
294 DylibPathValidator &Validator) const {
295 for (const auto &SP : Paths) {
296 std::string Base = Subst.substitute(input: SP);
297
298 SmallString<512> FullPath(Base);
299 if (!PlaceholderPrefix.empty() &&
300 Stem.starts_with_insensitive(Prefix: PlaceholderPrefix))
301 FullPath.append(RHS: Stem.drop_front(N: PlaceholderPrefix.size()));
302 else
303 sys::path::append(path&: FullPath, a: Stem);
304
305 LLVM_DEBUG(dbgs() << "SearchPathResolver::resolve FullPath = " << FullPath
306 << "\n";);
307
308 if (auto Valid = Validator.validate(Path: FullPath.str()))
309 return Valid;
310 }
311
312 return std::nullopt;
313}
314
315std::optional<std::string>
316DylibResolverImpl::tryWithExtensions(StringRef LibStem) const {
317 LLVM_DEBUG(dbgs() << "tryWithExtensions: baseName = " << LibStem << "\n";);
318 SmallVector<SmallString<256>, 8> Candidates;
319
320 // Add extensions by platform
321#if defined(__APPLE__)
322 Candidates.emplace_back(LibStem);
323 Candidates.back() += ".dylib";
324#elif defined(_WIN32)
325 Candidates.emplace_back(LibStem);
326 Candidates.back() += ".dll";
327#else
328 Candidates.emplace_back(Args&: LibStem);
329 Candidates.back() += ".so";
330#endif
331
332 // Optionally try "lib" prefix if not already there
333 StringRef FileName = sys::path::filename(path: LibStem);
334 StringRef Base = sys::path::parent_path(path: LibStem);
335 if (!FileName.starts_with(Prefix: "lib")) {
336 SmallString<256> WithPrefix(Base);
337 if (!WithPrefix.empty())
338 sys::path::append(path&: WithPrefix, a: ""); // ensure separator if needed
339 WithPrefix += "lib";
340 WithPrefix += FileName;
341
342#if defined(__APPLE__)
343 WithPrefix += ".dylib";
344#elif defined(_WIN32)
345 WithPrefix += ".dll";
346#else
347 WithPrefix += ".so";
348#endif
349
350 Candidates.push_back(Elt: std::move(WithPrefix));
351 }
352
353 LLVM_DEBUG({
354 dbgs() << " Candidates to try:\n";
355 for (const auto &C : Candidates)
356 dbgs() << " " << C << "\n";
357 });
358
359 // Try all variants using tryAllPaths
360 for (const auto &Name : Candidates) {
361
362 LLVM_DEBUG(dbgs() << " Trying candidate: " << Name << "\n";);
363
364 for (const auto &R : Resolvers) {
365 if (auto Res = R.resolve(Stem: Name, Subst: Substitutor, Validator))
366 return Res;
367 }
368 }
369
370 LLVM_DEBUG(dbgs() << " -> No candidate Resolved.\n";);
371
372 return std::nullopt;
373}
374
375std::optional<std::string>
376DylibResolverImpl::resolve(StringRef LibStem, bool VariateLibStem) const {
377 LLVM_DEBUG(dbgs() << "Resolving library stem: " << LibStem << "\n";);
378
379 // If it is an absolute path, don't try iterate over the paths.
380 if (sys::path::is_absolute(path: LibStem)) {
381 LLVM_DEBUG(dbgs() << " -> Absolute path detected.\n";);
382 return Validator.validate(Path: LibStem);
383 }
384
385 if (!LibStem.starts_with_insensitive(Prefix: "@rpath")) {
386 if (auto norm = Validator.validate(Path: Substitutor.substitute(input: LibStem))) {
387 LLVM_DEBUG(dbgs() << " -> Resolved after substitution: " << *norm
388 << "\n";);
389
390 return norm;
391 }
392 }
393
394 for (const auto &R : Resolvers) {
395 LLVM_DEBUG(dbgs() << " -> Resolving via search path ... \n";);
396 if (auto Result = R.resolve(Stem: LibStem, Subst: Substitutor, Validator)) {
397 LLVM_DEBUG(dbgs() << " -> Resolved via search path: " << *Result
398 << "\n";);
399
400 return Result;
401 }
402 }
403
404 // Expand libStem with paths, extensions, etc.
405 // std::string foundName;
406 if (VariateLibStem) {
407 LLVM_DEBUG(dbgs() << " -> Trying with extensions...\n";);
408
409 if (auto Norm = tryWithExtensions(LibStem)) {
410 LLVM_DEBUG(dbgs() << " -> Resolved via tryWithExtensions: " << *Norm
411 << "\n";);
412 return Norm;
413 }
414 }
415
416 LLVM_DEBUG(dbgs() << " -> Could not resolve: " << LibStem << "\n";);
417
418 return std::nullopt;
419}
420
421#ifndef _WIN32
422mode_t PathResolver::lstatCached(StringRef Path) {
423 // If already cached - retun cached result
424 if (auto Cache = LibPathCache->read_lstat(Path))
425 return *Cache;
426
427 // Not cached: perform lstat and store
428 struct stat buf{};
429 mode_t st_mode = (lstat(file: Path.str().c_str(), buf: &buf) == -1) ? 0 : buf.st_mode;
430
431 LibPathCache->insert_lstat(Path, m: st_mode);
432
433 return st_mode;
434}
435
436std::optional<std::string> PathResolver::readlinkCached(StringRef Path) {
437 // If already cached - retun cached result
438 if (auto Cache = LibPathCache->read_link(Path))
439 return Cache;
440
441 // If result not in cache - call system function and cache result
442 char buf[PATH_MAX];
443 ssize_t len;
444 if ((len = readlink(path: Path.str().c_str(), buf: buf, len: sizeof(buf))) != -1) {
445 buf[len] = '\0';
446 std::string s(buf);
447 LibPathCache->insert_link(Path, s);
448 return s;
449 }
450 return std::nullopt;
451}
452
453void createComponent(StringRef Path, StringRef BasePath, bool BaseIsResolved,
454 SmallVector<StringRef, 16> &Component) {
455 StringRef Separator = sys::path::get_separator();
456 if (!BaseIsResolved) {
457 if (Path[0] == '~' &&
458 (Path.size() == 1 || sys::path::is_separator(value: Path[1]))) {
459 static SmallString<128> HomeP;
460 if (HomeP.str().empty())
461 sys::path::home_directory(result&: HomeP);
462 StringRef(HomeP).split(A&: Component, Separator, /*MaxSplit*/ -1,
463 /*KeepEmpty*/ false);
464 } else if (BasePath.empty()) {
465 static SmallString<256> CurrentPath;
466 if (CurrentPath.str().empty())
467 sys::fs::current_path(result&: CurrentPath);
468 StringRef(CurrentPath)
469 .split(A&: Component, Separator, /*MaxSplit*/ -1, /*KeepEmpty*/ false);
470 } else {
471 BasePath.split(A&: Component, Separator, /*MaxSplit*/ -1,
472 /*KeepEmpty*/ false);
473 }
474 }
475
476 Path.split(A&: Component, Separator, /*MaxSplit*/ -1, /*KeepEmpty*/ false);
477}
478
479void normalizePathSegments(SmallVector<StringRef, 16> &PathParts) {
480 SmallVector<StringRef, 16> NormalizedPath;
481 for (auto &Part : PathParts) {
482 if (Part == ".") {
483 continue;
484 } else if (Part == "..") {
485 if (!NormalizedPath.empty() && NormalizedPath.back() != "..") {
486 NormalizedPath.pop_back();
487 } else {
488 NormalizedPath.push_back(Elt: "..");
489 }
490 } else {
491 NormalizedPath.push_back(Elt: Part);
492 }
493 }
494 PathParts.swap(RHS&: NormalizedPath);
495}
496#endif
497
498std::optional<std::string> PathResolver::realpathCached(StringRef Path,
499 std::error_code &EC,
500 StringRef Base,
501 bool BaseIsResolved,
502 long SymLoopLevel) {
503 EC.clear();
504
505 if (Path.empty()) {
506 EC = std::make_error_code(e: std::errc::no_such_file_or_directory);
507 LLVM_DEBUG(dbgs() << "PathResolver::realpathCached: Empty path\n";);
508
509 return std::nullopt;
510 }
511
512 if (SymLoopLevel <= 0) {
513 EC = std::make_error_code(e: std::errc::too_many_symbolic_link_levels);
514 LLVM_DEBUG(
515 dbgs() << "PathResolver::realpathCached: Too many Symlink levels: "
516 << Path << "\n";);
517
518 return std::nullopt;
519 }
520
521 // If already cached - retun cached result
522 bool isRelative = sys::path::is_relative(path: Path);
523 if (!isRelative) {
524 if (auto Cached = LibPathCache->read_realpath(Path)) {
525 EC = Cached->ErrnoCode;
526 if (EC) {
527 LLVM_DEBUG(dbgs() << "PathResolver::realpathCached: Cached (error) for "
528 << Path << "\n";);
529 } else {
530 LLVM_DEBUG(
531 dbgs() << "PathResolver::realpathCached: Cached (success) for "
532 << Path << " => " << Cached->canonicalPath << "\n";);
533 }
534 return Cached->canonicalPath.empty()
535 ? std::nullopt
536 : std::make_optional(t&: Cached->canonicalPath);
537 }
538 }
539
540 LLVM_DEBUG(dbgs() << "PathResolver::realpathCached: Resolving path: " << Path
541 << "\n";);
542
543 // If result not in cache - call system function and cache result
544
545 StringRef Separator(sys::path::get_separator());
546 SmallString<256> Resolved(Separator);
547#ifndef _WIN32
548 SmallVector<StringRef, 16> Components;
549
550 if (isRelative) {
551 if (BaseIsResolved) {
552 Resolved.assign(RHS: Base);
553 LLVM_DEBUG(dbgs() << " Using Resolved base: " << Base << "\n";);
554 }
555 createComponent(Path, BasePath: Base, BaseIsResolved, Component&: Components);
556 } else {
557 Path.split(A&: Components, Separator, /*MaxSplit*/ -1, /*KeepEmpty*/ false);
558 }
559
560 normalizePathSegments(PathParts&: Components);
561 LLVM_DEBUG({
562 for (auto &C : Components)
563 dbgs() << " " << C << " ";
564
565 dbgs() << "\n";
566 });
567
568 // Handle path list items
569 for (const auto &Component : Components) {
570 if (Component == ".")
571 continue;
572 if (Component == "..") {
573 // collapse "a/b/../c" to "a/c"
574 size_t S = Resolved.rfind(Str: Separator);
575 if (S != llvm::StringRef::npos)
576 Resolved.resize(N: S);
577 if (Resolved.empty())
578 Resolved = Separator;
579 continue;
580 }
581
582 size_t oldSize = Resolved.size();
583 sys::path::append(path&: Resolved, a: Component);
584 const char *ResolvedPath = Resolved.c_str();
585 LLVM_DEBUG(dbgs() << " Processing Component: " << Component << " => "
586 << ResolvedPath << "\n";);
587 mode_t st_mode = lstatCached(Path: ResolvedPath);
588
589 if (S_ISLNK(st_mode)) {
590 LLVM_DEBUG(dbgs() << " Found symlink: " << ResolvedPath << "\n";);
591
592 auto SymlinkOpt = readlinkCached(Path: ResolvedPath);
593 if (!SymlinkOpt) {
594 EC = std::make_error_code(e: std::errc::no_such_file_or_directory);
595 LibPathCache->insert_realpath(Path, Info: LibraryPathCache::PathInfo{.canonicalPath: "", .ErrnoCode: EC});
596 LLVM_DEBUG(dbgs() << " Failed to read symlink: " << ResolvedPath
597 << "\n";);
598
599 return std::nullopt;
600 }
601
602 StringRef Symlink = *SymlinkOpt;
603 LLVM_DEBUG(dbgs() << " Symlink points to: " << Symlink << "\n";);
604
605 std::string resolvedBase = "";
606 if (sys::path::is_relative(path: Symlink)) {
607 Resolved.resize(N: oldSize);
608 resolvedBase = Resolved.str().str();
609 }
610
611 auto RealSymlink =
612 realpathCached(Path: Symlink, EC, Base: resolvedBase,
613 /*BaseIsResolved=*/true, SymLoopLevel: SymLoopLevel - 1);
614 if (!RealSymlink) {
615 LibPathCache->insert_realpath(Path, Info: LibraryPathCache::PathInfo{.canonicalPath: "", .ErrnoCode: EC});
616 LLVM_DEBUG(dbgs() << " Failed to resolve symlink target: " << Symlink
617 << "\n";);
618
619 return std::nullopt;
620 }
621
622 Resolved.assign(RHS: *RealSymlink);
623 LLVM_DEBUG(dbgs() << " Symlink Resolved to: " << Resolved << "\n";);
624
625 } else if (st_mode == 0) {
626 EC = std::make_error_code(e: std::errc::no_such_file_or_directory);
627 LibPathCache->insert_realpath(Path, Info: LibraryPathCache::PathInfo{.canonicalPath: "", .ErrnoCode: EC});
628 LLVM_DEBUG(dbgs() << " Component does not exist: " << ResolvedPath
629 << "\n";);
630
631 return std::nullopt;
632 }
633 }
634#else
635 EC = sys::fs::real_path(Path, Resolved); // Windows fallback
636#endif
637
638 std::string Canonical = Resolved.str().str();
639 {
640 LibPathCache->insert_realpath(Path, Info: LibraryPathCache::PathInfo{
641 .canonicalPath: Canonical,
642 .ErrnoCode: std::error_code() // success
643 });
644 }
645 LLVM_DEBUG(dbgs() << "PathResolver::realpathCached: Final Resolved: " << Path
646 << " => " << Canonical << "\n";);
647 return Canonical;
648}
649
650void LibraryScanHelper::addBasePath(const std::string &Path, PathType K) {
651 std::error_code EC;
652 std::string Canon = resolveCanonical(P: Path, ec&: EC);
653 if (EC) {
654 LLVM_DEBUG(
655 dbgs()
656 << "LibraryScanHelper::addBasePath: Failed to canonicalize path: "
657 << Path << "\n";);
658 return;
659 }
660 std::unique_lock<std::shared_mutex> Lock(Mtx);
661 if (LibSearchPaths.count(Key: Canon)) {
662 LLVM_DEBUG(dbgs() << "LibraryScanHelper::addBasePath: Already added: "
663 << Canon << "\n";);
664 return;
665 }
666 K = K == PathType::Unknown ? classifyKind(P: Canon) : K;
667 LibSearchPaths[Canon] = std::make_unique<LibrarySearchPath>(args&: Canon, args&: K);
668 auto &SP = LibSearchPaths[Canon];
669
670 if (K == PathType::User) {
671 LLVM_DEBUG(dbgs() << "LibraryScanHelper::addBasePath: Added User path: "
672 << Canon << "\n";);
673 UnscannedUsr.push_back(x: StringRef(SP->BasePath));
674 } else {
675 LLVM_DEBUG(dbgs() << "LibraryScanHelper::addBasePath: Added System path: "
676 << Canon << "\n";);
677 UnscannedSys.push_back(x: StringRef(SP->BasePath));
678 }
679}
680
681void LibraryScanHelper::getNextBatch(
682 PathType K, size_t BatchSize,
683 SmallVectorImpl<const LibrarySearchPath *> &Result) {
684 auto &Queue = (K == PathType::User) ? UnscannedUsr : UnscannedSys;
685
686 std::unique_lock<std::shared_mutex> Lock(Mtx);
687
688 while (!Queue.empty() && (BatchSize == 0 || Result.size() < BatchSize)) {
689 StringRef Base = Queue.front();
690 auto It = LibSearchPaths.find(Key: Base);
691 if (It != LibSearchPaths.end()) {
692 auto &SP = It->second;
693 ScanState Expected = ScanState::NotScanned;
694 if (SP->State.compare_exchange_strong(e&: Expected, i: ScanState::Scanning)) {
695 Result.push_back(Elt: SP.get());
696 }
697 }
698 Queue.pop_front();
699 }
700}
701
702bool LibraryScanHelper::isTrackedBasePath(StringRef Path) const {
703 std::error_code EC;
704 std::string Canon = resolveCanonical(P: Path, ec&: EC);
705 if (EC)
706 return false;
707
708 std::shared_lock<std::shared_mutex> Lock(Mtx);
709 return LibSearchPaths.count(Key: Canon) > 0;
710}
711
712bool LibraryScanHelper::leftToScan(PathType K) const {
713 std::shared_lock<std::shared_mutex> Lock(Mtx);
714 for (const auto &KV : LibSearchPaths) {
715 const auto &SP = KV.second;
716 if (SP->Kind == K && SP->State == ScanState::NotScanned)
717 return true;
718 }
719 return false;
720}
721
722void LibraryScanHelper::resetToScan() {
723 std::shared_lock<std::shared_mutex> Lock(Mtx);
724
725 for (auto &[_, SP] : LibSearchPaths) {
726 ScanState Expected = ScanState::Scanned;
727
728 if (!SP->State.compare_exchange_strong(e&: Expected, i: ScanState::NotScanned))
729 continue;
730
731 auto &TargetList =
732 (SP->Kind == PathType::User) ? UnscannedUsr : UnscannedSys;
733 TargetList.emplace_back(args&: SP->BasePath);
734 }
735}
736
737std::string LibraryScanHelper::resolveCanonical(StringRef Path,
738 std::error_code &EC) const {
739 auto Canon = LibPathResolver->resolve(Path, ec&: EC);
740 return EC ? Path.str() : *Canon;
741}
742
743PathType LibraryScanHelper::classifyKind(StringRef Path) const {
744 // Detect home directory
745 const char *Home = getenv(name: "HOME");
746 if (Home && Path.starts_with(Prefix: Home))
747 return PathType::User;
748
749 static const std::array<std::string, 5> UserPrefixes = {
750 "/usr/local", // often used by users for manual installs
751 "/opt/homebrew", // common on macOS
752 "/opt/local", // MacPorts
753 "/home", // Linux home dirs
754 "/Users", // macOS user dirs
755 };
756
757 for (const auto &Prefix : UserPrefixes) {
758 if (Path.starts_with(Prefix))
759 return PathType::User;
760 }
761
762 return PathType::System;
763}
764
765Expected<LibraryDepsInfo> parseMachODeps(const object::MachOObjectFile &Obj) {
766 LibraryDepsInfo Libdeps;
767 LLVM_DEBUG(dbgs() << "Parsing Mach-O dependencies...\n";);
768 for (const auto &Command : Obj.load_commands()) {
769 switch (Command.C.cmd) {
770 case MachO::LC_LOAD_DYLIB: {
771 MachO::dylib_command dylibCmd = Obj.getDylibIDLoadCommand(L: Command);
772 const char *name = Command.Ptr + dylibCmd.dylib.name;
773 Libdeps.addDep(s: name);
774 LLVM_DEBUG(dbgs() << " Found LC_LOAD_DYLIB: " << name << "\n";);
775 } break;
776 case MachO::LC_LOAD_WEAK_DYLIB:
777 case MachO::LC_REEXPORT_DYLIB:
778 case MachO::LC_LOAD_UPWARD_DYLIB:
779 case MachO::LC_LAZY_LOAD_DYLIB:
780 break;
781 case MachO::LC_RPATH: {
782 // Extract RPATH
783 MachO::rpath_command rpathCmd = Obj.getRpathCommand(L: Command);
784 const char *rpath = Command.Ptr + rpathCmd.path;
785 LLVM_DEBUG(dbgs() << " Found LC_RPATH: " << rpath << "\n";);
786
787 SmallVector<StringRef, 4> RawPaths;
788 SplitString(Source: StringRef(rpath), OutFragments&: RawPaths,
789 Delimiters: sys::EnvPathSeparator == ':' ? ":" : ";");
790
791 for (const auto &raw : RawPaths) {
792 Libdeps.addRPath(s: raw.str()); // Convert to std::string
793 LLVM_DEBUG(dbgs() << " Parsed RPATH entry: " << raw << "\n";);
794 }
795 break;
796 }
797 }
798 }
799
800 return Expected<LibraryDepsInfo>(std::move(Libdeps));
801}
802
803template <class ELFT>
804static Expected<StringRef> getDynamicStrTab(const object::ELFFile<ELFT> &Elf) {
805 auto DynamicEntriesOrError = Elf.dynamicEntries();
806 if (!DynamicEntriesOrError)
807 return DynamicEntriesOrError.takeError();
808
809 for (const typename ELFT::Dyn &Dyn : *DynamicEntriesOrError) {
810 if (Dyn.d_tag == ELF::DT_STRTAB) {
811 auto MappedAddrOrError = Elf.toMappedAddr(Dyn.getPtr());
812 if (!MappedAddrOrError)
813 return MappedAddrOrError.takeError();
814 return StringRef(reinterpret_cast<const char *>(*MappedAddrOrError));
815 }
816 }
817
818 // If the dynamic segment is not present, we fall back on the sections.
819 auto SectionsOrError = Elf.sections();
820 if (!SectionsOrError)
821 return SectionsOrError.takeError();
822
823 for (const typename ELFT::Shdr &Sec : *SectionsOrError) {
824 if (Sec.sh_type == ELF::SHT_DYNSYM)
825 return Elf.getStringTableForSymtab(Sec);
826 }
827
828 return make_error<StringError>(Args: "dynamic string table not found",
829 Args: inconvertibleErrorCode());
830}
831
832template <typename ELFT>
833Expected<LibraryDepsInfo> parseELF(const object::ELFFile<ELFT> &Elf) {
834 LibraryDepsInfo Deps;
835 Expected<StringRef> StrTabOrErr = getDynamicStrTab(Elf);
836 if (!StrTabOrErr)
837 return StrTabOrErr.takeError();
838
839 const char *Data = StrTabOrErr->data();
840
841 auto DynamicEntriesOrError = Elf.dynamicEntries();
842 if (!DynamicEntriesOrError) {
843 return DynamicEntriesOrError.takeError();
844 }
845
846 for (const typename ELFT::Dyn &Dyn : *DynamicEntriesOrError) {
847 switch (Dyn.d_tag) {
848 case ELF::DT_NEEDED:
849 Deps.addDep(s: Data + Dyn.d_un.d_val);
850 break;
851 case ELF::DT_RPATH: {
852 SmallVector<StringRef, 4> RawPaths;
853 SplitString(Data + Dyn.d_un.d_val, RawPaths,
854 sys::EnvPathSeparator == ':' ? ":" : ";");
855 for (const auto &raw : RawPaths)
856 Deps.addRPath(s: raw.str());
857 break;
858 }
859 case ELF::DT_RUNPATH: {
860 SmallVector<StringRef, 4> RawPaths;
861 SplitString(Data + Dyn.d_un.d_val, RawPaths,
862 sys::EnvPathSeparator == ':' ? ":" : ";");
863 for (const auto &raw : RawPaths)
864 Deps.addRunPath(s: raw.str());
865 break;
866 }
867 case ELF::DT_FLAGS_1:
868 // Check if this is not a pie executable.
869 if (Dyn.d_un.d_val & ELF::DF_1_PIE)
870 Deps.isPIE = true;
871 break;
872 // (Dyn.d_tag == ELF::DT_NULL) continue;
873 // (Dyn.d_tag == ELF::DT_AUXILIARY || Dyn.d_tag == ELF::DT_FILTER)
874 default:
875 break;
876 }
877 }
878
879 return Expected<LibraryDepsInfo>(std::move(Deps));
880}
881
882Expected<LibraryDepsInfo> parseELFDeps(const object::ELFObjectFileBase &Obj) {
883 using namespace object;
884 LLVM_DEBUG(dbgs() << "parseELFDeps: Detected ELF object\n";);
885 if (const auto *ELF = dyn_cast<ELF32LEObjectFile>(Val: &Obj))
886 return parseELF(Elf: ELF->getELFFile());
887 else if (const auto *ELF = dyn_cast<ELF32BEObjectFile>(Val: &Obj))
888 return parseELF(Elf: ELF->getELFFile());
889 else if (const auto *ELF = dyn_cast<ELF64LEObjectFile>(Val: &Obj))
890 return parseELF(Elf: ELF->getELFFile());
891 else if (const auto *ELF = dyn_cast<ELF64BEObjectFile>(Val: &Obj))
892 return parseELF(Elf: ELF->getELFFile());
893
894 LLVM_DEBUG(dbgs() << "parseELFDeps: Unknown ELF format\n";);
895 return createStringError(EC: std::errc::not_supported, Fmt: "Unknown ELF format");
896}
897
898Expected<LibraryDepsInfo> parseDependencies(StringRef FilePath,
899 object::ObjectFile *Obj) {
900
901 if (auto *elfObj = dyn_cast<object::ELFObjectFileBase>(Val: Obj)) {
902 LLVM_DEBUG(dbgs() << "extractDeps: File " << FilePath
903 << " is an ELF object\n";);
904
905 return parseELFDeps(Obj: *elfObj);
906 }
907
908 if (auto *macho = dyn_cast<object::MachOObjectFile>(Val: Obj)) {
909 LLVM_DEBUG(dbgs() << "extractDeps: File " << FilePath
910 << " is a Mach-O object\n";);
911 return parseMachODeps(Obj: *macho);
912 }
913
914 if (Obj->isCOFF()) {
915 // TODO: COFF support
916 return LibraryDepsInfo();
917 }
918
919 LLVM_DEBUG(dbgs() << "extractDeps: Unsupported binary format for file "
920 << FilePath << "\n";);
921 return createStringError(EC: inconvertibleErrorCode(),
922 Fmt: "Unsupported binary format: %s",
923 Vals: FilePath.str().c_str());
924}
925
926Expected<LibraryDepsInfo> LibraryScanner::extractDeps(StringRef FilePath) {
927 LLVM_DEBUG(dbgs() << "extractDeps: Attempting to open file " << FilePath
928 << "\n";);
929 // check cache first
930 if (auto Cached = ObjCache.take(Path: FilePath)) {
931 auto ObjOrErr = Cached->getObjectFile();
932 if (!ObjOrErr)
933 return ObjOrErr.takeError();
934 return parseDependencies(FilePath, Obj: &*ObjOrErr);
935 }
936
937 // fall back to normal loading
938 ObjectFileLoader ObjLoader(FilePath);
939 auto ObjOrErr = ObjLoader.getObjectFile();
940 if (!ObjOrErr) {
941 LLVM_DEBUG(dbgs() << "extractDeps: Failed to open " << FilePath << "\n";);
942 return ObjOrErr.takeError();
943 }
944
945 return parseDependencies(FilePath, Obj: &*ObjOrErr);
946}
947
948bool LibraryScanner::shouldScan(StringRef FilePath, bool IsResolvingDep) {
949 LLVM_DEBUG(dbgs() << "[shouldScan] Checking: " << FilePath << "\n";);
950
951 LibraryPathCache &Cache = ScanHelper.getCache();
952 // [1] Skip if we've already seen this path (via cache)
953 if (Cache.hasSeen(CanonPath: FilePath)) {
954 LLVM_DEBUG(dbgs() << " -> Skipped: already seen.\n";);
955 return false;
956 }
957
958 // [2] Already tracked in LibraryManager?
959 /*if (LibMgr.hasLibrary(FilePath)) {
960 LLVM_DEBUG(dbgs() << " -> Skipped: already tracked by LibraryManager.\n";);
961 return false;
962 }*/
963
964 // [3] Skip if it's not a shared library.
965 if (!IsResolvingDep && !Validator.isSharedLibrary(Path: FilePath)) {
966 LLVM_DEBUG(dbgs() << " -> Skipped: not a shared library.\n";);
967 return false;
968 }
969
970 // Mark seen this path
971 Cache.markSeen(CanonPath: FilePath.str());
972
973 // [4] Run user-defined hook (default: always true)
974 if (!ShouldScanCall(FilePath)) {
975 LLVM_DEBUG(dbgs() << " -> Skipped: user-defined hook rejected.\n";);
976 return false;
977 }
978
979 LLVM_DEBUG(dbgs() << " -> Accepted: ready to scan " << FilePath << "\n";);
980 return true;
981}
982
983void LibraryScanner::handleLibrary(StringRef FilePath, PathType K, int level) {
984 LLVM_DEBUG(dbgs() << "LibraryScanner::handleLibrary: Scanning: " << FilePath
985 << ", level=" << level << "\n";);
986 if (!shouldScan(FilePath, IsResolvingDep: level > 0)) {
987 LLVM_DEBUG(dbgs() << " Skipped (shouldScan returned false): " << FilePath
988 << "\n";);
989 return;
990 }
991
992 auto DepsOrErr = extractDeps(FilePath);
993 if (!DepsOrErr) {
994 LLVM_DEBUG(dbgs() << " Failed to extract deps for: " << FilePath << "\n";);
995 handleError(Err: DepsOrErr.takeError());
996 return;
997 }
998
999 LibraryDepsInfo &Deps = *DepsOrErr;
1000
1001 LLVM_DEBUG({
1002 dbgs() << " Found deps : \n";
1003 for (const auto &dep : Deps.deps)
1004 dbgs() << " : " << dep << "\n";
1005 dbgs() << " Found @rpath : " << Deps.rpath.size() << "\n";
1006 for (const auto &r : Deps.rpath)
1007 dbgs() << " : " << r << "\n";
1008 dbgs() << " Found @runpath : \n";
1009 for (const auto &r : Deps.runPath)
1010 dbgs() << " : " << r << "\n";
1011 });
1012
1013 if (Deps.isPIE && level == 0) {
1014 LLVM_DEBUG(dbgs() << " Skipped PIE executable at top level: " << FilePath
1015 << "\n";);
1016
1017 return;
1018 }
1019
1020 bool Added = LibMgr.addLibrary(Path: FilePath.str(), Kind: K);
1021 if (!Added) {
1022 LLVM_DEBUG(dbgs() << " Already added: " << FilePath << "\n";);
1023 return;
1024 }
1025
1026 // Heuristic 1: No RPATH/RUNPATH, skip deps
1027 if (Deps.rpath.empty() && Deps.runPath.empty()) {
1028 LLVM_DEBUG(
1029 dbgs() << "LibraryScanner::handleLibrary: Skipping deps (Heuristic1): "
1030 << FilePath << "\n";);
1031 return;
1032 }
1033
1034 // Heuristic 2: All RPATH and RUNPATH already tracked
1035 auto allTracked = [&](const auto &Paths) {
1036 LLVM_DEBUG(dbgs() << " Checking : " << Paths.size() << "\n";);
1037 return std::all_of(Paths.begin(), Paths.end(), [&](StringRef P) {
1038 LLVM_DEBUG(dbgs() << " Checking isTrackedBasePath : " << P << "\n";);
1039 return ScanHelper.isTrackedBasePath(
1040 Path: DylibResolver::resolvelinkerFlag(libStem: P, loaderPath: FilePath));
1041 });
1042 };
1043
1044 if (allTracked(Deps.rpath) && allTracked(Deps.runPath)) {
1045 LLVM_DEBUG(
1046 dbgs() << "LibraryScanner::handleLibrary: Skipping deps (Heuristic2): "
1047 << FilePath << "\n";);
1048 return;
1049 }
1050
1051 DylibResolver Resolver(Validator);
1052 Resolver.configure(loaderPath: FilePath,
1053 SearchPathCfg: {{.Paths: Deps.rpath, .type: SearchPathType::RPath},
1054 {.Paths: ScanHelper.getSearchPaths(), .type: SearchPathType::UsrOrSys},
1055 {.Paths: Deps.runPath, .type: SearchPathType::RunPath}});
1056 for (StringRef Dep : Deps.deps) {
1057 LLVM_DEBUG(dbgs() << " Resolving dep: " << Dep << "\n";);
1058 auto DepFullOpt = Resolver.resolve(libStem: Dep);
1059 if (!DepFullOpt) {
1060 LLVM_DEBUG(dbgs() << " Failed to resolve dep: " << Dep << "\n";);
1061 continue;
1062 }
1063 LLVM_DEBUG(dbgs() << " Resolved dep to: " << *DepFullOpt << "\n";);
1064
1065 handleLibrary(FilePath: *DepFullOpt, K, level: level + 1);
1066 }
1067}
1068
1069void LibraryScanner::scanBaseDir(LibrarySearchPath *SP) {
1070 if (!sys::fs::is_directory(Path: SP->BasePath) || SP->BasePath.empty()) {
1071 LLVM_DEBUG(
1072 dbgs() << "LibraryScanner::scanBaseDir: Invalid or empty basePath: "
1073 << SP->BasePath << "\n";);
1074 return;
1075 }
1076
1077 LLVM_DEBUG(dbgs() << "LibraryScanner::scanBaseDir: Scanning directory: "
1078 << SP->BasePath << "\n";);
1079 std::error_code EC;
1080
1081 SP->State.store(i: ScanState::Scanning);
1082
1083 for (sys::fs::directory_iterator It(SP->BasePath, EC), end; It != end && !EC;
1084 It.increment(ec&: EC)) {
1085 auto Entry = *It;
1086 if (!Entry.status())
1087 continue;
1088
1089 auto Status = *Entry.status();
1090 if (sys::fs::is_regular_file(status: Status) || sys::fs::is_symlink_file(status: Status)) {
1091 LLVM_DEBUG(dbgs() << " Found file: " << Entry.path() << "\n";);
1092
1093 std::string FinalPath;
1094 bool IsSymlink = sys::fs::is_symlink_file(status: Status);
1095
1096 // Resolve symlink
1097 if (IsSymlink) {
1098 LLVM_DEBUG(dbgs() << " Symlink → resolving...\n");
1099
1100 auto CanonicalOpt = ScanHelper.resolve(P: Entry.path(), ec&: EC);
1101 if (EC || !CanonicalOpt) {
1102 LLVM_DEBUG(dbgs() << " -> Skipped: resolve failed (EC="
1103 << EC.message() << ")\n");
1104 continue;
1105 }
1106
1107 FinalPath = std::move(*CanonicalOpt);
1108
1109 LLVM_DEBUG(dbgs() << " Canonical: " << FinalPath << "\n");
1110
1111 } else {
1112 // make absolute
1113 SmallString<256> Abs(Entry.path());
1114 sys::fs::make_absolute(path&: Abs);
1115 FinalPath = Abs.str().str();
1116
1117 LLVM_DEBUG(dbgs() << " Regular: absolute = " << FinalPath << "\n");
1118 }
1119
1120 // Check if it's a directory — skip directories
1121 if (sys::fs::is_directory(status: Status)) {
1122 LLVM_DEBUG(dbgs() << " -> Skipped: path is a directory.\n";);
1123 continue;
1124 }
1125
1126 // async support ?
1127 handleLibrary(FilePath: FinalPath, K: SP->Kind);
1128 }
1129 }
1130
1131 SP->State.store(i: ScanState::Scanned);
1132}
1133
1134void LibraryScanner::scanNext(PathType K, size_t BatchSize) {
1135 LLVM_DEBUG(dbgs() << "LibraryScanner::scanNext: Scanning next batch of size "
1136 << BatchSize << " for kind "
1137 << (K == PathType::User ? "User" : "System") << "\n";);
1138
1139 SmallVector<const LibrarySearchPath *> SearchPaths;
1140 ScanHelper.getNextBatch(K, BatchSize, Result&: SearchPaths);
1141 for (const auto *SP : SearchPaths) {
1142 LLVM_DEBUG(dbgs() << " Scanning unit with basePath: " << SP->BasePath
1143 << "\n";);
1144 scanBaseDir(SP: const_cast<LibrarySearchPath *>(SP));
1145 }
1146}
1147} // end namespace llvm::orc
1148