CppInterOp
C++ Language Interoperability Layer
Loading...
Searching...
No Matches
DynamicLibraryManager.cpp
Go to the documentation of this file.
1//------------------------------------------------------------------------------
2// CLING - the C++ LLVM-based InterpreterG :)
3// author: Vassil Vassilev <vasil.georgiev.vasilev@cern.ch>
4//
5// This file is dual-licensed: you can choose to license it under the University
6// of Illinois Open Source License or the GNU Lesser General Public License. See
7// LICENSE.TXT for details.
8//------------------------------------------------------------------------------
9
11#include "Compatibility.h"
12#include "Paths.h"
13
14#include "llvm/ADT/StringSet.h"
15#include "llvm/BinaryFormat/Magic.h"
16#include "llvm/Support/Debug.h"
17#include "llvm/Support/DynamicLibrary.h"
18#include "llvm/Support/FileSystem.h"
19#include "llvm/Support/Path.h"
20
21#if defined(_WIN32)
22#include "llvm/BinaryFormat/COFF.h"
23#include "llvm/Support/Endian.h"
24#endif
25
26#include <fstream>
27#include <sys/stat.h>
28#include <system_error>
29
30namespace CppInternal {
31
32using namespace utils;
33using namespace llvm;
34
36 const SmallVector<const char*, 10> kSysLibraryEnv = {
37 "LD_LIBRARY_PATH",
38#if __APPLE__
39 "DYLD_LIBRARY_PATH",
40 "DYLD_FALLBACK_LIBRARY_PATH",
41 /*
42 "DYLD_VERSIONED_LIBRARY_PATH",
43 "DYLD_FRAMEWORK_PATH",
44 "DYLD_FALLBACK_FRAMEWORK_PATH",
45 "DYLD_VERSIONED_FRAMEWORK_PATH",
46 */
47#elif defined(_WIN32)
48 "PATH",
49#endif
50 };
51
52 // Behaviour is to not add paths that don't exist...In an interpreted env
53 // does this make sense? Path could pop into existence at any time.
54 for (const char* Var : kSysLibraryEnv) {
55 if (const char* Env = GetEnv(Var)) {
56 SmallVector<StringRef, 10> CurPaths;
57 SplitPaths(Env, CurPaths, SplitMode::kPruneNonExistent,
59 for (const auto& Path : CurPaths)
60 addSearchPath(Path);
61 }
62 }
63
64 // $CWD is the last user path searched.
65 addSearchPath(".");
66
67 SmallVector<std::string, 64> SysPaths;
69
70 for (const std::string& P : SysPaths)
71 addSearchPath(P, /*IsUser*/ false);
72}
73///\returns substitution of pattern in the front of original with replacement
74/// Example: substFront("@rpath/abc", "@rpath/", "/tmp") -> "/tmp/abc"
75static std::string substFront(StringRef original, StringRef pattern,
76 StringRef replacement) {
77 if (!original.starts_with_insensitive(pattern))
78 return original.str();
79 SmallString<512> result(replacement);
80 result.append(original.drop_front(pattern.size()));
81 return result.str().str();
82}
83
84///\returns substitution of all known linker variables in \c original
85static std::string substAll(StringRef original, StringRef libLoader) {
86
87 // Handle substitutions (MacOS):
88 // @rpath - This function does not substitute @rpath, because
89 // this variable is already handled by lookupLibrary where
90 // @rpath is replaced with all paths from RPATH one by one.
91 // @executable_path - Main program path.
92 // @loader_path - Loader library (or main program) path.
93 //
94 // Handle substitutions (Linux):
95 // https://man7.org/linux/man-pages/man8/ld.so.8.html
96 // $origin - Loader library (or main program) path.
97 // $lib - lib lib64
98 // $platform - x86_64 AT_PLATFORM
99
100 std::string result;
101#ifdef __APPLE__
102 SmallString<512> mainExecutablePath(
103 llvm::sys::fs::getMainExecutable(nullptr, nullptr));
104 llvm::sys::path::remove_filename(mainExecutablePath);
105 SmallString<512> loaderPath;
106 if (libLoader.empty()) {
107 loaderPath = mainExecutablePath;
108 } else {
109 loaderPath = libLoader.str();
110 llvm::sys::path::remove_filename(loaderPath);
111 }
112
113 result = substFront(original, "@executable_path", mainExecutablePath);
114 result = substFront(result, "@loader_path", loaderPath);
115 return result;
116#else
117 SmallString<512> loaderPath;
118 if (libLoader.empty()) {
119 loaderPath = llvm::sys::fs::getMainExecutable(nullptr, nullptr);
120 } else {
121 loaderPath = libLoader.str();
122 }
123 llvm::sys::path::remove_filename(loaderPath);
124
125 result = substFront(original, "$origin", loaderPath);
126 // result = substFront(result, "$lib", true?"lib":"lib64");
127 // result = substFront(result, "$platform", "x86_64");
128 return result;
129#endif
130}
131
132std::string DynamicLibraryManager::lookupLibInPaths(
133 StringRef libStem, SmallVector<llvm::StringRef, 2> RPath /*={}*/,
134 SmallVector<llvm::StringRef, 2> RunPath /*={}*/,
135 StringRef libLoader /*=""*/) const {
136#define DEBUG_TYPE "Dyld::lookupLibInPaths"
137
138 LLVM_DEBUG(dbgs() << "Dyld::lookupLibInPaths" << libStem.str()
139 << ", ..., libLoader=" << libLoader << "\n");
140
141 // Lookup priority is: RPATH, LD_LIBRARY_PATH/m_SearchPaths, RUNPATH
142 // See: https://en.wikipedia.org/wiki/Rpath
143 // See: https://amir.rachum.com/blog/2016/09/17/shared-libraries/
144
145 LLVM_DEBUG(dbgs() << "Dyld::lookupLibInPaths: \n");
146 LLVM_DEBUG(dbgs() << ":: RPATH\n");
147#ifndef NDEBUG
148 for (auto Info : RPath) {
149 LLVM_DEBUG(dbgs() << ":::: " << Info.str() << "\n");
150 }
151#endif
152 LLVM_DEBUG(dbgs() << ":: SearchPaths (LD_LIBRARY_PATH, etc...)\n");
153 for (auto Info : getSearchPaths()) {
154 LLVM_DEBUG(dbgs() << ":::: " << Info.Path
155 << ", user=" << (Info.IsUser ? "true" : "false") << "\n");
156 }
157 LLVM_DEBUG(dbgs() << ":: RUNPATH\n");
158#ifndef NDEBUG
159 for (auto Info : RunPath) {
160 LLVM_DEBUG(dbgs() << ":::: " << Info.str() << "\n");
161 }
162#endif
163 SmallString<512> ThisPath;
164 // RPATH
165 for (auto Info : RPath) {
166 ThisPath = substAll(Info, libLoader);
167 llvm::sys::path::append(ThisPath, libStem);
168 // to absolute path?
169 LLVM_DEBUG(dbgs() << "## Try: " << ThisPath);
170 if (isSharedLibrary(ThisPath.str())) {
171 LLVM_DEBUG(dbgs() << " ... Found (in RPATH)!\n");
172 return ThisPath.str().str();
173 }
174 }
175 // m_SearchPaths
176 for (const SearchPathInfo& Info : m_SearchPaths) {
177 ThisPath = Info.Path;
178 llvm::sys::path::append(ThisPath, libStem);
179 // to absolute path?
180 LLVM_DEBUG(dbgs() << "## Try: " << ThisPath);
181 if (isSharedLibrary(ThisPath.str())) {
182 LLVM_DEBUG(dbgs() << " ... Found (in SearchPaths)!\n");
183 return ThisPath.str().str();
184 }
185 }
186 // RUNPATH
187 for (auto Info : RunPath) {
188 ThisPath = substAll(Info, libLoader);
189 llvm::sys::path::append(ThisPath, libStem);
190 // to absolute path?
191 LLVM_DEBUG(dbgs() << "## Try: " << ThisPath);
192 if (isSharedLibrary(ThisPath.str())) {
193 LLVM_DEBUG(dbgs() << " ... Found (in RUNPATH)!\n");
194 return ThisPath.str().str();
195 }
196 }
197
198 LLVM_DEBUG(dbgs() << "## NotFound!!!\n");
199
200 return "";
201
202#undef DEBUG_TYPE
203}
204
205std::string DynamicLibraryManager::lookupLibMaybeAddExt(
206 StringRef libStem, SmallVector<llvm::StringRef, 2> RPath /*={}*/,
207 SmallVector<llvm::StringRef, 2> RunPath /*={}*/,
208 StringRef libLoader /*=""*/) const {
209#define DEBUG_TYPE "Dyld::lookupLibMaybeAddExt:"
210
211 using namespace llvm::sys;
212
213 LLVM_DEBUG(dbgs() << "Dyld::lookupLibMaybeAddExt: " << libStem.str()
214 << ", ..., libLoader=" << libLoader << "\n");
215
216 std::string foundDyLib = lookupLibInPaths(libStem, RPath, RunPath, libLoader);
217
218 if (foundDyLib.empty()) {
219 // Add DyLib extension:
220 SmallString<512> filenameWithExt(libStem);
221#if defined(LLVM_ON_UNIX)
222#ifdef __APPLE__
223 SmallString<512>::iterator IStemEnd = filenameWithExt.end() - 1;
224#endif
225 static const char* DyLibExt = ".so";
226#elif defined(_WIN32)
227 static const char* DyLibExt = ".dll";
228#else
229#error "Unsupported platform."
230#endif
231 filenameWithExt += DyLibExt;
232 foundDyLib = lookupLibInPaths(filenameWithExt, RPath, RunPath, libLoader);
233#ifdef __APPLE__
234 if (foundDyLib.empty()) {
235 filenameWithExt.erase(IStemEnd + 1, filenameWithExt.end());
236 filenameWithExt += ".dylib";
237 foundDyLib = lookupLibInPaths(filenameWithExt, RPath, RunPath, libLoader);
238 }
239#endif
240 }
241
242 if (foundDyLib.empty())
243 return std::string();
244
245 // get canonical path name and check if already loaded
246 const std::string Path = platform::NormalizePath(foundDyLib);
247 if (Path.empty()) {
248 LLVM_DEBUG(
249 dbgs() << "cling::DynamicLibraryManager::lookupLibMaybeAddExt(): "
250 << "error getting real (canonical) path of library "
251 << foundDyLib << '\n');
252 return foundDyLib;
253 }
254 return Path;
255
256#undef DEBUG_TYPE
257}
258
259std::string DynamicLibraryManager::normalizePath(StringRef path) {
260#define DEBUG_TYPE "Dyld::normalizePath:"
261 // Make the path canonical if the file exists.
262 const std::string Path = path.str();
263 struct stat buffer;
264 if (::stat(Path.c_str(), &buffer) != 0)
265 return std::string();
266
267 const std::string NPath = platform::NormalizePath(Path);
268 if (NPath.empty())
269 LLVM_DEBUG(dbgs() << "Could not normalize: '" << Path << "'");
270 return NPath;
271#undef DEBUG_TYPE
272}
273
274std::string RPathToStr2(SmallVector<StringRef, 2> V) {
275 std::string result;
276 for (auto item : V)
277 result += item.str() + ",";
278 if (!result.empty())
279 result.pop_back();
280 return result;
281}
282
284 StringRef libStem, SmallVector<llvm::StringRef, 2> RPath /*={}*/,
285 SmallVector<llvm::StringRef, 2> RunPath /*={}*/,
286 StringRef libLoader /*=""*/, bool variateLibStem /*=true*/) const {
287#define DEBUG_TYPE "Dyld::lookupLibrary:"
288 LLVM_DEBUG(dbgs() << "Dyld::lookupLibrary: " << libStem.str() << ", "
289 << RPathToStr2(RPath) << ", " << RPathToStr2(RunPath)
290 << ", " << libLoader.str() << "\n");
291
292 // If it is an absolute path, don't try iterate over the paths.
293 if (llvm::sys::path::is_absolute(libStem)) {
294 if (isSharedLibrary(libStem))
295 return normalizePath(libStem);
296
297 LLVM_DEBUG(dbgs() << "Dyld::lookupLibrary: '" << libStem.str() << "'"
298 << "is not a shared library\n");
299 return std::string();
300 }
301
302 // Subst all known linker variables ($origin, @rpath, etc.)
303#ifdef __APPLE__
304 // On MacOS @rpath is preplaced by all paths in RPATH one by one.
305 if (libStem.starts_with_insensitive("@rpath")) {
306 for (auto& P : RPath) {
307 std::string result = substFront(libStem, "@rpath", P);
308 if (isSharedLibrary(result))
309 return normalizePath(result);
310 }
311 } else {
312#endif
313 std::string result = substAll(libStem, libLoader);
314 if (isSharedLibrary(result))
315 return normalizePath(result);
316#ifdef __APPLE__
317 }
318#endif
319
320 // Expand libStem with paths, extensions, etc.
321 std::string foundName;
322 if (variateLibStem) {
323 foundName = lookupLibMaybeAddExt(libStem, RPath, RunPath, libLoader);
324 if (foundName.empty()) {
325 StringRef libStemName = llvm::sys::path::filename(libStem);
326 if (!libStemName.starts_with("lib")) {
327 // try with "lib" prefix:
328 foundName = lookupLibMaybeAddExt(
329 libStem.str().insert(libStem.size() - libStemName.size(), "lib"),
330 RPath, RunPath, libLoader);
331 }
332 }
333 } else {
334 foundName = lookupLibInPaths(libStem, RPath, RunPath, libLoader);
335 }
336
337 if (!foundName.empty())
338 return platform::NormalizePath(foundName);
339
340 return std::string();
341#undef DEBUG_TYPE
342}
343
345DynamicLibraryManager::loadLibrary(StringRef libStem, bool permanent,
346 bool resolved) {
347#define DEBUG_TYPE "Dyld::loadLibrary:"
348 LLVM_DEBUG(dbgs() << "Dyld::loadLibrary: " << libStem.str() << ", "
349 << (permanent ? "permanent" : "not-permanent") << ", "
350 << (resolved ? "resolved" : "not-resolved") << "\n");
351
352 std::string canonicalLoadedLib;
353 if (resolved) {
354 canonicalLoadedLib = libStem.str();
355 } else {
356 canonicalLoadedLib = lookupLibrary(libStem);
357 if (canonicalLoadedLib.empty())
358 return kLoadLibNotFound;
359 }
360
361 if (m_LoadedLibraries.find(canonicalLoadedLib) != m_LoadedLibraries.end())
363
364 // TODO: !permanent case
365
366 std::string errMsg;
367 DyLibHandle dyLibHandle = platform::DLOpen(canonicalLoadedLib, &errMsg);
368 if (!dyLibHandle) {
369 // We emit callback to LibraryLoadingFailed when we get error with error
370 // message.
371 // TODO: Implement callbacks
372
373 LLVM_DEBUG(dbgs() << "DynamicLibraryManager::loadLibrary(): " << errMsg);
374
375 return kLoadLibLoadError;
376 }
377
378 std::pair<DyLibs::iterator, bool> insRes = m_DyLibs.insert(
379 std::pair<DyLibHandle, std::string>(dyLibHandle, canonicalLoadedLib));
380 if (!insRes.second)
382 m_LoadedLibraries.insert(canonicalLoadedLib);
383 return kLoadLibSuccess;
384#undef DEBUG_TYPE
385}
386
388#define DEBUG_TYPE "Dyld::unloadLibrary:"
389 std::string canonicalLoadedLib = lookupLibrary(libStem);
390 if (!isLibraryLoaded(canonicalLoadedLib))
391 return;
392
393 DyLibHandle dyLibHandle = nullptr;
394 for (DyLibs::const_iterator I = m_DyLibs.begin(), E = m_DyLibs.end(); I != E;
395 ++I) {
396 if (I->second == canonicalLoadedLib) {
397 dyLibHandle = I->first;
398 break;
399 }
400 }
401
402 // TODO: !permanent case
403
404 std::string errMsg;
405 platform::DLClose(dyLibHandle, &errMsg);
406 if (!errMsg.empty()) {
407 LLVM_DEBUG(dbgs() << "cling::DynamicLibraryManager::unloadLibrary(): "
408 << errMsg << '\n');
409 }
410
411 // TODO: Implement callbacks
412
413 m_DyLibs.erase(dyLibHandle);
414 m_LoadedLibraries.erase(canonicalLoadedLib);
415#undef DEBUG_TYPE
416}
417
418bool DynamicLibraryManager::isLibraryLoaded(StringRef fullPath) const {
419 std::string canonPath = normalizePath(fullPath);
420 if (m_LoadedLibraries.find(canonPath) != m_LoadedLibraries.end())
421 return true;
422 return false;
423}
424
425void DynamicLibraryManager::dump(llvm::raw_ostream* S /*= nullptr*/) const {
426 llvm::raw_ostream& OS = S ? *S : llvm::outs();
427
428 // FIXME: print in a stable order the contents of m_SearchPaths
429 for (const auto& Info : getSearchPaths()) {
430 if (!Info.IsUser)
431 OS << "[system] ";
432 OS << Info.Path.c_str() << "\n";
433 }
434}
435
436#if defined(_WIN32)
437static bool IsDLL(llvm::StringRef headers) {
438 using namespace llvm::support::endian;
439
440 uint32_t headeroffset = read32le(headers.data() + 0x3c);
441 auto peheader = headers.substr(headeroffset, 24);
442 if (peheader.size() != 24) {
443 return false;
444 }
445 // Read Characteristics from the coff header
446 uint32_t characteristics = read16le(peheader.data() + 22);
447 return (characteristics & llvm::COFF::IMAGE_FILE_DLL) != 0;
448}
449#endif
450
451bool DynamicLibraryManager::isSharedLibrary(StringRef libFullPath,
452 bool* exists /*=0*/) {
453 using namespace llvm;
454
455 auto filetype = sys::fs::get_file_type(libFullPath, /*Follow*/ true);
456 if (filetype != sys::fs::file_type::regular_file) {
457 if (exists) {
458 // get_file_type returns status_error also in case of file_not_found.
459 *exists = filetype != sys::fs::file_type::status_error;
460 }
461 return false;
462 }
463
464 // Do not use the identify_magic overload taking a path: It will open the
465 // file and then mmap its contents, possibly causing bus errors when another
466 // process truncates the file while we are trying to read it. Instead just
467 // read the first 1024 bytes, which should be enough for identify_magic to
468 // do its work.
469 // TODO: Fix the code upstream and consider going back to calling the
470 // convenience function after a future LLVM upgrade.
471 std::string path = libFullPath.str();
472 std::ifstream in(path, std::ios::binary);
473 char header[1024] = {0};
474 in.read(header, sizeof(header));
475 if (in.fail()) {
476 if (exists)
477 *exists = false;
478 return false;
479 }
480
481 StringRef headerStr(header, in.gcount());
482 file_magic Magic = identify_magic(headerStr);
483
484 bool result =
485#ifdef __APPLE__
486 (Magic == file_magic::macho_fixed_virtual_memory_shared_lib ||
487 Magic == file_magic::macho_dynamically_linked_shared_lib ||
488 Magic == file_magic::macho_dynamically_linked_shared_lib_stub ||
489 Magic == file_magic::macho_universal_binary)
490#elif defined(LLVM_ON_UNIX)
491#ifdef __CYGWIN__
492 (Magic == file_magic::pecoff_executable)
493#else
494 (Magic == file_magic::elf_shared_object)
495#endif
496#elif defined(_WIN32)
497 // We should only include dll libraries without including executables,
498 // object code and others...
499 (Magic == file_magic::pecoff_executable && IsDLL(headerStr))
500#else
501#error "Unsupported platform."
502#endif
503 ;
504
505 return result;
506}
507
508} // end namespace CppInternal
static char * GetEnv(const char *Var_Name)
void unloadLibrary(llvm::StringRef libStem)
LoadLibResult loadLibrary(llvm::StringRef, bool permanent, bool resolved=false)
Loads a shared library.
static std::string normalizePath(llvm::StringRef path)
static bool isSharedLibrary(llvm::StringRef libFullPath, bool *exists=nullptr)
Returns true if file is a shared library.
LoadLibResult
Describes the result of loading a library.
@ kLoadLibAlreadyLoaded
library was already loaded
@ kLoadLibLoadError
loading the library failed
@ kLoadLibSuccess
library loaded successfully
void addSearchPath(llvm::StringRef dir, bool isUser=true, bool prepend=false)
void dump(llvm::raw_ostream *S=nullptr) const
const SearchPathInfos & getSearchPaths() const
Returns the system include paths.
bool isLibraryLoaded(llvm::StringRef fullPath) const
Returns true if the file was a dynamic library and it was already loaded.
std::string lookupLibrary(llvm::StringRef libStem, llvm::SmallVector< llvm::StringRef, 2 > RPath={}, llvm::SmallVector< llvm::StringRef, 2 > RunPath={}, llvm::StringRef libLoader="", bool variateLibStem=true) const
Looks up a library taking into account the current include paths and the system include paths.
void * DLOpen(const std::string &Path, std::string *Err=nullptr)
Open a handle to a shared library.
std::string NormalizePath(const std::string &Path)
Returns a normalized version of the given Path.
Definition Paths.cpp:100
const char *const kEnvDelim
Platform specific delimiter for splitting environment variables.
bool GetSystemLibraryPaths(llvm::SmallVectorImpl< std::string > &Paths)
Definition Paths.cpp:59
void DLClose(void *Lib, std::string *Err=nullptr)
Close a handle to a shared library.
bool SplitPaths(llvm::StringRef PathStr, llvm::SmallVectorImpl< llvm::StringRef > &Paths, SplitMode Mode, llvm::StringRef Delim, bool Verbose)
Collect the constituent paths from a PATH string.
Definition Paths.cpp:250
std::string RPathToStr2(SmallVector< StringRef, 2 > V)
static std::string substFront(StringRef original, StringRef pattern, StringRef replacement)
static std::string substAll(StringRef original, StringRef libLoader)
Definition Paths.h:18