From 2340343f8588b80decbcea1f949c30c08a8d0c73 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 18 Jun 2026 13:43:08 -0700 Subject: [PATCH 1/6] Add C pal_get_loaded_library Add a C implementation of looking up an already-loaded library in the current process, so callers that need to reuse an existing hostfxr (e.g. nethost) can do so without the C++ pal:: surface. - Add a pal_dll_t typedef and pal_get_loaded_library declaration in pal.h. - Implement pal_get_loaded_library in pal.unix.c (dlopen RTLD_NOLOAD, dladdr to resolve the path, and the /proc/self/maps fallback) and pal.windows.c (GetModuleHandleW + a shared get_module_file_name helper factored out of pal_get_own_executable_path). - Repoint the C++ pal::get_loaded_library wrappers at the new C function, removing the old C++ implementations and the proc-maps helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/corehost/hostmisc/pal.h | 15 +++ src/native/corehost/hostmisc/pal.unix.c | 128 +++++++++++++++++++ src/native/corehost/hostmisc/pal.unix.cpp | 84 +----------- src/native/corehost/hostmisc/pal.windows.c | 34 ++++- src/native/corehost/hostmisc/pal.windows.cpp | 11 +- 5 files changed, 188 insertions(+), 84 deletions(-) diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index b01372953ef84c..056a1ec9876a84 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -203,6 +203,21 @@ pal_char_t* pal_get_default_installation_dir(void); // on Windows, file path on Unix). Caller should free() the returned pointer. pal_char_t* pal_get_dotnet_self_registered_config_location(void); +// Handle to a loaded dynamic library. +#if defined(_WIN32) +typedef HMODULE pal_dll_t; +#else +typedef void* pal_dll_t; +#endif + +// Find a library named library_name that is already loaded into the current +// process (without loading it if it is not). symbol_name is used to obtain an +// address inside the library so that its on-disk path can be resolved. On +// success returns true, sets *dll to the library handle, and sets *out_path to +// a heap-allocated path (caller must free()). Returns false if the library is +// not already loaded. +bool pal_get_loaded_library(const pal_char_t* library_name, const char* symbol_name, /*out*/ pal_dll_t* dll, /*out*/ pal_char_t** out_path); + #ifdef __cplusplus } #endif diff --git a/src/native/corehost/hostmisc/pal.unix.c b/src/native/corehost/hostmisc/pal.unix.c index f4ccc0d324fc91..6fe831e4645d41 100644 --- a/src/native/corehost/hostmisc/pal.unix.c +++ b/src/native/corehost/hostmisc/pal.unix.c @@ -3,6 +3,14 @@ // C implementations of the pal_* APIs needed by trace.c on non-Windows. +// glibc guards dladdr/Dl_info (used by pal_get_loaded_library) behind _GNU_SOURCE +// in . As a C translation unit we don't pick it up transitively the way +// the C++ files do via libstdc++, so request it explicitly. Must be defined +// before including any system header. +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #if defined(TARGET_FREEBSD) #define _WITH_GETLINE #endif @@ -13,6 +21,7 @@ #include #include +#include #include #include #include @@ -297,3 +306,122 @@ pal_char_t* pal_get_default_installation_dir(void) return pal_strdup(_X("/usr/share/dotnet")); #endif } + +static bool is_path_fully_qualified(const pal_char_t* path) +{ + return path[0] == DIR_SEPARATOR; +} + +// dlopen on some systems only finds a loaded library when given its full path. +// As a fallback, scan /proc/self/maps for a mapped file whose name contains +// library_name. On success sets *dll and *out_path (heap-allocated, caller +// frees) and returns true. +static bool get_loaded_library_from_proc_maps(const pal_char_t* library_name, pal_dll_t* dll, pal_char_t** out_path) +{ + FILE* file = pal_file_open(_X("/proc/self/maps"), _X("r")); + if (file == NULL) + return false; + + char* line = NULL; + size_t line_cap = 0; + bool found = false; + char found_path[PATH_MAX]; + while (getline(&line, &line_cap, file) != -1) + { + char buf[PATH_MAX]; + if (sscanf(line, "%*p-%*p %*[-rwxsp] %*p %*[:0-9a-f] %*d %s\n", buf) == 1) + { + const char* last_sep = strrchr(buf, DIR_SEPARATOR); + if (last_sep == NULL) + continue; + + if (strstr(last_sep, library_name) != NULL) + { + memcpy(found_path, buf, strlen(buf) + 1); + found = true; + break; + } + } + } + + free(line); + fclose(file); + if (!found) + return false; + + pal_dll_t dll_maybe = dlopen(found_path, RTLD_LAZY | RTLD_NOLOAD); + if (dll_maybe == NULL) + return false; + + pal_char_t* path_copy = pal_strdup(found_path); + if (path_copy == NULL) + { + dlclose(dll_maybe); + return false; + } + + *dll = dll_maybe; + *out_path = path_copy; + return true; +} + +bool pal_get_loaded_library( + const pal_char_t* library_name, + const char* symbol_name, + pal_dll_t* dll, + pal_char_t** out_path) +{ + *dll = NULL; + *out_path = NULL; + + const pal_char_t* lookup_name = library_name; +#if defined(TARGET_OSX) + pal_char_t* rpath_name = NULL; + if (!is_path_fully_qualified(library_name)) + { + size_t cap = STRING_LENGTH(_X("@rpath/")) + pal_strlen(library_name) + 1; + rpath_name = (pal_char_t*)malloc(cap * sizeof(pal_char_t)); + if (rpath_name == NULL) + return false; + + pal_str_printf(rpath_name, cap, _X("@rpath/%s"), library_name); + lookup_name = rpath_name; + } +#endif + + pal_dll_t dll_maybe = dlopen(lookup_name, RTLD_LAZY | RTLD_NOLOAD); +#if defined(TARGET_OSX) + free(rpath_name); +#endif + + if (dll_maybe == NULL) + { + if (is_path_fully_qualified(library_name)) + return false; + + return get_loaded_library_from_proc_maps(library_name, dll, out_path); + } + + // Not all systems support getting the path from just the handle (e.g. + // dlinfo), so we rely on the caller passing a symbol name to get (any) + // address in the library. + assert(symbol_name != NULL); + void* proc = dlsym(dll_maybe, symbol_name); + Dl_info info; + if (dladdr(proc, &info) == 0) + { + dlclose(dll_maybe); + return false; + } + + pal_char_t* path_copy = pal_strdup(info.dli_fname); + if (path_copy == NULL) + { + dlclose(dll_maybe); + return false; + } + + *dll = dll_maybe; + *out_path = path_copy; + return true; +} diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index 26d984db325ed5..9274cd1bba65cf 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -136,92 +136,20 @@ 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); - free(line); - 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(TARGET_OSX) - if (!pal::is_path_fully_qualified(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_fully_qualified(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) - { - dlclose(dll_maybe); + pal_dll_t dll_c = nullptr; + pal_char_t* path_c = nullptr; + if (!::pal_get_loaded_library(library_name, symbol_name, &dll_c, &path_c)) return false; - } - *dll = dll_maybe; - path->assign(info.dli_fname); + *dll = dll_c; + path->assign(path_c); + free(path_c); return true; } diff --git a/src/native/corehost/hostmisc/pal.windows.c b/src/native/corehost/hostmisc/pal.windows.c index 0e22c74cfec23d..d83aea5b14f47c 100644 --- a/src/native/corehost/hostmisc/pal.windows.c +++ b/src/native/corehost/hostmisc/pal.windows.c @@ -22,7 +22,9 @@ #define ALT_DIR_SEPARATOR L'/' #define VOLUME_SEPARATOR L':' -pal_char_t* pal_get_own_executable_path(void) +// Returns the full path of `module` (or the current executable when `module` +// is NULL) as a heap-allocated string, or NULL on failure. Caller must free(). +static pal_char_t* get_module_file_name(HMODULE module) { // GetModuleFileNameW returns 0 on failure, the number of characters // written (not including the terminating null) on success, and the buffer @@ -42,7 +44,7 @@ pal_char_t* pal_get_own_executable_path(void) } buf = new_buf; - size_written = GetModuleFileNameW(NULL, buf, size); + size_written = GetModuleFileNameW(module, buf, size); } while (size_written == size); if (size_written == 0) @@ -54,6 +56,11 @@ pal_char_t* pal_get_own_executable_path(void) return buf; } +pal_char_t* pal_get_own_executable_path(void) +{ + return get_module_file_name(NULL); +} + bool pal_directory_exists(const pal_char_t* path) { DWORD attributes = GetFileAttributesW(path); @@ -510,3 +517,26 @@ pal_char_t* pal_get_default_installation_dir(void) return result; } + +bool pal_get_loaded_library( + const pal_char_t* library_name, + const char* symbol_name, + pal_dll_t* dll, + pal_char_t** out_path) +{ + (void)symbol_name; + *dll = NULL; + *out_path = NULL; + + HMODULE dll_maybe = GetModuleHandleW(library_name); + if (dll_maybe == NULL) + return false; + + pal_char_t* path = get_module_file_name(dll_maybe); + if (path == NULL) + return false; + + *dll = dll_maybe; + *out_path = path; + return true; +} diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index 041242b66659e2..e95c633893949a 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -253,12 +253,15 @@ bool pal::get_loaded_library( /*out*/ dll_t *dll, /*out*/ pal::string_t *path) { - dll_t dll_maybe = ::GetModuleHandleW(library_name); - if (dll_maybe == nullptr) + pal_dll_t dll_c = nullptr; + pal_char_t* path_c = nullptr; + if (!::pal_get_loaded_library(library_name, symbol_name, &dll_c, &path_c)) return false; - *dll = dll_maybe; - return pal::get_module_path(*dll, path); + *dll = dll_c; + path->assign(path_c); + free(path_c); + return true; } bool pal::load_library(const string_t* in_path, dll_t* dll) From ec866421f69400812cf85831c44a716aa99899ad Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 18 Jun 2026 13:43:21 -0700 Subject: [PATCH 2/6] Convert fxr_resolver_try_get_existing_fxr to C Now that pal_get_loaded_library is available in C, move the last fxr_resolver function that was still C++-only into fxr_resolver.c. Add fxr_resolver_try_get_existing_fxr (declaration in fxr_resolver.h, implementation in fxr_resolver.c) and make the C++ fxr_resolver::try_get_existing_fxr wrapper delegate to it. Drop the file comment noting that try_get_existing_fxr had to stay in C++. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/corehost/fxr_resolver.c | 14 ++++++++++++-- src/native/corehost/fxr_resolver.cpp | 8 ++++++-- src/native/corehost/fxr_resolver.h | 7 +++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/native/corehost/fxr_resolver.c b/src/native/corehost/fxr_resolver.c index 536ac39cbd3cac..9d9ee0c8cb0e8e 100644 --- a/src/native/corehost/fxr_resolver.c +++ b/src/native/corehost/fxr_resolver.c @@ -2,8 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // C implementation of the fxr_resolver_* APIs (the C++ fxr_resolver:: namespace -// in fxr_resolver.cpp now delegates here). try_get_existing_fxr stays in C++ -// because pal::get_loaded_library is templated/typed for C++ callers. +// in fxr_resolver.cpp now delegates here). #include "fxr_resolver.h" @@ -443,3 +442,14 @@ bool fxr_resolver_try_get_path_from_dotnet_root( free(fxr_dir); return ok; } + +bool fxr_resolver_try_get_existing_fxr(pal_dll_t* out_fxr, pal_char_t** out_fxr_path) +{ + *out_fxr_path = NULL; + + 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); + return true; +} diff --git a/src/native/corehost/fxr_resolver.cpp b/src/native/corehost/fxr_resolver.cpp index 226051ffda8fbf..5ec44f2924d07d 100644 --- a/src/native/corehost/fxr_resolver.cpp +++ b/src/native/corehost/fxr_resolver.cpp @@ -58,9 +58,13 @@ bool fxr_resolver::try_get_path_from_dotnet_root(const pal::string_t& dotnet_roo bool fxr_resolver::try_get_existing_fxr(pal::dll_t* out_fxr, pal::string_t* out_fxr_path) { - if (!pal::get_loaded_library(LIBFXR_NAME, "hostfxr_main", out_fxr, out_fxr_path)) + pal_dll_t fxr_c = nullptr; + pal_char_t* fxr_path_c = nullptr; + if (!::fxr_resolver_try_get_existing_fxr(&fxr_c, &fxr_path_c)) return false; - trace::verbose(_X("Found previously loaded library %s [%s]."), LIBFXR_NAME, out_fxr_path->c_str()); + *out_fxr = fxr_c; + out_fxr_path->assign(fxr_path_c); + free(fxr_path_c); return true; } diff --git a/src/native/corehost/fxr_resolver.h b/src/native/corehost/fxr_resolver.h index 0be9df5d3fb8ef..f35bcdd0b6ad8f 100644 --- a/src/native/corehost/fxr_resolver.h +++ b/src/native/corehost/fxr_resolver.h @@ -37,6 +37,13 @@ bool fxr_resolver_try_get_path_from_dotnet_root( const pal_char_t* dotnet_root, /*out*/ pal_char_t** out_fxr_path); +// Try to find an already-loaded hostfxr in the current process. On success +// returns true, sets *out_fxr to the library handle and *out_fxr_path to a +// heap-allocated path. Caller should free() out_fxr_path. +bool fxr_resolver_try_get_existing_fxr( + /*out*/ pal_dll_t* out_fxr, + /*out*/ pal_char_t** out_fxr_path); + #ifdef __cplusplus } #endif From 61fde336a3d80fa09cbf2b1c2148c18193236420 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 18 Jun 2026 13:43:35 -0700 Subject: [PATCH 3/6] Convert nethost to C Rewrite nethost.cpp as nethost.c using the C host APIs: trace_* for tracing, fxr_resolver_try_get_existing_fxr / try_get_path_from_dotnet_root / try_get_path for resolution, and utils_get_directory. The RAII error_writer_scope_t is replaced by saving and restoring the previous error writer manually around the call, and pal::string_t buffers become manually-managed pal_char_t* allocations. Update nethost/CMakeLists.txt to build nethost.c. The compiled nethost code is now C-only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/corehost/nethost/CMakeLists.txt | 2 +- src/native/corehost/nethost/nethost.c | 94 ++++++++++++++++++++++ src/native/corehost/nethost/nethost.cpp | 72 ----------------- 3 files changed, 95 insertions(+), 73 deletions(-) create mode 100644 src/native/corehost/nethost/nethost.c delete mode 100644 src/native/corehost/nethost/nethost.cpp diff --git a/src/native/corehost/nethost/CMakeLists.txt b/src/native/corehost/nethost/CMakeLists.txt index 67e61b9988488c..69556e5d347ff9 100644 --- a/src/native/corehost/nethost/CMakeLists.txt +++ b/src/native/corehost/nethost/CMakeLists.txt @@ -3,7 +3,7 @@ # CMake does not recommend using globbing since it messes with the freshness checks set(SOURCES - nethost.cpp + nethost.c ) if(CLR_CMAKE_TARGET_WIN32) diff --git a/src/native/corehost/nethost/nethost.c b/src/native/corehost/nethost/nethost.c new file mode 100644 index 00000000000000..6ff2f2fb827c05 --- /dev/null +++ b/src/native/corehost/nethost/nethost.c @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "nethost.h" +#include +#include +#include +#include +#include + +#include +#include +#include + +// Swallow the trace messages so we don't output to stderr of a process that we +// do not own unless tracing is enabled. +static void __cdecl swallow_trace(const pal_char_t* msg) +{ + (void)msg; +} + +static int get_hostfxr_path_internal( + char_t* buffer, + size_t* buffer_size, + const struct get_hostfxr_parameters* parameters) +{ + size_t min_parameters_size = offsetof(struct get_hostfxr_parameters, dotnet_root) + sizeof(const char_t*); + if (parameters != NULL && parameters->size < min_parameters_size) + { + trace_error(_X("Invalid size for get_hostfxr_parameters. Expected at least %d"), min_parameters_size); + return InvalidArgFailure; + } + + pal_dll_t fxr = NULL; + pal_char_t* fxr_path = NULL; + if (!fxr_resolver_try_get_existing_fxr(&fxr, &fxr_path)) + { + if (parameters != NULL && parameters->dotnet_root != NULL) + { + trace_info(_X("Using dotnet root parameter [%s] as runtime location."), parameters->dotnet_root); + if (!fxr_resolver_try_get_path_from_dotnet_root(parameters->dotnet_root, &fxr_path)) + return CoreHostLibMissingFailure; + } + else + { + pal_char_t* root_path = NULL; + if (parameters != NULL && parameters->assembly_path != NULL) + root_path = utils_get_directory(parameters->assembly_path); + + pal_char_t* dotnet_root = NULL; + bool resolved = fxr_resolver_try_get_path(root_path, fxr_search_location_default, NULL, &dotnet_root, &fxr_path); + free(root_path); + free(dotnet_root); + if (!resolved) + return CoreHostLibMissingFailure; + } + } + + size_t len = pal_strlen(fxr_path); + size_t required_size = len + 1; // null terminator + + size_t input_buffer_size = *buffer_size; + *buffer_size = required_size; + if (buffer == NULL || input_buffer_size < required_size) + { + free(fxr_path); + return HostApiBufferTooSmall; + } + + memcpy(buffer, fxr_path, len * sizeof(char_t)); + buffer[len] = _X('\0'); + free(fxr_path); + return Success; +} + +NETHOST_API int NETHOST_CALLTYPE get_hostfxr_path( + char_t* buffer, + size_t* buffer_size, + const struct get_hostfxr_parameters* parameters) +{ + if (buffer_size == NULL) + return InvalidArgFailure; + + trace_setup(); + + // Swallow traces for the duration of the call and restore the previous error + // writer before returning (equivalent to the C++ error_writer_scope_t). + trace_error_writer_fn previous_writer = trace_set_error_writer(swallow_trace); + + int rc = get_hostfxr_path_internal(buffer, buffer_size, parameters); + + trace_set_error_writer(previous_writer); + return rc; +} diff --git a/src/native/corehost/nethost/nethost.cpp b/src/native/corehost/nethost/nethost.cpp deleted file mode 100644 index 28b8d5aea7f15d..00000000000000 --- a/src/native/corehost/nethost/nethost.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "nethost.h" -#include -#include -#include -#include -#include - -namespace -{ - // Swallow the trace messages so we don't output to stderr of a process that we do not own unless tracing is enabled. - void __cdecl swallow_trace(const pal::char_t* msg) - { - (void)msg; - } -} - -NETHOST_API int NETHOST_CALLTYPE get_hostfxr_path( - char_t * buffer, - size_t * buffer_size, - const struct get_hostfxr_parameters *parameters) -{ - if (buffer_size == nullptr) - return StatusCode::InvalidArgFailure; - - trace::setup(); - error_writer_scope_t writer_scope(swallow_trace); - - size_t min_parameters_size = offsetof(get_hostfxr_parameters, dotnet_root) + sizeof(const char_t*); - if (parameters != nullptr && parameters->size < min_parameters_size) - { - trace::error(_X("Invalid size for get_hostfxr_parameters. Expected at least %d"), min_parameters_size); - return StatusCode::InvalidArgFailure; - } - - pal::dll_t fxr; - pal::string_t fxr_path; - if (!fxr_resolver::try_get_existing_fxr(&fxr, &fxr_path)) - { - if (parameters != nullptr && parameters->dotnet_root != nullptr) - { - pal::string_t dotnet_root = parameters->dotnet_root; - trace::info(_X("Using dotnet root parameter [%s] as runtime location."), dotnet_root.c_str()); - if (!fxr_resolver::try_get_path_from_dotnet_root(dotnet_root, &fxr_path)) - return StatusCode::CoreHostLibMissingFailure; - } - else - { - pal::string_t root_path; - if (parameters != nullptr && parameters->assembly_path != nullptr) - root_path = get_directory(parameters->assembly_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 - - size_t input_buffer_size = *buffer_size; - *buffer_size = required_size; - if (buffer == nullptr || input_buffer_size < required_size) - return StatusCode::HostApiBufferTooSmall; - - fxr_path.copy(buffer, len); - buffer[len] = '\0'; - return StatusCode::Success; -} From 190e36ac4479c5fb1b5b58108221f6b127955ea5 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 18 Jun 2026 14:32:41 -0700 Subject: [PATCH 4/6] Remove the C++ fxr_resolver wrappers fxr_resolver.cpp held thin C++ wrappers (marshalling pal::string_t to and from the pal_char_t* C API) over the fxr_resolver_* functions in fxr_resolver.c. Remove them and have the remaining C++ callers use the C API directly: - hostfxr_resolver.cpp uses fxr_search_location and fxr_resolver_try_get_path directly (with direct utils.h/error_codes.h includes that were previously pulled in transitively through fxr_resolver.h). - The load_fxr_and_get_delegate template (used by ijwhost and comhost) moves to its own header, load_fxr_and_get_delegate.h, and calls the C resolver functions. Its fxr handle is typed as pal_dll_t. fxr_resolver.h is now a pure C header and fxr_resolver is C-only (no fxr_resolver.cpp), which also drops it from the static singlefilehost sources. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AppHost/HostWriter.cs | 2 +- src/native/corehost/CMakeLists.txt | 2 +- .../apphost/standalone/hostfxr_resolver.cpp | 31 ++++- .../corehost/apphost/static/CMakeLists.txt | 1 - src/native/corehost/comhost/comhost.cpp | 2 +- src/native/corehost/fxr_resolver.cpp | 70 ---------- src/native/corehost/fxr_resolver.h | 124 +----------------- src/native/corehost/ijwhost/ijwhost.cpp | 2 +- .../corehost/load_fxr_and_get_delegate.h | 121 +++++++++++++++++ 9 files changed, 151 insertions(+), 204 deletions(-) delete mode 100644 src/native/corehost/fxr_resolver.cpp create mode 100644 src/native/corehost/load_fxr_and_get_delegate.h diff --git a/src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs b/src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs index 777fab812578b0..840bc46b85b319 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs @@ -38,7 +38,7 @@ public static partial class HostWriter public class DotNetSearchOptions { - // Keep in sync with fxr_resolver::search_location in fxr_resolver.h + // Keep in sync with fxr_search_location in fxr_resolver.h [Flags] public enum SearchLocation : byte { diff --git a/src/native/corehost/CMakeLists.txt b/src/native/corehost/CMakeLists.txt index 9be7532e2611cd..b5f279609e425e 100644 --- a/src/native/corehost/CMakeLists.txt +++ b/src/native/corehost/CMakeLists.txt @@ -83,7 +83,7 @@ endif() if(NOT CLR_CMAKE_TARGET_BROWSER) add_library(fxr_resolver INTERFACE) - target_sources(fxr_resolver INTERFACE fxr_resolver.cpp fxr_resolver.c) + target_sources(fxr_resolver INTERFACE fxr_resolver.c) target_include_directories(fxr_resolver INTERFACE fxr) add_compile_definitions(RAPIDJSON_HAS_CXX17) diff --git a/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp b/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp index 0fd8bc01269130..7baa7178a44855 100644 --- a/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp +++ b/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp @@ -6,6 +6,8 @@ #include "pal.h" #include "fxr_resolver.h" #include "trace.h" +#include "utils.h" +#include "error_codes.h" #include "hostfxr_resolver.h" namespace @@ -14,12 +16,12 @@ namespace #define EMBED_DOTNET_SEARCH_HI_PART_UTF8 "19ff3e9c3602ae8e841925bb461a0adb" #define EMBED_DOTNET_SEARCH_LO_PART_UTF8 "064a1f1903667a5e0d87e8f608f425ac" - // \0 + // \0 #define EMBED_DOTNET_SEARCH_FULL_UTF8 ("\0\0" EMBED_DOTNET_SEARCH_HI_PART_UTF8 EMBED_DOTNET_SEARCH_LO_PART_UTF8) // Get the .NET search options that should be used // Returns false if options are invalid - for example, app-relative search was specified, but the path is invalid or not embedded - bool try_get_dotnet_search_options(fxr_resolver::search_location& out_search_location, pal::string_t& out_app_relative_dotnet) + bool try_get_dotnet_search_options(fxr_search_location& out_search_location, pal::string_t& out_app_relative_dotnet) { constexpr int EMBED_SIZE = 512; static_assert(sizeof(EMBED_DOTNET_SEARCH_FULL_UTF8) / sizeof(EMBED_DOTNET_SEARCH_FULL_UTF8[0]) < EMBED_SIZE, "Placeholder value for .NET search options longer than expected"); @@ -27,9 +29,9 @@ namespace // Contains the EMBED_DOTNET_SEARCH_FULL_UTF8 value at compile time or app-relative .NET path written by the SDK (dotnet publish). static char embed[EMBED_SIZE] = EMBED_DOTNET_SEARCH_FULL_UTF8; - out_search_location = (fxr_resolver::search_location)embed[0]; + out_search_location = (fxr_search_location)embed[0]; assert(embed[1] == 0); // NUL separates the search location and embedded .NET root value - if ((out_search_location & fxr_resolver::search_location_app_relative) == 0) + if ((out_search_location & fxr_search_location_app_relative) == 0) return true; // Get the embedded app-relative .NET path @@ -98,7 +100,7 @@ hostfxr_main_fn hostfxr_resolver_t::resolve_main_v1() hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root) { - fxr_resolver::search_location search_location = fxr_resolver::search_location_default; + fxr_search_location search_location = fxr_search_location_default; pal::string_t app_relative_dotnet; pal::string_t app_relative_dotnet_path; if (!try_get_dotnet_search_options(search_location, app_relative_dotnet)) @@ -114,7 +116,24 @@ hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root) append_path(&app_relative_dotnet_path, app_relative_dotnet.c_str()); } - if (!fxr_resolver::try_get_path(app_root, search_location, &app_relative_dotnet_path, &m_dotnet_root, &m_fxr_path)) + pal_char_t* dotnet_root = nullptr; + pal_char_t* fxr_path = nullptr; + bool resolved = fxr_resolver_try_get_path( + app_root.c_str(), + search_location, + app_relative_dotnet_path.empty() ? nullptr : app_relative_dotnet_path.c_str(), + &dotnet_root, + &fxr_path); + if (resolved) + { + m_dotnet_root.assign(dotnet_root); + m_fxr_path.assign(fxr_path); + } + + free(dotnet_root); + free(fxr_path); + + if (!resolved) { m_status_code = StatusCode::CoreHostLibMissingFailure; } diff --git a/src/native/corehost/apphost/static/CMakeLists.txt b/src/native/corehost/apphost/static/CMakeLists.txt index 17a334d1302531..cf936edf2cdcfe 100644 --- a/src/native/corehost/apphost/static/CMakeLists.txt +++ b/src/native/corehost/apphost/static/CMakeLists.txt @@ -30,7 +30,6 @@ set(SOURCES ./hostfxr_resolver.cpp ./hostpolicy_resolver.cpp ../../hostpolicy/static/coreclr_resolver.cpp - ../../fxr_resolver.cpp ../../fxr_resolver.c ../../corehost.cpp ) diff --git a/src/native/corehost/comhost/comhost.cpp b/src/native/corehost/comhost/comhost.cpp index 6b7a67233b937b..e61dbd0572bb1c 100644 --- a/src/native/corehost/comhost/comhost.cpp +++ b/src/native/corehost/comhost/comhost.cpp @@ -4,7 +4,7 @@ #include "comhost.h" #include "redirected_error_writer.h" #include "hostfxr.h" -#include "fxr_resolver.h" +#include "load_fxr_and_get_delegate.h" #include "pal.h" #include "trace.h" #include "error_codes.h" diff --git a/src/native/corehost/fxr_resolver.cpp b/src/native/corehost/fxr_resolver.cpp deleted file mode 100644 index 5ec44f2924d07d..00000000000000 --- a/src/native/corehost/fxr_resolver.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// C++ wrappers around the C fxr_resolver_* APIs in fxr_resolver.c. The C++ -// surface stays the same; each member just marshals between pal::string_t and -// pal_char_t*. try_get_existing_fxr remains a C++ function because -// pal::get_loaded_library is a templated helper that only has a C++ caller. - -#include "fxr_resolver.h" - -#include -#include -#include -#include - -bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path) -{ - return try_get_path(root_path, search_location_default, nullptr, out_dotnet_root, out_fxr_path); -} - -bool fxr_resolver::try_get_path( - const pal::string_t& root_path, - search_location search, - /*opt*/ pal::string_t* app_relative_dotnet_root, - /*out*/ pal::string_t* out_dotnet_root, - /*out*/ pal::string_t* out_fxr_path) -{ - pal_char_t* dotnet_root_c = nullptr; - pal_char_t* fxr_path_c = nullptr; - bool ok = ::fxr_resolver_try_get_path( - root_path.c_str(), - static_cast(search), - app_relative_dotnet_root != nullptr ? app_relative_dotnet_root->c_str() : nullptr, - &dotnet_root_c, - &fxr_path_c); - - if (ok) - { - out_dotnet_root->assign(dotnet_root_c); - out_fxr_path->assign(fxr_path_c); - } - - free(dotnet_root_c); - free(fxr_path_c); - return ok; -} - -bool fxr_resolver::try_get_path_from_dotnet_root(const pal::string_t& dotnet_root, pal::string_t* out_fxr_path) -{ - pal_char_t* fxr_path_c = nullptr; - bool ok = ::fxr_resolver_try_get_path_from_dotnet_root(dotnet_root.c_str(), &fxr_path_c); - if (ok) - out_fxr_path->assign(fxr_path_c); - - free(fxr_path_c); - return ok; -} - -bool fxr_resolver::try_get_existing_fxr(pal::dll_t* out_fxr, pal::string_t* out_fxr_path) -{ - pal_dll_t fxr_c = nullptr; - pal_char_t* fxr_path_c = nullptr; - if (!::fxr_resolver_try_get_existing_fxr(&fxr_c, &fxr_path_c)) - return false; - - *out_fxr = fxr_c; - out_fxr_path->assign(fxr_path_c); - free(fxr_path_c); - return true; -} diff --git a/src/native/corehost/fxr_resolver.h b/src/native/corehost/fxr_resolver.h index f35bcdd0b6ad8f..d97b456fa03e23 100644 --- a/src/native/corehost/fxr_resolver.h +++ b/src/native/corehost/fxr_resolver.h @@ -11,8 +11,7 @@ extern "C" { #endif -// Keep in sync with fxr_resolver::search_location below and with -// DotNetRootOptions.SearchLocation in HostWriter.cs. +// Keep in sync with DotNetRootOptions.SearchLocation in HostWriter.cs. typedef enum { fxr_search_location_default = 0, @@ -48,125 +47,4 @@ bool fxr_resolver_try_get_existing_fxr( } #endif -#ifdef __cplusplus - -#include "hostfxr.h" -#include "trace.h" -#include "utils.h" -#include "error_codes.h" - -namespace fxr_resolver -{ - // Keep in sync with DotNetRootOptions.SearchLocation in HostWriter.cs - enum search_location : uint8_t - { - search_location_default = fxr_search_location_default, - search_location_app_local = fxr_search_location_app_local, - search_location_app_relative = fxr_search_location_app_relative, - search_location_environment_variable = fxr_search_location_environment_variable, - search_location_global = fxr_search_location_global, - }; - - 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_path(const pal::string_t& root_path, search_location search, /*opt*/ pal::string_t* embedded_dotnet_root, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path); - bool try_get_path_from_dotnet_root(const pal::string_t& dotnet_root, pal::string_t* out_fxr_path); - bool try_get_existing_fxr(pal::dll_t *out_fxr, pal::string_t *out_fxr_path); -} - -template -int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TBeforeRunCallback on_before_run, void** delegate, bool try_ignore_missing_config) -{ - pal::dll_t fxr; - - pal::string_t host_path; - if (!pal::get_own_module_path(&host_path) || !pal::fullpath(&host_path)) - { - trace::error(_X("Failed to resolve full path of the current host module [%s]"), host_path.c_str()); - return StatusCode::CurrentHostFindFailure; - } - - pal::string_t dotnet_root; - pal::string_t fxr_path; - if (fxr_resolver::try_get_existing_fxr(&fxr, &fxr_path)) - { - dotnet_root = get_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()); - } - else - { - // Do not specify the root path. Getting a delegate does not support self-contained (app-local fxr) - if (!fxr_resolver::try_get_path(pal::string_t{}, &dotnet_root, &fxr_path)) - { - return StatusCode::CoreHostLibMissingFailure; - } - - // We should always be loading hostfxr from an absolute path - if (!pal::is_path_fully_qualified(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()); - return StatusCode::CoreHostLibLoadFailure; - } - } - - // Leak fxr - - auto hostfxr_initialize_for_runtime_config = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_initialize_for_runtime_config")); - auto hostfxr_get_runtime_delegate = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_get_runtime_delegate")); - auto hostfxr_close = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_close")); - if (hostfxr_initialize_for_runtime_config == nullptr || hostfxr_get_runtime_delegate == nullptr || hostfxr_close == nullptr) - return StatusCode::CoreHostEntryPointFailure; - - pal::string_t config_path; - pal::hresult_t status = host_path_to_config_path(host_path, &config_path); - if (status != StatusCode::Success) - return status; - - hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); - { - propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn); - if (!try_ignore_missing_config || pal::file_exists(config_path)) - { - hostfxr_initialize_parameters parameters { - sizeof(hostfxr_initialize_parameters), - host_path.c_str(), - dotnet_root.c_str() - }; - - hostfxr_handle context; - int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), ¶meters, &context); - if (!STATUS_CODE_SUCCEEDED(rc)) - return rc; - - on_before_run(fxr, context); - - rc = hostfxr_get_runtime_delegate(context, type, delegate); - - int rcClose = hostfxr_close(context); - if (rcClose != StatusCode::Success) - { - assert(false && "Failed to close host context"); - trace::verbose(_X("Failed to close host context: 0x%x"), rcClose); - } - - return rc; - } - else - { - // null context means use the current one, if none exists it will fail - int rc = hostfxr_get_runtime_delegate(nullptr, type, delegate); - if (rc == StatusCode::HostInvalidState) - { - trace::error(_X("Expected active runtime context because runtimeconfig.json [%s] does not exist."), config_path.c_str()); - } - return rc; - } - } -} - -#endif // __cplusplus - #endif //_COREHOST_CLI_FXR_RESOLVER_H_ diff --git a/src/native/corehost/ijwhost/ijwhost.cpp b/src/native/corehost/ijwhost/ijwhost.cpp index 5ea726cab92ad5..f017cef6ec0502 100644 --- a/src/native/corehost/ijwhost/ijwhost.cpp +++ b/src/native/corehost/ijwhost/ijwhost.cpp @@ -4,7 +4,7 @@ #include "ijwhost.h" #include #include -#include +#include #include #include #include diff --git a/src/native/corehost/load_fxr_and_get_delegate.h b/src/native/corehost/load_fxr_and_get_delegate.h new file mode 100644 index 00000000000000..53f99ec6caf1f7 --- /dev/null +++ b/src/native/corehost/load_fxr_and_get_delegate.h @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef _COREHOST_LOAD_FXR_AND_GET_DELEGATE_H_ +#define _COREHOST_LOAD_FXR_AND_GET_DELEGATE_H_ + +#include "fxr_resolver.h" + +#include "hostfxr.h" +#include "trace.h" +#include "utils.h" +#include "error_codes.h" + +template +int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TBeforeRunCallback on_before_run, void** delegate, bool try_ignore_missing_config) +{ + pal_dll_t fxr; + + pal::string_t host_path; + if (!pal::get_own_module_path(&host_path) || !pal::fullpath(&host_path)) + { + trace::error(_X("Failed to resolve full path of the current host module [%s]"), host_path.c_str()); + return StatusCode::CurrentHostFindFailure; + } + + pal::string_t dotnet_root; + pal::string_t fxr_path; + pal_char_t* existing_fxr_path = nullptr; + if (fxr_resolver_try_get_existing_fxr(&fxr, &existing_fxr_path)) + { + fxr_path.assign(existing_fxr_path); + free(existing_fxr_path); + dotnet_root = get_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()); + } + else + { + // Do not specify the root path. Getting a delegate does not support self-contained (app-local fxr) + pal_char_t* dotnet_root_c = nullptr; + pal_char_t* fxr_path_c = nullptr; + bool resolved = fxr_resolver_try_get_path(nullptr, fxr_search_location_default, nullptr, &dotnet_root_c, &fxr_path_c); + if (resolved) + { + dotnet_root.assign(dotnet_root_c); + fxr_path.assign(fxr_path_c); + } + + free(dotnet_root_c); + free(fxr_path_c); + + if (!resolved) + return StatusCode::CoreHostLibMissingFailure; + + // We should always be loading hostfxr from an absolute path + if (!pal::is_path_fully_qualified(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()); + return StatusCode::CoreHostLibLoadFailure; + } + } + + // Leak fxr + + auto hostfxr_initialize_for_runtime_config = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_initialize_for_runtime_config")); + auto hostfxr_get_runtime_delegate = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_get_runtime_delegate")); + auto hostfxr_close = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_close")); + if (hostfxr_initialize_for_runtime_config == nullptr || hostfxr_get_runtime_delegate == nullptr || hostfxr_close == nullptr) + return StatusCode::CoreHostEntryPointFailure; + + pal::string_t config_path; + pal::hresult_t status = host_path_to_config_path(host_path, &config_path); + if (status != StatusCode::Success) + return status; + + hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); + { + propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn); + if (!try_ignore_missing_config || pal::file_exists(config_path)) + { + hostfxr_initialize_parameters parameters { + sizeof(hostfxr_initialize_parameters), + host_path.c_str(), + dotnet_root.c_str() + }; + + hostfxr_handle context; + int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), ¶meters, &context); + if (!STATUS_CODE_SUCCEEDED(rc)) + return rc; + + on_before_run(fxr, context); + + rc = hostfxr_get_runtime_delegate(context, type, delegate); + + int rcClose = hostfxr_close(context); + if (rcClose != StatusCode::Success) + { + assert(false && "Failed to close host context"); + trace::verbose(_X("Failed to close host context: 0x%x"), rcClose); + } + + return rc; + } + else + { + // null context means use the current one, if none exists it will fail + int rc = hostfxr_get_runtime_delegate(nullptr, type, delegate); + if (rc == StatusCode::HostInvalidState) + { + trace::error(_X("Expected active runtime context because runtimeconfig.json [%s] does not exist."), config_path.c_str()); + } + return rc; + } + } +} + +#endif //_COREHOST_LOAD_FXR_AND_GET_DELEGATE_H_ From 9189c4b38edb12e0762af9655d28d3f79aefcf37 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 18 Jun 2026 14:33:22 -0700 Subject: [PATCH 5/6] Link nethost against a C-only hostmisc nethost's own code is C, and after the previous change fxr_resolver is C-only too, but nethost still linked the full hostmisc (which includes C++ translation units). That gave the nethost targets a C++ linker language, so the clang++ driver added a dependency on the C++ runtime (libc++) and libnethost.a embedded the unused C++ objects. Split the hostmisc sources into C_SOURCES/CXX_SOURCES and add hostmisc_c, an object library built from just the C sources. hostmisc and hostmisc::public are unchanged for the C++ consumers (apphost, hostfxr, hostpolicy, etc.). Point nethost/libnethost at hostmisc_c (+ minipal_objects, which object libraries don't propagate transitively). The nethost targets now compile no C++, link with the C driver, and have no libc++ dependency; libnethost.a shrinks substantially by dropping the embedded C++ objects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/corehost/hostmisc/CMakeLists.txt | 25 ++++++++++++++++----- src/native/corehost/nethost/CMakeLists.txt | 4 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/native/corehost/hostmisc/CMakeLists.txt b/src/native/corehost/hostmisc/CMakeLists.txt index ba97b51b3fa914..be2411b560a600 100644 --- a/src/native/corehost/hostmisc/CMakeLists.txt +++ b/src/native/corehost/hostmisc/CMakeLists.txt @@ -4,10 +4,13 @@ include(configure.cmake) # CMake does not recommend using globbing since it messes with the freshness checks -set(SOURCES +set(C_SOURCES ${CMAKE_CURRENT_LIST_DIR}/fx_ver.c ${CMAKE_CURRENT_LIST_DIR}/trace.c ${CMAKE_CURRENT_LIST_DIR}/utils.c +) + +set(CXX_SOURCES ${CMAKE_CURRENT_LIST_DIR}/utils.cpp ${CMAKE_CURRENT_LIST_DIR}/../fxr/fx_ver.cpp ) @@ -21,18 +24,22 @@ set(HEADERS ) if(CLR_CMAKE_TARGET_WIN32) - list(APPEND SOURCES - ${CMAKE_CURRENT_LIST_DIR}/pal.windows.c + list(APPEND C_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/pal.windows.c) + list(APPEND CXX_SOURCES ${CMAKE_CURRENT_LIST_DIR}/pal.windows.cpp ${CMAKE_CURRENT_LIST_DIR}/longfile.windows.cpp) list(APPEND HEADERS ${CMAKE_CURRENT_LIST_DIR}/longfile.h) else() - list(APPEND SOURCES - ${CMAKE_CURRENT_LIST_DIR}/pal.unix.c + list(APPEND C_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/pal.unix.c) + list(APPEND CXX_SOURCES ${CMAKE_CURRENT_LIST_DIR}/pal.unix.cpp) endif() +set(SOURCES ${C_SOURCES} ${CXX_SOURCES}) + # hostmisc must be an "object library" as we want to build it once # and embed the objects into static libraries we ship (like libnethost). add_library(hostmisc_interface INTERFACE) @@ -75,3 +82,11 @@ endif() set_target_properties(hostmisc_public PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) add_library(hostmisc::public ALIAS hostmisc_public) + +# C-only subset of hostmisc. +add_library(hostmisc_c OBJECT ${C_SOURCES}) +target_link_libraries(hostmisc_c PUBLIC hostmisc_interface) +if(NOT CLR_CMAKE_TARGET_BROWSER) + target_link_libraries(hostmisc_c PUBLIC minipal_objects) +endif() +set_target_properties(hostmisc_c PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) diff --git a/src/native/corehost/nethost/CMakeLists.txt b/src/native/corehost/nethost/CMakeLists.txt index 69556e5d347ff9..50b40924796d88 100644 --- a/src/native/corehost/nethost/CMakeLists.txt +++ b/src/native/corehost/nethost/CMakeLists.txt @@ -33,8 +33,8 @@ if (WIN32) ) endif(WIN32) -target_link_libraries(nethost PRIVATE hostmisc fxr_resolver) -target_link_libraries(libnethost PRIVATE hostmisc::public fxr_resolver minipal_objects) +target_link_libraries(nethost PRIVATE hostmisc_c fxr_resolver minipal_objects) +target_link_libraries(libnethost PRIVATE hostmisc_c fxr_resolver minipal_objects) target_compile_definitions(nethost PRIVATE FEATURE_LIBHOST NETHOST_EXPORT) target_compile_definitions(libnethost PRIVATE FEATURE_LIBHOST NETHOST_EXPORT) From a525dd7699e4a6b62063b1dfb872e62ad8afc6b4 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 24 Jun 2026 17:40:28 -0700 Subject: [PATCH 6/6] Address PR review feedback - nethost.c: use %zu (not %d) for the size_t in the invalid-size trace. - pal.unix.c: bound the /proc/self/maps %s read with an explicit field width so a pathological line can't overflow the buffer. - fxr_resolver.c: initialize *out_fxr in fxr_resolver_try_get_existing_fxr, and drop the stale fxr_resolver.cpp reference from the file comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/corehost/fxr_resolver.c | 4 +--- src/native/corehost/hostmisc/pal.unix.c | 11 ++++++++--- src/native/corehost/nethost/nethost.c | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/native/corehost/fxr_resolver.c b/src/native/corehost/fxr_resolver.c index 9d9ee0c8cb0e8e..d2037916d0a2ac 100644 --- a/src/native/corehost/fxr_resolver.c +++ b/src/native/corehost/fxr_resolver.c @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// C implementation of the fxr_resolver_* APIs (the C++ fxr_resolver:: namespace -// in fxr_resolver.cpp now delegates here). - #include "fxr_resolver.h" #include "fx_ver.h" @@ -445,6 +442,7 @@ bool fxr_resolver_try_get_path_from_dotnet_root( bool fxr_resolver_try_get_existing_fxr(pal_dll_t* out_fxr, pal_char_t** out_fxr_path) { + *out_fxr = NULL; *out_fxr_path = NULL; if (!pal_get_loaded_library(LIBFXR_NAME, "hostfxr_main", out_fxr, out_fxr_path)) diff --git a/src/native/corehost/hostmisc/pal.unix.c b/src/native/corehost/hostmisc/pal.unix.c index 6fe831e4645d41..8bb643d36d7c38 100644 --- a/src/native/corehost/hostmisc/pal.unix.c +++ b/src/native/corehost/hostmisc/pal.unix.c @@ -312,6 +312,11 @@ static bool is_path_fully_qualified(const pal_char_t* path) return path[0] == DIR_SEPARATOR; } +// Two-level stringize so PATH_MAX's value (not its name) can be used as an +// explicit sscanf field width below. +#define PROC_MAPS_STR2(x) #x +#define PROC_MAPS_STR(x) PROC_MAPS_STR2(x) + // dlopen on some systems only finds a loaded library when given its full path. // As a fallback, scan /proc/self/maps for a mapped file whose name contains // library_name. On success sets *dll and *out_path (heap-allocated, caller @@ -325,11 +330,11 @@ static bool get_loaded_library_from_proc_maps(const pal_char_t* library_name, pa char* line = NULL; size_t line_cap = 0; bool found = false; - char found_path[PATH_MAX]; + char found_path[PATH_MAX + 1]; while (getline(&line, &line_cap, file) != -1) { - char buf[PATH_MAX]; - if (sscanf(line, "%*p-%*p %*[-rwxsp] %*p %*[:0-9a-f] %*d %s\n", buf) == 1) + char buf[PATH_MAX + 1]; // + 1 for the NUL terminator + if (sscanf(line, "%*p-%*p %*[-rwxsp] %*p %*[:0-9a-f] %*d %" PROC_MAPS_STR(PATH_MAX) "s\n", buf) == 1) { const char* last_sep = strrchr(buf, DIR_SEPARATOR); if (last_sep == NULL) diff --git a/src/native/corehost/nethost/nethost.c b/src/native/corehost/nethost/nethost.c index 6ff2f2fb827c05..b1a8b6bb165285 100644 --- a/src/native/corehost/nethost/nethost.c +++ b/src/native/corehost/nethost/nethost.c @@ -27,7 +27,7 @@ static int get_hostfxr_path_internal( size_t min_parameters_size = offsetof(struct get_hostfxr_parameters, dotnet_root) + sizeof(const char_t*); if (parameters != NULL && parameters->size < min_parameters_size) { - trace_error(_X("Invalid size for get_hostfxr_parameters. Expected at least %d"), min_parameters_size); + trace_error(_X("Invalid size for get_hostfxr_parameters. Expected at least %zu"), min_parameters_size); return InvalidArgFailure; }