Index: include/lld/ReaderWriter/MachOLinkingContext.h =================================================================== --- include/lld/ReaderWriter/MachOLinkingContext.h +++ include/lld/ReaderWriter/MachOLinkingContext.h @@ -17,6 +17,8 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/MachO.h" +#include + using llvm::MachO::HeaderFileType; namespace lld { @@ -78,6 +80,38 @@ void setDoNothing(bool value) { _doNothing = value; } bool doNothing() const { return _doNothing; } bool printAtoms() const { return _printAtoms; } + bool testingLibResolution() const { return _testingLibResolution; } + const StringRefVector &searchDirs() const { return _searchDirs; } + + /// \brief Checks whether a given path on the filesystem exists. + /// + /// When running in -test_libresolution mode, this method consults an + /// internally maintained list of files that exist (provided by -path_exists) + /// instead of the actual filesystem. + bool pathExists(StringRef path) const; + + /// \brief Adds any library search paths derived from the given base, possibly + /// modified by -syslibroots. + /// + /// The set of paths added consists of approximately all syslibroot-prepended + /// versions of libPath that exist, or the original libPath if there are none + /// for whatever reason. With various edge-cases for compatibility. + void addModifiedSearchDir(StringRef libPath, + const StringRefVector &syslibRoots, + bool isSystemPath = false); + + /// \brief Determine whether -lFoo can be resolve within the given path, and + /// return the filename if so. + /// + /// The -lFoo option is documented to search for libFoo.dylib and libFoo.a in + /// that order, unless Foo ends in ".o", in which case only the exact file + /// matches (e.g. -lfoo.o would only find foo.o). + ErrorOr searchDirForLibrary(StringRef path, + StringRef libName) const; + + /// \brief Iterates through all search path entries lookinf for libName (as + /// specified by -lFoo). + ErrorOr searchLibrary(StringRef libName) const; /// \brief The dylib's binary compatibility version, in the raw uint32 format. /// @@ -125,6 +159,13 @@ } void setBundleLoader(StringRef loader) { _bundleLoader = loader; } void setPrintAtoms(bool value=true) { _printAtoms = value; } + void setTestingLibResolution(bool value = true) { + _testingLibResolution = value; + } + void addExistingPathForDebug(StringRef path) { + _existingPaths.insert(path); + } + StringRef dyldPath() const { return "/usr/lib/dyld"; } static Arch archFromCpuType(uint32_t cputype, uint32_t cpusubtype); @@ -153,6 +194,8 @@ static ArchInfo _s_archInfos[]; + std::set _existingPaths; // For testing only. + StringRefVector _searchDirs; HeaderFileType _outputMachOType; // e.g MH_EXECUTE bool _outputMachOTypeStatic; // Disambiguate static vs dynamic prog bool _doNothing; // for -help and -v which just print info @@ -166,6 +209,7 @@ StringRef _installName; bool _deadStrippableDylib; bool _printAtoms; + bool _testingLibResolution; StringRef _bundleLoader; mutable std::unique_ptr _kindHandler; mutable std::unique_ptr _writer; Index: lib/Driver/DarwinLdDriver.cpp =================================================================== --- lib/Driver/DarwinLdDriver.cpp +++ lib/Driver/DarwinLdDriver.cpp @@ -266,14 +266,81 @@ if (parsedArgs->getLastArg(OPT_print_atoms)) ctx.setPrintAtoms(); + // In -test_libresolution mode, we'll be given an explicit list of paths that + // exist. We'll also be expected to print out information about how we located + // libraries and so on that the user specified, but not to actually do any + // linking. + if (parsedArgs->getLastArg(OPT_test_libresolution)) { + ctx.setTestingLibResolution(); + + // With paths existing by fiat, linking is not going to end well. + ctx.setDoNothing(true); + + // Only bother looking for an existence override if we're going to use it. + for (llvm::opt::arg_iterator + it = parsedArgs->filtered_begin(OPT_path_exists), + ie = parsedArgs->filtered_end(); + it != ie; ++it) { + ctx.addExistingPathForDebug((*it)->getValue()); + } + } + std::unique_ptr inputGraph(new InputGraph()); - // Handle input files - for (llvm::opt::arg_iterator it = parsedArgs->filtered_begin(OPT_INPUT), + // Now construct the set of library search directories, following ld64's + // baroque set of accumulated hacks. Mostly, the algorithm constructs + // { syslibroots } x { libpaths } + // + // Unfortunately, there are numerous exceptions: + // 1. Only absolute paths get modified by syslibroot options. + // 2. If there is just 1 -syslibroot, system paths not found in it are + // skipped. + // 3. If the last -syslibroot is "/", all of them are ignored entirely. + // 4. If { syslibroots } x path == {}, the original path is kept. + std::vector syslibRoots; + for (llvm::opt::arg_iterator it = parsedArgs->filtered_begin(OPT_syslibroot), ie = parsedArgs->filtered_end(); - it != ie; ++it) { + it != ie; ++it) { + syslibRoots.push_back((*it)->getValue()); + } + + // FIXME: handle -L options: these get added *before* the default paths, + // possibly modified by any syslibroot options. + ctx.addModifiedSearchDir("/usr/lib", syslibRoots, true); + ctx.addModifiedSearchDir("/usr/local/lib", syslibRoots, true); + + // Now that we've constructed the final set of search paths, print out what + // we'll be using for testing purposes. + if (ctx.testingLibResolution()) { + diagnostics << "Library search paths:\n"; + for (auto path : ctx.searchDirs()) { + diagnostics << " " << path << '\n'; + } + } + + // Handle input files + for (auto &arg : *parsedArgs) { + StringRef inputPath; + switch (arg->getOption().getID()) { + default: + continue; + case OPT_INPUT: + inputPath = arg->getValue(); + break; + case OPT_l: { + ErrorOr resolvedPath = ctx.searchLibrary(arg->getValue()); + if (!resolvedPath) { + diagnostics << "Unable to find library -l" << arg->getValue() << "\n"; + return false; + } else if (ctx.testingLibResolution()) { + diagnostics << "Found library " << resolvedPath.get() << '\n'; + } + inputPath = resolvedPath.get(); + break; + } + } inputGraph->addInputElement(std::unique_ptr( - new MachOFileNode(ctx, (*it)->getValue(), globalWholeArchive))); + new MachOFileNode(ctx, inputPath, globalWholeArchive))); } if (!inputGraph->size()) { Index: lib/Driver/DarwinLdOptions.td =================================================================== --- lib/Driver/DarwinLdOptions.td +++ lib/Driver/DarwinLdOptions.td @@ -69,10 +69,23 @@ def all_load : Flag<["-"], "all_load">, HelpText<"Forces all members of all static libraries to be loaded">, Group; +def syslibroot : Separate<["-"], "syslibroot">, + HelpText<"Add prefix to all system library search paths">, + Group; + +// Input options +def l : Joined<["-"], "l">, + HelpText<"Root name of library to searchf for">; // test case options -def print_atoms : Flag<["-"], "print_atoms">, +def print_atoms : Flag<["-"], "print_atoms">, HelpText<"Emit output as yaml atoms">; +def test_libresolution : Flag<["-"], "test_libresolution">, + HelpText<"Only files specified by -file_exists are considered to exist." + " Print debugging information during resolution">; +def path_exists : Separate<["-"], "path_exists">, + HelpText<"When used with -test_libresolution, only these paths exist">; + // general options def output : Separate<["-"], "o">, HelpText<"Output file path">; Index: lib/ReaderWriter/MachO/MachOLinkingContext.cpp =================================================================== --- lib/ReaderWriter/MachO/MachOLinkingContext.cpp +++ lib/ReaderWriter/MachO/MachOLinkingContext.cpp @@ -20,8 +20,10 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/Triple.h" +#include "llvm/Support/Errc.h" #include "llvm/Support/Host.h" #include "llvm/Support/MachO.h" +#include "llvm/Support/Path.h" using lld::mach_o::KindHandler; using namespace llvm::MachO; @@ -123,7 +125,7 @@ _doNothing(false), _arch(arch_unknown), _os(OS::macOSX), _osMinVersion(0), _pageZeroSize(0), _pageSize(4096), _compatibilityVersion(0), _currentVersion(0), _deadStrippableDylib(false), _printAtoms(false), - _kindHandler(nullptr) {} + _testingLibResolution(false), _kindHandler(nullptr) {} MachOLinkingContext::~MachOLinkingContext() {} @@ -262,6 +264,87 @@ } } +bool MachOLinkingContext::pathExists(StringRef path) const { + if (!testingLibResolution()) + return llvm::sys::fs::exists(path.str()); + + // Otherwise, we're in test mode: only files explicitly provided on the + // command-line exist. + return _existingPaths.find(path) != _existingPaths.end(); +} + +void MachOLinkingContext::addModifiedSearchDir( + StringRef libPath, const StringRefVector &syslibRoots, bool isSystemPath) { + bool addedModifiedPath = false; + + // Two cases to consider here: + // + If the last -syslibroot is "/", all of them are ignored (don't ask). + // + -syslibroot only applies to absolute paths. + if (!syslibRoots.empty() && syslibRoots.back() != "/" && + llvm::sys::path::is_absolute(libPath)) { + for (auto syslibRoot : syslibRoots) { + SmallString<128> path(syslibRoot); + llvm::sys::path::append(path, libPath); + if (pathExists(path)) { + _searchDirs.push_back(path.str().copy(_allocator)); + addedModifiedPath = true; + } + } + } + + if (addedModifiedPath) + return; + + // Finally, if only one -syslibroot is given, system paths which aren't in it + // get suppressed. + if (syslibRoots.size() != 1 || !isSystemPath) { + if (pathExists(libPath)) { + _searchDirs.push_back(libPath); + } + } +} + +ErrorOr +MachOLinkingContext::searchDirForLibrary(StringRef path, + StringRef libName) const { + SmallString<128> fullPath; + if (libName.endswith(".o")) { + // A request ending in .o is special: just search for the file directly. + fullPath.assign(path); + llvm::sys::path::append(fullPath, libName); + if (pathExists(fullPath)) + return fullPath.str().copy(_allocator); + return make_error_code(llvm::errc::no_such_file_or_directory); + } + + // Search for dynamic library + fullPath.assign(path); + llvm::sys::path::append(fullPath, Twine("lib") + libName + ".dylib"); + if (pathExists(fullPath)) + return fullPath.str().copy(_allocator); + + // If not, try for a static library + fullPath.assign(path); + llvm::sys::path::append(fullPath, Twine("lib") + libName + ".a"); + if (pathExists(fullPath)) + return fullPath.str().copy(_allocator); + + return make_error_code(llvm::errc::no_such_file_or_directory); +} + + + +ErrorOr MachOLinkingContext::searchLibrary(StringRef libName) const { + SmallString<128> path; + for (StringRef dir : searchDirs()) { + ErrorOr ec = searchDirForLibrary(dir, libName); + if (ec) + return ec; + } + + return make_error_code(llvm::errc::no_such_file_or_directory); +} + bool MachOLinkingContext::validateImpl(raw_ostream &diagnostics) { // TODO: if -arch not specified, look at arch of first .o file. Index: test/mach-o/lib-search-paths.yaml =================================================================== --- /dev/null +++ test/mach-o/lib-search-paths.yaml @@ -0,0 +1,16 @@ +# RUN: lld -flavor darwin -arch x86_64 %s -syslibroot %p/Inputs/lib-search-paths -lmyshared -lmystatic -lfile.o -print_atoms 2>&1 -r | FileCheck %s + +--- !native +undefined-atoms: + - name: _from_myshared + - name: _from_mystatic + - name: _from_fileo + +# CHECK: defined-atoms: +# CHECK: - name: _from_mystatic +# CHECK: content: [ 02, 00, 00, 00 ] +# CHECK: - name: _from_fileo +# CHECK: content: [ 2A, 00, 00, 00 ] +# CHECK: shared-library-atoms: +# CHECK: - name: _from_myshared +# CHECK: load-name: libmyshared.dylib Index: test/mach-o/libresolve-bizarre-root-override.yaml =================================================================== --- /dev/null +++ test/mach-o/libresolve-bizarre-root-override.yaml @@ -0,0 +1,17 @@ +# RUN: not lld -flavor darwin -test_libresolution \ +# RUN: -path_exists /usr/lib \ +# RUN: -path_exists /Applications/MySDK/usr/local/lib \ +# RUN: -path_exists /Applications/MySDK/usr/lib \ +# RUN: -path_exists /Applications/MySDK/usr/lib/libSystem.dylib \ +# RUN: -syslibroot /Applications/MySDK \ +# RUN: -syslibroot / \ +# RUN: -lSystem \ +# RUN: 2>&1 | FileCheck %s + +# When the last -syslibroot is simply "/", all of them get discarded. So in this +# case, only /usr/lib should show up. + +# CHECK: Library search paths: +# CHECK: /usr/lib +# CHECK-NOT: /usr/local/lib +# CHECK: Unable to find library -lSystem Index: test/mach-o/libresolve-multiple-syslibroots.yaml =================================================================== --- /dev/null +++ test/mach-o/libresolve-multiple-syslibroots.yaml @@ -0,0 +1,17 @@ +# RUN: lld -flavor darwin -test_libresolution \ +# RUN: -path_exists /usr/lib \ +# RUN: -path_exists /Applications/MyFirstSDK/usr/local/lib \ +# RUN: -path_exists /Applications/MySecondSDK/usr/local/lib \ +# RUN: -path_exists /Applications/MyFirstSDK/usr/local/lib/libSystem.a \ +# RUN: -path_exists /Applications/MySecondSDK/usr/local/lib/libSystem.a \ +# RUN: -syslibroot /Applications/MyFirstSDK \ +# RUN: -syslibroot /Applications/MySecondSDK \ +# RUN: -lSystem \ +# RUN: 2>&1 | FileCheck %s + + +# CHECK: Library search paths: +# CHECK: /usr/lib +# CHECK: /Applications/MyFirstSDK/usr/local/lib +# CHECK: /Applications/MySecondSDK/usr/local/lib +# CHECK: Found library /Applications/MyFirstSDK/usr/local/lib/libSystem.a Index: test/mach-o/libresolve-one-syslibroot.yaml =================================================================== --- /dev/null +++ test/mach-o/libresolve-one-syslibroot.yaml @@ -0,0 +1,19 @@ +# RUN: lld -flavor darwin -test_libresolution \ +# RUN: -path_exists /usr/lib \ +# RUN: -path_exists /Applications/MySDK/usr/local/lib \ +# RUN: -path_exists /Applications/MySDK/usr/local/lib/libSystem.a \ +# RUN: -syslibroot /Applications/MySDK \ +# RUN: -lSystem \ +# RUN: 2>&1 | FileCheck %s + +# When just one -syslibroot is specified, we apparently want to skip *system* +# paths that aren't found. User ones should still get added. In this case +# /usr/lib exists, but not the equivalent in the -syslibroot, so there should be +# no mention of /usr/lib. + +# CHECK: Library search paths: +# CHECK-NOT: /usr/lib +# CHECK-NOT: /usr/local/lib +# CHECK: /Applications/MySDK/usr/local/lib +# CHECK-NOT: /usr/local/lib +# CHECK: Found library /Applications/MySDK/usr/local/lib/libSystem.a Index: test/mach-o/libresolve-simple.yaml =================================================================== --- /dev/null +++ test/mach-o/libresolve-simple.yaml @@ -0,0 +1,11 @@ +# RUN: lld -flavor darwin -arch x86_64 -r -test_libresolution \ +# RUN: -path_exists /usr/lib \ +# RUN: -path_exists /usr/local/lib \ +# RUN: -path_exists /usr/lib/libSystem.dylib \ +# RUN: -lSystem \ +# RUN: 2>&1 | FileCheck %s + +# CHECK: Library search paths: +# CHECK: /usr/lib +# CHECK: /usr/local/lib +# CHECK: Found library /usr/lib/libSystem.dylib Index: test/mach-o/libresolve-syslibroot.yaml =================================================================== --- /dev/null +++ test/mach-o/libresolve-syslibroot.yaml @@ -0,0 +1,11 @@ +# RUN: lld -flavor darwin -arch x86_64 -r -test_libresolution \ +# RUN: -path_exists /usr/lib \ +# RUN: -path_exists /usr/local/lib \ +# RUN: -path_exists /usr/lib/libSystem.dylib \ +# RUN: -lSystem \ +# RUN: 2>&1 | FileCheck %s + +# CHECK: Library search paths: +# CHECK: /usr/lib +# CHECK: /usr/local/lib +# CHECK: Found library /usr/lib/libSystem.dylib