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