From 5ae6e8c2007015b78683dfbdbfc4d80a9240434b Mon Sep 17 00:00:00 2001 From: Elinor Fung <47805090+elinor-fung@users.noreply.github.com> Date: Mon, 29 Apr 2019 15:05:53 -0700 Subject: [PATCH] Check for previously loaded hostfxr before loading in component hosts (dotnet/core-setup#6107) Commit migrated from https://github.com/dotnet/core-setup/commit/a5b7fbb36794f6b778e66282a60a67f84711d701 --- src/installer/corehost/cli/fxr_resolver.cpp | 21 +++++ src/installer/corehost/cli/fxr_resolver.h | 30 ++++-- src/installer/corehost/cli/nethost/nethost.cpp | 10 +- .../corehost/cli/test/nativehost/nativehost.cpp | 12 +++ src/installer/corehost/common/pal.h | 7 +- src/installer/corehost/common/pal.unix.cpp | 101 +++++++++++++++++++-- src/installer/corehost/common/pal.windows.cpp | 26 ++++-- .../HostActivationTests/NativeHosting/Nethost.cs | 20 ++++ 8 files changed, 197 insertions(+), 30 deletions(-) diff --git a/src/installer/corehost/cli/fxr_resolver.cpp b/src/installer/corehost/cli/fxr_resolver.cpp index 4757972..12e2853 100644 --- a/src/installer/corehost/cli/fxr_resolver.cpp +++ b/src/installer/corehost/cli/fxr_resolver.cpp @@ -130,3 +130,24 @@ bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* o return true; } + +bool fxr_resolver::try_get_existing_fxr(pal::dll_t *out_fxr, pal::string_t *out_fxr_path) +{ + pal::dll_t fxr; + if (!pal::get_loaded_library(LIBFXR_NAME, "hostfxr_main", out_fxr, out_fxr_path)) + return false; + + trace::verbose(_X("Found previously loaded library %s [%s]."), LIBFXR_NAME, out_fxr_path->c_str()); + return true; +} + +pal::string_t fxr_resolver::dotnet_root_from_fxr_path(const pal::string_t &fxr_path) +{ + // If coreclr exists next to hostfxr, assume everything is local (e.g. self-contained) + if (coreclr_exists_in_dir(fxr_path)) + return get_directory(fxr_path); + + // Path to hostfxr is: /host/fxr// + pal::string_t fxr_root = get_directory(get_directory(fxr_path)); + return get_directory(get_directory(fxr_root)); +} \ No newline at end of file diff --git a/src/installer/corehost/cli/fxr_resolver.h b/src/installer/corehost/cli/fxr_resolver.h index 3fd4ce1..f84365f 100644 --- a/src/installer/corehost/cli/fxr_resolver.h +++ b/src/installer/corehost/cli/fxr_resolver.h @@ -14,6 +14,8 @@ namespace fxr_resolver { bool try_get_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path); + bool try_get_existing_fxr(pal::dll_t *out_fxr, pal::string_t *out_fxr_path); + pal::string_t dotnet_root_from_fxr_path(const pal::string_t &fxr_path); } template @@ -30,18 +32,26 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostNameToAppNameCall pal::string_t dotnet_root; pal::string_t fxr_path; - if (!fxr_resolver::try_get_path(get_directory(host_path), &dotnet_root, &fxr_path)) + if (fxr_resolver::try_get_existing_fxr(&fxr, &fxr_path)) { - return StatusCode::CoreHostLibMissingFailure; + dotnet_root = fxr_resolver::dotnet_root_from_fxr_path(fxr_path); + trace::verbose(_X("The library %s was already loaded. Reusing the previously loaded library [%s]."), LIBFXR_NAME, fxr_path.c_str()); } - - // Load library - if (!pal::load_library(&fxr_path, &fxr)) + else { - trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, fxr_path.c_str()); - trace::error(_X(" - Installing .NET Core prerequisites might help resolve this problem.")); - trace::error(_X(" %s"), DOTNET_CORE_INSTALL_PREREQUISITES_URL); - return StatusCode::CoreHostLibLoadFailure; + if (!fxr_resolver::try_get_path(get_directory(host_path), &dotnet_root, &fxr_path)) + { + return StatusCode::CoreHostLibMissingFailure; + } + + // Load library + if (!pal::load_library(&fxr_path, &fxr)) + { + trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, fxr_path.c_str()); + trace::error(_X(" - Installing .NET Core prerequisites might help resolve this problem.")); + trace::error(_X(" %s"), DOTNET_CORE_INSTALL_PREREQUISITES_URL); + return StatusCode::CoreHostLibLoadFailure; + } } // Leak fxr @@ -60,7 +70,7 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostNameToAppNameCall { return status; } - + return get_delegate_from_hostfxr(host_path.c_str(), dotnet_root.c_str(), app_path_to_use->c_str(), type, (void**)delegate); } diff --git a/src/installer/corehost/cli/nethost/nethost.cpp b/src/installer/corehost/cli/nethost/nethost.cpp index 4cdc466..9deff2b 100644 --- a/src/installer/corehost/cli/nethost/nethost.cpp +++ b/src/installer/corehost/cli/nethost/nethost.cpp @@ -33,10 +33,14 @@ NETHOST_API int NETHOST_CALLTYPE get_hostfxr_path( if (assembly_path != nullptr) root_path = get_directory(assembly_path); - pal::string_t dotnet_root; + pal::dll_t fxr; pal::string_t fxr_path; - if(!fxr_resolver::try_get_path(root_path, &dotnet_root, &fxr_path)) - return StatusCode::CoreHostLibMissingFailure; + if (!fxr_resolver::try_get_existing_fxr(&fxr, &fxr_path)) + { + pal::string_t dotnet_root; + if(!fxr_resolver::try_get_path(root_path, &dotnet_root, &fxr_path)) + return StatusCode::CoreHostLibMissingFailure; + } size_t len = fxr_path.length(); size_t required_size = len + 1; // null terminator diff --git a/src/installer/corehost/cli/test/nativehost/nativehost.cpp b/src/installer/corehost/cli/test/nativehost/nativehost.cpp index eff581a..352c80e 100644 --- a/src/installer/corehost/cli/test/nativehost/nativehost.cpp +++ b/src/installer/corehost/cli/test/nativehost/nativehost.cpp @@ -33,6 +33,7 @@ int main(const int argc, const pal::char_t *argv[]) const pal::char_t *command = argv[1]; if (pal::strcmp(command, _X("get_hostfxr_path")) == 0) { + // args: ... [] [] const pal::char_t *assembly_path = nullptr; if (argc >= 3) assembly_path = argv[2]; @@ -47,6 +48,17 @@ int main(const int argc, const pal::char_t *argv[]) } #endif + if (argc >= 4) + { + pal::string_t to_load = argv[3]; + pal::dll_t fxr; + if (!pal::load_library(&to_load, &fxr)) + { + std::cout << "Failed to load library: " << tostr(to_load).data() << std::endl; + return EXIT_FAILURE; + } + } + pal::string_t fxr_path; size_t len = fxr_path.size(); int res = get_hostfxr_path(nullptr, &len, assembly_path); diff --git a/src/installer/corehost/common/pal.h b/src/installer/corehost/common/pal.h index 38c6759..344fd2a 100644 --- a/src/installer/corehost/common/pal.h +++ b/src/installer/corehost/common/pal.h @@ -58,7 +58,7 @@ // at the time the SharedFX in question was built), we need to use a reasonable fallback RID to allow // consuming the native assets. // -// For Windows and OSX, we will maintain the last highest RID-Platform we are known to support for them as the +// For Windows and OSX, we will maintain the last highest RID-Platform we are known to support for them as the // degree of compat across their respective releases is usually high. // // We cannot maintain the same (compat) invariant for linux and thus, we will fallback to using lowest RID-Plaform. @@ -229,7 +229,7 @@ namespace pal return fallbackRid; } - + bool touch_file(const pal::string_t& path); bool realpath(string_t* path, bool skip_error_logging = false); bool file_exists(const string_t& path); @@ -245,7 +245,7 @@ namespace pal bool get_current_module(dll_t *mod); bool getenv(const char_t* name, string_t* recv); bool get_default_servicing_directory(string_t* recv); - + //On Linux, there are no global locations //On Windows there will be up to 2 global locations bool get_global_dotnet_dirs(std::vector* recv); @@ -258,6 +258,7 @@ namespace pal int xtoi(const char_t* input); + bool get_loaded_library(const char_t *library_name, const char *symbol_name, /*out*/ dll_t *dll, /*out*/ pal::string_t *path); bool load_library(const string_t* path, dll_t* dll); proc_t get_symbol(dll_t library, const char* name); void unload_library(dll_t library); diff --git a/src/installer/corehost/common/pal.unix.cpp b/src/installer/corehost/common/pal.unix.cpp index 737d392..de26da1 100644 --- a/src/installer/corehost/common/pal.unix.cpp +++ b/src/installer/corehost/common/pal.unix.cpp @@ -75,6 +75,91 @@ bool pal::getcwd(pal::string_t* recv) return true; } +namespace +{ + bool get_loaded_library_from_proc_maps(const pal::char_t *library_name, pal::dll_t *dll, pal::string_t *path) + { + char *line = nullptr; + size_t lineLen = 0; + ssize_t read; + FILE *file = pal::file_open(_X("/proc/self/maps"), _X("r")); + if (file == nullptr) + return false; + + // Read maps file line by line to check fo the library + bool found = false; + pal::string_t path_local; + while ((read = getline(&line, &lineLen, file)) != -1) + { + char buf[PATH_MAX]; + if (sscanf(line, "%*p-%*p %*[-rwxsp] %*p %*[:0-9a-f] %*d %s\n", buf) == 1) + { + path_local = buf; + size_t pos = path_local.rfind(DIR_SEPARATOR); + if (pos == std::string::npos) + continue; + + pos = path_local.find(library_name, pos); + if (pos != std::string::npos) + { + found = true; + break; + } + } + } + + fclose(file); + if (!found) + return false; + + pal::dll_t dll_maybe = dlopen(path_local.c_str(), RTLD_LAZY | RTLD_NOLOAD); + if (dll_maybe == nullptr) + return false; + + *dll = dll_maybe; + path->assign(path_local); + return true; + } +} + +bool pal::get_loaded_library( + const char_t *library_name, + const char *symbol_name, + /*out*/ dll_t *dll, + /*out*/ pal::string_t *path) +{ + pal::string_t library_name_local; +#if defined(__APPLE__) + if (!pal::is_path_rooted(library_name)) + library_name_local.append("@rpath/"); +#endif + library_name_local.append(library_name); + + dll_t dll_maybe = dlopen(library_name_local.c_str(), RTLD_LAZY | RTLD_NOLOAD); + if (dll_maybe == nullptr) + { + if (pal::is_path_rooted(library_name)) + return false; + + // dlopen on some systems only finds loaded libraries when given the full path + // Check proc maps as a fallback + return get_loaded_library_from_proc_maps(library_name, dll, path); + } + + // Not all systems support getting the path from just the handle (e.g. dlinfo), + // so we rely on the caller passing in a symbol name so that we get (any) address + // in the library + assert(symbol_name != nullptr); + pal::proc_t proc = pal::get_symbol(dll_maybe, symbol_name); + Dl_info info; + if (dladdr(proc, &info) == 0) + return false; + + *dll = dll_maybe; + path->assign(info.dli_fname); + return true; +} + bool pal::load_library(const string_t* path, dll_t* dll) { *dll = dlopen(path->c_str(), RTLD_LAZY); @@ -156,7 +241,7 @@ bool pal::get_default_servicing_directory(string_t* recv) // We should have the path in ext. trace::info(_X("Realpath CORE_SERVICING [%s]"), ext.c_str()); } - + if (!pal::directory_exists(ext)) { trace::info(_X("Directory core servicing at [%s] was not specified or found"), ext.c_str()); @@ -357,7 +442,7 @@ pal::string_t pal::get_current_os_rid_platform() // Read the file to get ID and VERSION_ID data that will be used // to construct the RID. std::fstream fsVersionFile; - + fsVersionFile.open(versionFile, std::fstream::in); // Proceed only if we were able to open the file @@ -416,7 +501,7 @@ pal::string_t pal::get_current_os_rid_platform() { ridOS.append(valID); } - + if (fFoundVersion) { ridOS.append(_X(".")); @@ -434,7 +519,7 @@ pal::string_t pal::get_current_os_rid_platform() { // Read the file to check if the current OS is RHEL or CentOS 6.x std::fstream fsVersionFile; - + fsVersionFile.open(rhelVersionFile, std::fstream::in); // Proceed only if we were able to open the file @@ -457,7 +542,7 @@ pal::string_t pal::get_current_os_rid_platform() // Close the file now that we are done with it. fsVersionFile.close(); - } + } } return normalize_linux_rid(ridOS); @@ -496,7 +581,7 @@ bool pal::get_own_executable_path(pal::string_t* recv) recv->assign(buf); return true; } - + // ENOMEM if (error_code == ENOMEM) { @@ -570,7 +655,7 @@ bool pal::realpath(pal::string_t* path, bool skip_error_logging) { perror("realpath()"); } - + return false; } @@ -605,7 +690,7 @@ static void readdir(const pal::string_t& path, const pal::string_t& pattern, boo { continue; } - + // We are interested in files only switch (entry->d_type) { diff --git a/src/installer/corehost/common/pal.windows.cpp b/src/installer/corehost/common/pal.windows.cpp index 36e80ed..933f8d6 100644 --- a/src/installer/corehost/common/pal.windows.cpp +++ b/src/installer/corehost/common/pal.windows.cpp @@ -104,6 +104,20 @@ bool pal::getcwd(pal::string_t* recv) return false; } +bool pal::get_loaded_library( + const char_t *library_name, + const char *symbol_name, + /*out*/ dll_t *dll, + /*out*/ pal::string_t *path) +{ + dll_t dll_maybe = ::GetModuleHandleW(library_name); + if (dll_maybe == nullptr) + return false; + + *dll = dll_maybe; + return pal::get_module_path(*dll, path); +} + bool pal::load_library(const string_t* in_path, dll_t* dll) { string_t path = *in_path; @@ -120,7 +134,7 @@ bool pal::load_library(const string_t* in_path, dll_t* dll) return false; } } - + //Adding the assert to ensure relative paths which are not just filenames are not used for LoadLibrary Calls assert(!LongFile::IsPathNotFullyQualified(path) || !LongFile::ContainsDirectorySeparator(path)); @@ -329,7 +343,7 @@ typedef NTSTATUS (WINAPI *pFuncRtlGetVersion)(RTL_OSVERSIONINFOW *); pal::string_t pal::get_current_os_rid_platform() { pal::string_t ridOS; - + RTL_OSVERSIONINFOW osinfo; // Init the buffer @@ -350,7 +364,7 @@ pal::string_t pal::get_current_os_rid_platform() if (osinfo.dwMajorVersion > majorVer) { majorVer = osinfo.dwMajorVersion; - + // Reset the minor version since we picked a different major version. minorVer = 0; } @@ -371,7 +385,7 @@ pal::string_t pal::get_current_os_rid_platform() ridOS.append(_X("win8")); break; case 3: - default: + default: // For unknown version, we will support the highest RID that we know for this major version. ridOS.append(_X("win81")); break; @@ -386,7 +400,7 @@ pal::string_t pal::get_current_os_rid_platform() } } } - + return ridOS; } @@ -562,7 +576,7 @@ bool pal::realpath(string_t* path, bool skip_error_logging) } const string_t* prefix = &LongFile::ExtendedPrefix; - //Check if the resolved path is a UNC. By default we assume relative path to resolve to disk + //Check if the resolved path is a UNC. By default we assume relative path to resolve to disk if (str.compare(0, LongFile::UNCPathPrefix.length(), LongFile::UNCPathPrefix) == 0) { prefix = &LongFile::UNCExtendedPathPrefix; diff --git a/src/installer/test/HostActivationTests/NativeHosting/Nethost.cs b/src/installer/test/HostActivationTests/NativeHosting/Nethost.cs index 7942755..1a27e31 100644 --- a/src/installer/test/HostActivationTests/NativeHosting/Nethost.cs +++ b/src/installer/test/HostActivationTests/NativeHosting/Nethost.cs @@ -129,6 +129,19 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting .And.HaveStdOutContaining($"hostfxr_path: {hostFxrPath}".ToLower()); } + [Fact] + public void GetHostFxrPath_HostFxrAlreadyLoaded() + { + Command.Create(sharedState.NativeHostPath, $"{GetHostFxrPath} {sharedState.TestAssemblyPath} {sharedState.ProductHostFxrPath}") + .CaptureStdErr() + .CaptureStdOut() + .EnvironmentVariable("COREHOST_TRACE", "1") + .Execute() + .Should().Pass() + .And.HaveStdOutContaining($"hostfxr_path: {sharedState.ProductHostFxrPath}".ToLower()) + .And.HaveStdErrContaining($"Found previously loaded library {HostFxrName}"); + } + public class SharedTestState : SharedTestStateBase { public string HostFxrPath { get; } @@ -137,6 +150,8 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting public string TestAssemblyPath { get; } + public string ProductHostFxrPath { get; } + public SharedTestState() { // Copy nethost next to native host @@ -156,6 +171,11 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting string assemblyPath = Path.Combine(appDir, "App.dll"); File.WriteAllText(assemblyPath, string.Empty); TestAssemblyPath = assemblyPath; + + string productDir = Path.Combine(BaseDirectory, "product"); + Directory.CreateDirectory(productDir); + ProductHostFxrPath = Path.Combine(productDir, HostFxrName); + File.Copy(Path.Combine(RepoDirectories.CorehostPackages, HostFxrName), ProductHostFxrPath); } private string CreateHostFxr(string destinationDirectory) -- 2.7.4