#include "il2cpp-config.h" #if IL2CPP_TARGET_POSIX //#define VERBOSE_OUTPUT #ifdef VERBOSE_OUTPUT #include #endif #include #include #include #if IL2CPP_TARGET_LINUX #include #include #endif #include "il2cpp-runtime-metadata.h" #include "os/LibraryLoader.h" #include "os/Mutex.h" #include "utils/PathUtils.h" #include "utils/StringUtils.h" #include "utils/Environment.h" namespace il2cpp { namespace os { #if !IL2CPP_TINY_WITHOUT_DEBUGGER static std::set s_NativeHandlesOpen; typedef std::set::const_iterator OpenHandleIterator; os::FastMutex s_NativeHandlesOpenMutex; #endif struct LibraryNamePrefixAndSuffix { LibraryNamePrefixAndSuffix(const char* prefix_, const char* suffix_) { prefix = std::string(prefix_); suffix = std::string(suffix_); } std::string prefix; std::string suffix; }; static LibraryNamePrefixAndSuffix LibraryNamePrefixAndSuffixVariations[8] = { LibraryNamePrefixAndSuffix("", ".so"), LibraryNamePrefixAndSuffix("", ".dll"), LibraryNamePrefixAndSuffix("", ".dylib"), LibraryNamePrefixAndSuffix("", ".bundle"), LibraryNamePrefixAndSuffix("lib", ".so"), LibraryNamePrefixAndSuffix("lib", ".dll"), LibraryNamePrefixAndSuffix("lib", ".dylib"), LibraryNamePrefixAndSuffix("lib", ".bundle") }; // Note that testing this code can be a bit difficult, since dlopen will cache // the values it returns, and we don't call dlcose from the C# level. See the // comments in the integration test code for more details. static void* LoadLibraryWithName(const char* name, int flags) { #ifdef VERBOSE_OUTPUT printf("Trying name: %s\n", name); #endif void* handle = NULL; #if IL2CPP_TARGET_IOS std::string dirName; if (utils::Environment::GetNumMainArgs() > 0) { std::string main = utils::StringUtils::Utf16ToUtf8(utils::Environment::GetMainArgs()[0]); dirName = utils::PathUtils::DirectoryName(main); } std::string libPath = utils::StringUtils::Printf("%s/%s", dirName.c_str(), name); handle = dlopen(libPath.c_str(), flags); // Fallback to just using the name. This might be a system dylib. if (handle == NULL) handle = dlopen(name, flags); #else handle = dlopen(name, flags); #endif if (handle != NULL) return handle; #ifdef VERBOSE_OUTPUT printf("Error: %s\n", dlerror()); #endif return NULL; } #if IL2CPP_TARGET_LINUX static void* LoadLibraryRelativeToExecutableDirectory(const char* name, int flags) { if (name == NULL || name[0] == '/') return NULL; char exePath[PATH_MAX + 1]; int len; if ((len = readlink("/proc/self/exe", exePath, sizeof(exePath))) == -1) { #ifdef VERBOSE_OUTPUT printf("Error: %s\n", perror()); #endif return NULL; } exePath[len] = '\0'; // readlink does not terminate buffer while (len > 0 && exePath[len] != '/') len--; exePath[len] = '\0'; std::string libPath = utils::StringUtils::Printf("%s/%s", exePath, name); return dlopen(libPath.c_str(), flags); } #endif static void* CheckLibraryVariations(const char* name, int flags) { int numberOfVariations = sizeof(LibraryNamePrefixAndSuffixVariations) / sizeof(LibraryNamePrefixAndSuffixVariations[0]); for (int i = 0; i < numberOfVariations; ++i) { std::string libraryName = LibraryNamePrefixAndSuffixVariations[i].prefix + name + LibraryNamePrefixAndSuffixVariations[i].suffix; void* handle = LoadLibraryWithName(libraryName.c_str(), flags); if (handle != NULL) return handle; #if IL2CPP_TARGET_LINUX // Linux does not search current directory by default handle = LoadLibraryRelativeToExecutableDirectory(libraryName.c_str(), flags); if (handle != NULL) return handle; #endif } return NULL; } Il2CppMethodPointer LibraryLoader::GetHardcodedPInvokeDependencyFunctionPointer(const il2cpp::utils::StringView& nativeDynamicLibrary, const il2cpp::utils::StringView& entryPoint) { return NULL; } void* LibraryLoader::LoadDynamicLibrary(const utils::StringView& nativeDynamicLibrary) { return LoadDynamicLibrary(nativeDynamicLibrary, RTLD_LAZY); } void* LibraryLoader::LoadDynamicLibrary(const utils::StringView& nativeDynamicLibrary, int flags) { #ifdef VERBOSE_OUTPUT printf("Attempting to load dynamic library: %s\n", nativeDynamicLibrary.Str()); #endif if (nativeDynamicLibrary.IsEmpty()) return LoadLibraryWithName(NULL, flags); #if IL2CPP_TARGET_LINUX // Workaround the fact that on Linux, libc is actually named libc.so.6 instead of libc.so. // mscorlib P/Invokes into plain libc, so we need this for those P/Invokes to succeed if (strncasecmp(nativeDynamicLibrary.Str(), "libc", nativeDynamicLibrary.Length()) == 0) return LoadLibraryWithName(LIBC_SO, flags); #endif StringViewAsNullTerminatedStringOf(char, nativeDynamicLibrary, libraryName); void* handle = LoadLibraryWithName(libraryName, flags); if (handle == NULL) handle = CheckLibraryVariations(libraryName, flags); if (handle == NULL) { size_t lengthWithoutDotDll = nativeDynamicLibrary.Length() - 4; if (strncmp(libraryName + lengthWithoutDotDll, ".dll", 4) == 0) { char* nativeDynamicLibraryWithoutExtension = static_cast(alloca((lengthWithoutDotDll + 1) * sizeof(char))); memcpy(nativeDynamicLibraryWithoutExtension, libraryName, lengthWithoutDotDll); nativeDynamicLibraryWithoutExtension[lengthWithoutDotDll] = 0; handle = CheckLibraryVariations(nativeDynamicLibraryWithoutExtension, flags); } } #if !IL2CPP_TINY_WITHOUT_DEBUGGER os::FastAutoLock lock(&s_NativeHandlesOpenMutex); if (handle != NULL) s_NativeHandlesOpen.insert(handle); #endif return handle; } Il2CppMethodPointer LibraryLoader::GetFunctionPointer(void* dynamicLibrary, const PInvokeArguments& pinvokeArgs) { StringViewAsNullTerminatedStringOf(char, pinvokeArgs.entryPoint, entryPoint); #if IL2CPP_TINY_WITHOUT_DEBUGGER return reinterpret_cast(dlsym(dynamicLibrary, entryPoint)); #else if (pinvokeArgs.isNoMangle) return reinterpret_cast(dlsym(dynamicLibrary, entryPoint)); const size_t kBufferOverhead = 10; void* functionPtr = NULL; size_t originalFuncNameLength = strlen(entryPoint) + 1; std::string functionName; functionName.resize(originalFuncNameLength + kBufferOverhead + 1); // Let's index the string from '1', because we might have to prepend an underscore in case of stdcall mangling memcpy(&functionName[1], entryPoint, originalFuncNameLength); memset(&functionName[1] + originalFuncNameLength, 0, kBufferOverhead); // If there's no 'dont mangle' flag set, 'W' function takes priority over original name, but 'A' function does not (yes, really) if (pinvokeArgs.charSet == CHARSET_UNICODE) { functionName[originalFuncNameLength] = 'W'; functionPtr = dlsym(dynamicLibrary, functionName.c_str() + 1); if (functionPtr != NULL) return reinterpret_cast(functionPtr); // If charset specific function lookup failed, try with original name functionPtr = dlsym(dynamicLibrary, entryPoint); } else { functionPtr = dlsym(dynamicLibrary, entryPoint); if (functionPtr != NULL) return reinterpret_cast(functionPtr); // If original name function lookup failed, try with mangled name functionName[originalFuncNameLength] = 'A'; functionPtr = dlsym(dynamicLibrary, functionName.c_str() + 1); } return reinterpret_cast(functionPtr); #endif } Il2CppMethodPointer LibraryLoader::GetFunctionPointer(void* dynamicLibrary, const char* functionName) { #ifdef VERBOSE_OUTPUT printf("Attempting to load method at entry point: %s\n", functionName); #endif Il2CppMethodPointer method = reinterpret_cast(dlsym(dynamicLibrary, functionName)); #ifdef VERBOSE_OUTPUT if (method == NULL) printf("Error: %s\n", dlerror()); #endif return method; } void LibraryLoader::CleanupLoadedLibraries() { #if !IL2CPP_TINY_WITHOUT_DEBUGGER os::FastAutoLock lock(&s_NativeHandlesOpenMutex); for (OpenHandleIterator it = s_NativeHandlesOpen.begin(); it != s_NativeHandlesOpen.end(); it++) { dlclose(*it); } #endif } bool LibraryLoader::CloseLoadedLibrary(void*& dynamicLibrary) { if (dynamicLibrary == NULL) return false; #if !IL2CPP_TINY_WITHOUT_DEBUGGER os::FastAutoLock lock(&s_NativeHandlesOpenMutex); OpenHandleIterator it = s_NativeHandlesOpen.find(dynamicLibrary); if (it != s_NativeHandlesOpen.end()) { dlclose(*it); s_NativeHandlesOpen.erase(it); return true; } return false; #else return true; #endif } } } #endif