diff --git a/src/native/corehost/apphost/apphost.c b/src/native/corehost/apphost/apphost.c new file mode 100644 index 00000000000000..64aa2dc255242a --- /dev/null +++ b/src/native/corehost/apphost/apphost.c @@ -0,0 +1,389 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal.h" +#include "trace.h" +#include "utils.h" +#include "bundle_marker.h" +#include "apphost_hostfxr_resolver.h" +#include "error_codes.h" +#include "hostfxr.h" + +#if defined(_WIN32) +#include "apphost.windows.h" +#include <_version.h> +#endif + +#include +#include +#include + +#if defined(FEATURE_STATIC_HOST) +extern void apphost_static_init(void); +#endif + +/** + * Detect if the apphost executable is allowed to load and execute a managed assembly. + * + * - The exe is built with a known hash string at some offset in the image + * - The exe is useless as is with the built-in hash value, and will fail with an error message + * - The hash value should be replaced with the managed DLL filename with optional relative path + * - The optional path is relative to the location of the apphost executable + * - The relative path plus filename are verified to reference a valid file + * - The filename should be "NUL terminated UTF-8" by "dotnet build" + * - The managed DLL filename does not have to be the same name as the apphost executable name + * - The exe may be signed at this point by the app publisher + * - Note: the maximum size of the filename and relative path is 1024 bytes in UTF-8 (not including NUL) + * o https://en.wikipedia.org/wiki/Comparison_of_file_systems + * has more details on maximum file name sizes. + */ +#define EMBED_HASH_HI_PART_UTF8 "c3ab8ff13720e8ad9047dd39466b3c89" // SHA-256 of "foobar" in UTF-8 +#define EMBED_HASH_LO_PART_UTF8 "74e592c2fa383d4a3960714caef0c4f2" +#define EMBED_HASH_FULL_UTF8 (EMBED_HASH_HI_PART_UTF8 EMBED_HASH_LO_PART_UTF8) // NUL terminated + +#define EMBED_SZ (int)(sizeof(EMBED_HASH_FULL_UTF8) / sizeof(EMBED_HASH_FULL_UTF8[0])) +#define EMBED_MAX (EMBED_SZ > 1025 ? EMBED_SZ : 1025) // 1024 DLL name length, 1 NUL + +// This avoids compiler optimization which cause EMBED_HASH_HI_PART_UTF8 EMBED_HASH_LO_PART_UTF8 +// to be placed adjacent causing them to match EMBED_HASH_FULL_UTF8 when searched for replacing. +// See https://github.com/dotnet/runtime/issues/109611 for more details. +static bool compare_memory_nooptimization(volatile const char* a, volatile const char* b, size_t length) +{ + for (size_t i = 0; i < length; i++) + { + if (*a++ != *b++) + return false; + } + return true; +} + +// app_dll receives the embedded DLL name as a pal_char_t string. +// app_dll_len is the buffer size in pal_char_t characters. +static bool is_exe_enabled_for_execution(pal_char_t* app_dll, size_t app_dll_len) +{ + // Contains the EMBED_HASH_FULL_UTF8 value at compile time or the managed DLL name replaced by "dotnet build". + // Must not be 'const' because strlen below could be determined at compile time (=64) instead of the actual + // length of the string at runtime. + // Always narrow UTF-8, regardless of platform. + static char embed[EMBED_MAX] = EMBED_HASH_FULL_UTF8; + + static const char hi_part[] = EMBED_HASH_HI_PART_UTF8; + static const char lo_part[] = EMBED_HASH_LO_PART_UTF8; + + size_t binding_len = strlen(&embed[0]); + + if (binding_len == 0 || binding_len >= app_dll_len) + { + trace_error(_X("The managed DLL bound to this executable could not be retrieved from the executable image.")); + return false; + } + + // Check if the path exceeds the max allowed size + if (binding_len > EMBED_MAX - 1) + { + trace_error(_X("The managed DLL bound to this executable is longer than the max allowed length (%d)"), EMBED_MAX - 1); + return false; + } + + // Check if the value is the same as the placeholder to detect unbound executables + size_t hi_len = sizeof(hi_part) - 1; + size_t lo_len = sizeof(lo_part) - 1; + if (binding_len >= (hi_len + lo_len) + && compare_memory_nooptimization(&embed[0], hi_part, hi_len) + && compare_memory_nooptimization(&embed[hi_len], lo_part, lo_len)) + { + trace_error(_X("This executable is not bound to a managed DLL to execute.")); + return false; + } + + if (!pal_utf8_to_palstr(&embed[0], app_dll, app_dll_len)) + { + trace_error(_X("The managed DLL bound to this executable could not be retrieved from the executable image.")); + return false; + } + + trace_info(_X("The managed DLL bound to this executable is: '%s'"), app_dll); + return true; +} + +static void report_outdated_framework_error(const pal_char_t* dotnet_root, const pal_char_t* host_path) +{ + pal_char_t download_url[MAX_DOWNLOAD_URL_LEN]; + utils_get_download_url(download_url, ARRAY_SIZE(download_url), NULL, NULL); + + trace_error( + MISSING_RUNTIME_ERROR_FORMAT, + INSTALL_OR_UPDATE_NET_ERROR_MESSAGE, + host_path, + _STRINGIFY(CURRENT_ARCH_NAME), + _STRINGIFY(HOST_VERSION), + dotnet_root, + download_url, + _STRINGIFY(HOST_VERSION)); +} + +// C equivalent of propagate_error_writer_t +typedef struct { + hostfxr_set_error_writer_fn set_error_writer; + bool error_writer_set; +} propagate_error_writer_state_t; + +static void propagate_error_writer_init(propagate_error_writer_state_t* state, hostfxr_set_error_writer_fn set_error_writer) +{ + trace_flush(); + + state->set_error_writer = set_error_writer; + state->error_writer_set = false; + + trace_error_writer_fn error_writer = trace_get_error_writer(); + if (error_writer != NULL && set_error_writer != NULL) + { + set_error_writer((hostfxr_error_writer_fn)error_writer); + state->error_writer_set = true; + } +} + +static void propagate_error_writer_cleanup(propagate_error_writer_state_t* state) +{ + if (state->error_writer_set && state->set_error_writer != NULL) + { + state->set_error_writer(NULL); + state->error_writer_set = false; + } +} + +static int exe_start(const int argc, const pal_char_t* argv[]) +{ +#if defined(FEATURE_STATIC_HOST) + apphost_static_init(); +#endif + + // Use realpath/GetModuleFileName to find the path of the host, resolving any symlinks. + pal_char_t* host_path = pal_get_own_executable_path(); + if (host_path == NULL) + { + trace_error(_X("Failed to resolve full path of the current executable [%s]"), _X("")); + return CurrentHostFindFailure; + } + + pal_char_t* host_path_full = pal_fullpath(host_path, false); + if (host_path_full == NULL) + { + trace_error(_X("Failed to resolve full path of the current executable [%s]"), host_path); + free(host_path); + return CurrentHostFindFailure; + } + + free(host_path); + host_path = host_path_full; + + bool requires_hostfxr_startupinfo_interface = false; + + // FEATURE_APPHOST path: read embedded DLL name + pal_char_t embedded_app_name[EMBED_MAX]; + if (!is_exe_enabled_for_execution(embedded_app_name, ARRAY_SIZE(embedded_app_name))) + { + free(host_path); + return AppHostExeNotBoundFailure; + } + +#if defined(_WIN32) + for (pal_char_t* c = embedded_app_name; *c != _X('\0'); c++) + { + if (*c == _X('/')) + *c = DIR_SEPARATOR; + } +#endif + + if (pal_strchr(embedded_app_name, DIR_SEPARATOR) != NULL) + { + requires_hostfxr_startupinfo_interface = true; + } + + pal_char_t* app_dir = utils_get_directory(host_path); + if (app_dir == NULL) + { + free(host_path); + return AppPathFindFailure; + } + + pal_char_t* app_path = utils_append_path_alloc(app_dir, embedded_app_name); + free(app_dir); + if (app_path == NULL) + { + free(host_path); + return AppPathFindFailure; + } + + const bool is_bundle = bundle_marker_is_bundle(); + if (is_bundle) + { + trace_info(_X("Detected Single-File app bundle")); + } + else + { + pal_char_t* app_path_full = pal_fullpath(app_path, false); + if (app_path_full == NULL) + { + trace_error(_X("The application to execute does not exist: '%s'."), app_path); + free(app_path); + free(host_path); + return AppPathFindFailure; + } + + free(app_path); + app_path = app_path_full; + } + + pal_char_t* app_root = utils_get_directory(app_path); + if (app_root == NULL) + { + free(app_path); + free(host_path); + return AppPathFindFailure; + } + + hostfxr_resolver_t fxr; + hostfxr_resolver_init(&fxr, app_root); + + int rc = fxr.status_code; + if (rc != Success) + { + hostfxr_resolver_cleanup(&fxr); + free(app_root); + free(app_path); + free(host_path); + return rc; + } + + if (is_bundle) + { + hostfxr_main_bundle_startupinfo_fn hostfxr_main_bundle_startupinfo = hostfxr_resolver_resolve_main_bundle_startupinfo(&fxr); + if (hostfxr_main_bundle_startupinfo != NULL) + { + const pal_char_t* host_path_cstr = host_path; + const pal_char_t* dotnet_root_cstr = fxr.dotnet_root != NULL && fxr.dotnet_root[0] != _X('\0') ? fxr.dotnet_root : NULL; + const pal_char_t* app_path_cstr = app_path[0] != _X('\0') ? app_path : NULL; + int64_t bundle_header_offset = bundle_marker_header_offset(); + + trace_info(_X("Invoking fx resolver [%s] hostfxr_main_bundle_startupinfo"), fxr.fxr_path); + trace_info(_X("Host path: [%s]"), host_path); + trace_info(_X("Dotnet path: [%s]"), fxr.dotnet_root != NULL ? fxr.dotnet_root : _X("")); + trace_info(_X("App path: [%s]"), app_path); + trace_info(_X("Bundle Header Offset: [%" PRId64 "]"), bundle_header_offset); + + hostfxr_set_error_writer_fn set_error_writer = hostfxr_resolver_resolve_set_error_writer(&fxr); + propagate_error_writer_state_t propagate_state; + propagate_error_writer_init(&propagate_state, set_error_writer); + rc = hostfxr_main_bundle_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr, bundle_header_offset); + propagate_error_writer_cleanup(&propagate_state); + } + else + { + trace_error(_X("The required library %s does not support single-file apps."), fxr.fxr_path); + report_outdated_framework_error(fxr.dotnet_root != NULL ? fxr.dotnet_root : _X(""), host_path); + rc = FrameworkMissingFailure; + } + } + else + { + hostfxr_main_startupinfo_fn hostfxr_main_startupinfo = hostfxr_resolver_resolve_main_startupinfo(&fxr); + if (hostfxr_main_startupinfo != NULL) + { + const pal_char_t* host_path_cstr = host_path; + const pal_char_t* dotnet_root_cstr = fxr.dotnet_root != NULL && fxr.dotnet_root[0] != _X('\0') ? fxr.dotnet_root : NULL; + const pal_char_t* app_path_cstr = app_path[0] != _X('\0') ? app_path : NULL; + + trace_info(_X("Invoking fx resolver [%s] hostfxr_main_startupinfo"), fxr.fxr_path); + trace_info(_X("Host path: [%s]"), host_path); + trace_info(_X("Dotnet path: [%s]"), fxr.dotnet_root != NULL ? fxr.dotnet_root : _X("")); + trace_info(_X("App path: [%s]"), app_path); + + hostfxr_set_error_writer_fn set_error_writer = hostfxr_resolver_resolve_set_error_writer(&fxr); + propagate_error_writer_state_t propagate_state; + propagate_error_writer_init(&propagate_state, set_error_writer); + + rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr); + + if (trace_get_error_writer() != NULL && rc == (int)FrameworkMissingFailure && set_error_writer == NULL) + { + report_outdated_framework_error(fxr.dotnet_root != NULL ? fxr.dotnet_root : _X(""), host_path); + } + + propagate_error_writer_cleanup(&propagate_state); + } +#if !defined(FEATURE_STATIC_HOST) + else + { + if (requires_hostfxr_startupinfo_interface) + { + trace_error(_X("The required library %s does not support relative app dll paths."), fxr.fxr_path); + rc = CoreHostEntryPointFailure; + } + else + { + trace_info(_X("Invoking fx resolver [%s] v1"), fxr.fxr_path); + + // Previous corehost trace messages must be printed before calling trace::setup in hostfxr + trace_flush(); + + hostfxr_main_fn main_fn_v1 = hostfxr_resolver_resolve_main_v1(&fxr); + if (main_fn_v1 != NULL) + { + rc = main_fn_v1(argc, argv); + } + else + { + trace_error(_X("The required library %s does not contain the expected entry point."), fxr.fxr_path); + rc = CoreHostEntryPointFailure; + } + } + } +#endif // !defined(FEATURE_STATIC_HOST) + } + + hostfxr_resolver_cleanup(&fxr); + free(app_root); + free(app_path); + free(host_path); + return rc; +} + +#if defined(_WIN32) +int __cdecl wmain(int argc, const pal_char_t* argv[]) +#else +int main(const int argc, const pal_char_t* argv[]) +#endif +{ + trace_setup(); + + if (trace_is_enabled()) + { + trace_info(_X("--- Invoked apphost [version: ") +#if defined(_WIN32) + _STRINGIFY(VER_PRODUCTVERSION_STR) +#else + _STRINGIFY(HOST_VERSION) _X(" @Commit: ") _STRINGIFY(REPO_COMMIT_HASH) +#endif + _X("] main = {")); + for (int i = 0; i < argc; ++i) + { + trace_info(_X("%s"), argv[i]); + } + trace_info(_X("}")); + } + +#if defined(_WIN32) + apphost_buffer_errors(); +#endif + + int exit_code = exe_start(argc, argv); + + trace_flush(); + +#if defined(_WIN32) + apphost_write_buffered_errors(exit_code); +#endif + + return exit_code; +} diff --git a/src/native/corehost/apphost/apphost.windows.c b/src/native/corehost/apphost/apphost.windows.c new file mode 100644 index 00000000000000..3660d7175ef3fa --- /dev/null +++ b/src/native/corehost/apphost/apphost.windows.c @@ -0,0 +1,458 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "apphost.windows.h" +#include "error_codes.h" +#include "pal.h" +#include "trace.h" +#include "utils.h" + +#include +#include +#include + +#define APPHOST_DETAILS_MESSAGE \ + _X("Architecture: ") _STRINGIFY(CURRENT_ARCH_NAME) _X("\n") \ + _X("App host version: ") _STRINGIFY(HOST_VERSION) _X("\n\n") + +// Allocate and format a string. Caller must free() the returned pointer. +static pal_char_t* format_alloc(const pal_char_t* format, ...) +{ + va_list args; + va_start(args, format); + int len = pal_strlen_vprintf(format, args); + va_end(args); + if (len < 0) + return NULL; + + pal_char_t* buffer = (pal_char_t*)malloc((size_t)(len + 1) * sizeof(pal_char_t)); + if (buffer == NULL) + return NULL; + + va_start(args, format); + pal_str_vprintf(buffer, (size_t)(len + 1), format, args); + va_end(args); + return buffer; +} + +// Return the next '\n'-delimited line at *cursor and advance *cursor past it, +// setting *line_len to the line length (excluding the newline). Returns NULL +// when the buffer is exhausted. The line is a slice of the buffer and is not +// null-terminated at *line_len. +static const pal_char_t* get_next_line(const pal_char_t** cursor, size_t* line_len) +{ + const pal_char_t* start = *cursor; + if (start == NULL || *start == _X('\0')) + return NULL; + + const pal_char_t* nl = pal_strchr(start, _X('\n')); + *line_len = (nl != NULL) ? (size_t)(nl - start) : pal_strlen(start); + *cursor = (nl != NULL) ? nl + 1 : start + *line_len; + return start; +} + +static pal_char_t* g_buffered_errors; + +static void __cdecl buffering_trace_writer(const pal_char_t* message) +{ + // Append the message and a trailing newline to the buffer for later use. + size_t existing_len = (g_buffered_errors != NULL) ? pal_strlen(g_buffered_errors) : 0; + size_t message_len = pal_strlen(message); + pal_char_t* grown = (pal_char_t*)realloc(g_buffered_errors, (existing_len + message_len + 2) * sizeof(pal_char_t)); + if (grown != NULL) + { + memcpy(grown + existing_len, message, message_len * sizeof(pal_char_t)); + grown[existing_len + message_len] = _X('\n'); + grown[existing_len + message_len + 1] = _X('\0'); + g_buffered_errors = grown; + } + + // Also write to stderr immediately + pal_err_print_line(message); +} + +// Determines if the current module (apphost executable) is marked as a Windows GUI application +static bool is_gui_application(void) +{ + HMODULE module = GetModuleHandleW(NULL); + assert(module != NULL); + + // https://learn.microsoft.com/windows/win32/debug/pe-format + BYTE* bytes = (BYTE*)module; + UINT32 pe_header_offset = (UINT32)((IMAGE_DOS_HEADER*)bytes)->e_lfanew; + UINT16 subsystem = ((IMAGE_NT_HEADERS*)(bytes + pe_header_offset))->OptionalHeader.Subsystem; + + return subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI; +} + +static void write_errors_to_event_log(const pal_char_t* executable_path, const pal_char_t* executable_name) +{ + // Report errors to the Windows Event Log. + HANDLE eventSource = RegisterEventSourceW(NULL, _X(".NET Runtime")); + const DWORD traceErrorID = 1023; // Matches CoreCLR ERT_UnmanagedFailFast + pal_char_t* message = format_alloc( + _X("Description: A .NET application failed.\n") + _X("Application: %s\n") + _X("Path: %s\n") + _X("Message: %s\n"), + executable_name, + executable_path, + g_buffered_errors != NULL ? g_buffered_errors : _X("")); + + if (message != NULL) + { + LPCWSTR messages[] = { message }; + ReportEventW(eventSource, EVENTLOG_ERROR_TYPE, 0, traceErrorID, NULL, 1, 0, messages, NULL); + free(message); + } + + DeregisterEventSource(eventSource); +} + +// Extract the applaunch URL from a buffered error line, if present. On success +// writes the URL into url (size url_len, safely truncated) and returns true. +static bool try_get_url_from_line(const pal_char_t* line, size_t line_len, pal_char_t* url, size_t url_len) +{ + const pal_char_t url_prefix[] = DOTNET_CORE_APPLAUNCH_URL _X("?"); + if (utils_starts_with(line, line_len, url_prefix, STRING_LENGTH(url_prefix), true)) + { + pal_str_printf(url, url_len, _X("%.*s"), (int)line_len, line); + return true; + } + + const pal_char_t url_prefix_before_7_0[] = _X(" - ") DOTNET_CORE_APPLAUNCH_URL _X("?"); + if (utils_starts_with(line, line_len, url_prefix_before_7_0, STRING_LENGTH(url_prefix_before_7_0), true)) + { + // Strip the " - " indent so the stored URL begins at the applaunch URL. + size_t offset = STRING_LENGTH(_X(" - ")); + pal_str_printf(url, url_len, _X("%.*s"), (int)(line_len - offset), line + offset); + return true; + } + + return false; +} + +static void open_url(const pal_char_t* url) +{ + // Open the URL in default browser + ShellExecuteW( + NULL, + _X("open"), + url, + NULL, + NULL, + SW_SHOWNORMAL); +} + +static bool enable_visual_styles(void) +{ + // Create an activation context using a manifest that enables visual styles + // See https://learn.microsoft.com/windows/win32/controls/cookbook-overview + // To avoid increasing the size of all applications by embedding a manifest, + // we just use the WindowsShell manifest. + const pal_char_t manifest_name[] = _X("WindowsShell.Manifest"); + + // GetWindowsDirectoryW writes at most MAX_PATH chars; reserve room to append + // a separator and the manifest file name. + pal_char_t manifest[MAX_PATH + ARRAY_SIZE(manifest_name)]; + UINT len = GetWindowsDirectoryW(manifest, MAX_PATH); + if (len == 0 || len >= MAX_PATH) + { + trace_verbose(_X("GetWindowsDirectory failed. Error code: %d"), GetLastError()); + return false; + } + + utils_append_path(manifest, ARRAY_SIZE(manifest), manifest_name); + + // Since this is only for errors shown when the process is about to exit, we + // skip releasing/deactivating the context to minimize impact on apphost size + ACTCTXW actctx = { sizeof(ACTCTXW), 0, manifest }; + HANDLE context_handle = CreateActCtxW(&actctx); + if (context_handle == INVALID_HANDLE_VALUE) + { + trace_verbose(_X("CreateActCtxW failed using manifest '%s'. Error code: %d"), manifest, GetLastError()); + return false; + } + + ULONG_PTR cookie; + if (ActivateActCtx(context_handle, &cookie) == FALSE) + { + trace_verbose(_X("ActivateActCtx failed. Error code: %d"), GetLastError()); + return false; + } + + return true; +} + +// Build a hyperlink for display in a task dialog. +static pal_char_t* format_hyperlink(const pal_char_t* url) +{ + size_t url_len = pal_strlen(url); + pal_char_t* display = (pal_char_t*)malloc((url_len * 2 + 1) * sizeof(pal_char_t)); + if (display == NULL) + return NULL; + + // & indicates an accelerator key when in hyperlink text. + // Replace & with && such that the single ampersand is shown. + size_t j = 0; + for (size_t i = 0; i < url_len; ++i) + { + display[j++] = url[i]; + if (url[i] == _X('&')) + display[j++] = _X('&'); + } + display[j] = _X('\0'); + + pal_char_t* result = format_alloc(_X("%s"), url, display); + free(display); + return result; +} + +static HRESULT CALLBACK task_dialog_callback(HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) +{ + (void)hwnd; + (void)wParam; + (void)lpRefData; + + if (uNotification == TDN_HYPERLINK_CLICKED && lParam != 0) + open_url((LPCWSTR)lParam); + + return S_OK; +} + +static bool try_show_error_with_task_dialog( + const pal_char_t* executable_name, + const pal_char_t* instruction, + const pal_char_t* details, + const pal_char_t* url) +{ + HMODULE comctl32 = LoadLibraryExW(L"comctl32.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (comctl32 == NULL) + return false; + + typedef HRESULT (WINAPI* task_dialog_indirect)( + const TASKDIALOGCONFIG* pTaskConfig, + int* pnButton, + int* pnRadioButton, + BOOL* pfVerificationFlagChecked); + + task_dialog_indirect task_dialog_indirect_func = (task_dialog_indirect)GetProcAddress(comctl32, "TaskDialogIndirect"); + if (task_dialog_indirect_func == NULL) + { + FreeLibrary(comctl32); + return false; + } + + TASKDIALOGCONFIG config = { 0 }; + config.cbSize = sizeof(TASKDIALOGCONFIG); + config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_ENABLE_HYPERLINKS | TDF_SIZE_TO_CONTENT | TDF_USE_COMMAND_LINKS; + config.dwCommonButtons = TDCBF_CLOSE_BUTTON; + config.pszWindowTitle = executable_name; + config.pszMainInstruction = instruction; + + // Use the application's icon if available + HMODULE exe_module = GetModuleHandleW(NULL); + assert(exe_module != NULL); + if (FindResourceW(exe_module, IDI_APPLICATION, RT_GROUP_ICON) != NULL) + { + config.hInstance = exe_module; + config.pszMainIcon = IDI_APPLICATION; + } + else + { + config.pszMainIcon = TD_ERROR_ICON; + } + + int download_button_id = 1000; + TASKDIALOG_BUTTON download_button = { download_button_id, _X("Download it now\n") _X("You will need to run the downloaded installer") }; + config.cButtons = 1; + config.pButtons = &download_button; + config.nDefaultButton = download_button_id; + + pal_char_t* app_launch_link = format_hyperlink(DOTNET_APP_LAUNCH_FAILED_URL); + pal_char_t* download_link = format_hyperlink(url); + pal_char_t* expanded_info = format_alloc( + _X("%s") DOC_LINK_INTRO _X("\n%s\n\nDownload link:\n%s"), + details, + app_launch_link != NULL ? app_launch_link : _X(""), + download_link != NULL ? download_link : _X("")); + config.pszExpandedInformation = expanded_info; + + // Callback to handle hyperlink clicks + config.pfCallback = task_dialog_callback; + + int clicked_button; + bool succeeded = SUCCEEDED(task_dialog_indirect_func(&config, &clicked_button, NULL, NULL)); + if (succeeded && clicked_button == download_button_id) + open_url(url); + + FreeLibrary(comctl32); + free(app_launch_link); + free(download_link); + free(expanded_info); + return succeeded; +} + +static void show_error_dialog(const pal_char_t* executable_name, int error_code) +{ + pal_char_t* gui_errors_disabled = pal_getenv(_X("DOTNET_DISABLE_GUI_ERRORS")); + if (gui_errors_disabled != NULL) + { + bool disabled = pal_xtoi(gui_errors_disabled) == 1; + free(gui_errors_disabled); + if (disabled) + return; + } + + const pal_char_t* instruction = NULL; + pal_char_t* details = NULL; + pal_char_t url[MAX_DOWNLOAD_URL_LEN]; + url[0] = _X('\0'); + + if (error_code == CoreHostLibMissingFailure) + { + instruction = INSTALL_NET_DESKTOP_ERROR_MESSAGE; + + const pal_char_t* cursor = g_buffered_errors; + const pal_char_t* line; + size_t line_len; + while ((line = get_next_line(&cursor, &line_len)) != NULL) + { + if (try_get_url_from_line(line, line_len, url, ARRAY_SIZE(url))) + break; + } + } + else if (error_code == FrameworkMissingFailure) + { + // We don't have a great way of passing out different kinds of detailed error info across components, so + // just match the expected error string. See fx_resolver.messages.cpp. + instruction = INSTALL_OR_UPDATE_NET_ERROR_MESSAGE; + + const pal_char_t prefix[] = _X("Framework: '"); + const pal_char_t prefix_before_7_0[] = _X("The framework '"); + const pal_char_t suffix_before_7_0[] = _X(" was not found."); + const pal_char_t custom_prefix[] = _X(" _ "); + + const pal_char_t* cursor = g_buffered_errors; + const pal_char_t* line; + size_t line_len; + while ((line = get_next_line(&cursor, &line_len)) != NULL) + { + bool has_prefix = utils_starts_with(line, line_len, prefix, STRING_LENGTH(prefix), true); + if (has_prefix + || (utils_starts_with(line, line_len, prefix_before_7_0, STRING_LENGTH(prefix_before_7_0), true) + && utils_ends_with(line, line_len, suffix_before_7_0, STRING_LENGTH(suffix_before_7_0), true))) + { + free(details); + if (has_prefix) + { + size_t offset = STRING_LENGTH(prefix) - 1; + details = format_alloc(_X("Required: %.*s\n\n"), (int)(line_len - offset), line + offset); + } + else + { + size_t prefix_len = STRING_LENGTH(prefix_before_7_0) - 1; + size_t suffix_len = STRING_LENGTH(suffix_before_7_0); + size_t len = (line_len > prefix_len + suffix_len) ? line_len - prefix_len - suffix_len : 0; + details = format_alloc(_X("Required: %.*s\n\n"), (int)len, line + prefix_len); + } + } + else if (utils_starts_with(line, line_len, custom_prefix, STRING_LENGTH(custom_prefix), true)) + { + size_t offset = STRING_LENGTH(custom_prefix); + free(details); + details = format_alloc(_X("%.*s\n\n"), (int)(line_len - offset), line + offset); + } + else if (try_get_url_from_line(line, line_len, url, ARRAY_SIZE(url))) + { + break; + } + } + } + else if (error_code == BundleExtractionFailure) + { + const pal_char_t bundle_error_prefix[] = _X("Bundle header version compatibility check failed."); + const pal_char_t* cursor = g_buffered_errors; + const pal_char_t* line; + size_t line_len; + while ((line = get_next_line(&cursor, &line_len)) != NULL) + { + if (utils_starts_with(line, line_len, bundle_error_prefix, STRING_LENGTH(bundle_error_prefix), true)) + { + instruction = INSTALL_NET_DESKTOP_ERROR_MESSAGE; + + utils_get_download_url(url, ARRAY_SIZE(url), NULL, NULL); + size_t len = pal_strlen(url); + pal_str_printf(url + len, ARRAY_SIZE(url) - len, _X("&apphost_version=") _STRINGIFY(HOST_VERSION)); + break; + } + } + + if (instruction == NULL) + return; + } + else + { + return; + } + + assert(url[0] != _X('\0')); + assert(is_gui_application()); + + size_t url_len = pal_strlen(url); + pal_str_printf(url + url_len, ARRAY_SIZE(url) - url_len, _X("&gui=true")); + const pal_char_t* details_text = details != NULL ? details : APPHOST_DETAILS_MESSAGE; + + trace_verbose(_X("Showing error dialog for application: '%s' - error code: 0x%x - url: '%s' - details: %s"), + executable_name, error_code, url, details_text); + + // Prefer the rich task dialog (requires enabling visual styles). + if (enable_visual_styles() && try_show_error_with_task_dialog(executable_name, instruction, details_text, url)) + { + free(details); + return; + } + + // Fall back to a plain message box if the task dialog can't be shown. + pal_char_t* dialog_message = format_alloc( + _X("%s\n\n%s") DOC_LINK_INTRO _X("\n") DOTNET_APP_LAUNCH_FAILED_URL _X("\n\n") + _X("Would you like to download it now?"), + instruction, + details_text); + if (dialog_message != NULL + && MessageBoxW(NULL, dialog_message, executable_name, MB_ICONERROR | MB_YESNO) == IDYES) + { + open_url(url); + } + + free(dialog_message); + free(details); +} + +void apphost_buffer_errors(void) +{ + trace_verbose(_X("Redirecting errors to custom writer.")); + trace_set_error_writer(buffering_trace_writer); +} + +void apphost_write_buffered_errors(int error_code) +{ + if (g_buffered_errors == NULL) + return; + + pal_char_t* executable_path = pal_get_own_executable_path(); + pal_char_t executable_name[MAX_PATH] = { 0 }; + if (executable_path != NULL) + { + utils_get_filename(executable_path, executable_name, ARRAY_SIZE(executable_name)); + } + + write_errors_to_event_log(executable_path != NULL ? executable_path : _X(""), executable_name); + + if (is_gui_application()) + show_error_dialog(executable_name, error_code); + + free(executable_path); + free(g_buffered_errors); + g_buffered_errors = NULL; +} diff --git a/src/native/corehost/apphost/apphost.windows.cpp b/src/native/corehost/apphost/apphost.windows.cpp deleted file mode 100644 index f1320b1d5e28dc..00000000000000 --- a/src/native/corehost/apphost/apphost.windows.cpp +++ /dev/null @@ -1,367 +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 "apphost.windows.h" -#include "error_codes.h" -#include "pal.h" -#include "trace.h" -#include "utils.h" - -#include -#include - -namespace -{ - pal::string_t g_buffered_errors; - - void __cdecl buffering_trace_writer(const pal::char_t* message) - { - // Add to buffer for later use. - g_buffered_errors.append(message).append(_X("\n")); - // Also write to stderr immediately - pal::err_print_line(message); - } - - // Determines if the current module (apphost executable) is marked as a Windows GUI application - bool is_gui_application() - { - HMODULE module = ::GetModuleHandleW(nullptr); - assert(module != nullptr); - - // https://learn.microsoft.com/windows/win32/debug/pe-format - BYTE *bytes = reinterpret_cast(module); - UINT32 pe_header_offset = reinterpret_cast(bytes)->e_lfanew; - UINT16 subsystem = reinterpret_cast(bytes + pe_header_offset)->OptionalHeader.Subsystem; - - return subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI; - } - - void write_errors_to_event_log(const pal::char_t *executable_path, const pal::char_t *executable_name) - { - // Report errors to the Windows Event Log. - auto eventSource = ::RegisterEventSourceW(nullptr, _X(".NET Runtime")); - const DWORD traceErrorID = 1023; // Matches CoreCLR ERT_UnmanagedFailFast - pal::string_t message; - message.append(_X("Description: A .NET application failed.\n")); - message.append(_X("Application: ")).append(executable_name).append(_X("\n")); - message.append(_X("Path: ")).append(executable_path).append(_X("\n")); - message.append(_X("Message: ")).append(g_buffered_errors).append(_X("\n")); - - LPCWSTR messages[] = {message.c_str()}; - ::ReportEventW(eventSource, EVENTLOG_ERROR_TYPE, 0, traceErrorID, nullptr, 1, 0, messages, nullptr); - ::DeregisterEventSource(eventSource); - } - - bool try_get_url_from_line(const pal::string_t& line, pal::string_t& url) - { - const pal::char_t url_prefix[] = DOTNET_CORE_APPLAUNCH_URL _X("?"); - if (utils::starts_with(line, url_prefix, true)) - { - url.assign(line); - return true; - } - - const pal::char_t url_prefix_before_7_0[] = _X(" - ") DOTNET_CORE_APPLAUNCH_URL _X("?"); - if (utils::starts_with(line, url_prefix_before_7_0, true)) - { - size_t offset = utils::strlen(url_prefix_before_7_0) - utils::strlen(DOTNET_CORE_APPLAUNCH_URL) - 1; - url.assign(line.substr(offset, line.length() - offset)); - return true; - } - - return false; - } - - pal::string_t get_apphost_details_message() - { - pal::string_t msg = _X("Architecture: "); - msg.append(get_current_arch_name()); - msg.append(_X("\n") - _X("App host version: ") _STRINGIFY(HOST_VERSION) _X("\n\n")); - return msg; - } - - void open_url(const pal::char_t* url) - { - // Open the URL in default browser - ::ShellExecuteW( - nullptr, - _X("open"), - url, - nullptr, - nullptr, - SW_SHOWNORMAL); - } - - bool enable_visual_styles() - { - // Create an activation context using a manifest that enables visual styles - // See https://learn.microsoft.com/windows/win32/controls/cookbook-overview - // To avoid increasing the size of all applications by embedding a manifest, - // we just use the WindowsShell manifest. - pal::char_t buf[MAX_PATH]; - UINT len = ::GetWindowsDirectoryW(buf, MAX_PATH); - if (len == 0 || len >= MAX_PATH) - { - trace::verbose(_X("GetWindowsDirectory failed. Error code: %d"), ::GetLastError()); - return false; - } - - pal::string_t manifest(buf); - append_path(&manifest, _X("WindowsShell.Manifest")); - - // Since this is only for errors shown when the process is about to exit, we - // skip releasing/deactivating the context to minimize impact on apphost size - ACTCTXW actctx = { sizeof(ACTCTXW), 0, manifest.c_str() }; - HANDLE context_handle = ::CreateActCtxW(&actctx); - if (context_handle == INVALID_HANDLE_VALUE) - { - trace::verbose(_X("CreateActCtxW failed using manifest '%s'. Error code: %d"), manifest.c_str(), ::GetLastError()); - return false; - } - - ULONG_PTR cookie; - if (::ActivateActCtx(context_handle, &cookie) == FALSE) - { - trace::verbose(_X("ActivateActCtx failed. Error code: %d"), ::GetLastError()); - return false; - } - - return true; - } - - void append_hyperlink(pal::string_t& str, const pal::char_t* url) - { - str.append(_X("")); - - // & indicates an accelerator key when in hyperlink text. - // Replace & with && such that the single ampersand is shown. - for (size_t i = 0; i < pal::strlen(url); ++i) - { - str.push_back(url[i]); - if (url[i] == _X('&')) - str.push_back(_X('&')); - } - - str.append(_X("")); - } - - bool try_show_error_with_task_dialog( - const pal::char_t *executable_name, - const pal::char_t *instruction, - const pal::char_t *details, - const pal::char_t *url) - { - HMODULE comctl32 = ::LoadLibraryExW(L"comctl32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (comctl32 == nullptr) - return false; - - typedef HRESULT (WINAPI* task_dialog_indirect)( - const TASKDIALOGCONFIG* pTaskConfig, - int* pnButton, - int* pnRadioButton, - BOOL* pfVerificationFlagChecked); - - task_dialog_indirect task_dialog_indirect_func = (task_dialog_indirect)::GetProcAddress(comctl32, "TaskDialogIndirect"); - if (task_dialog_indirect_func == nullptr) - { - ::FreeLibrary(comctl32); - return false; - } - - TASKDIALOGCONFIG config{0}; - config.cbSize = sizeof(TASKDIALOGCONFIG); - config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_ENABLE_HYPERLINKS | TDF_SIZE_TO_CONTENT | TDF_USE_COMMAND_LINKS; - config.dwCommonButtons = TDCBF_CLOSE_BUTTON; - config.pszWindowTitle = executable_name; - config.pszMainInstruction = instruction; - - // Use the application's icon if available - HMODULE exe_module = ::GetModuleHandleW(nullptr); - assert(exe_module != nullptr); - if (::FindResourceW(exe_module, IDI_APPLICATION, RT_GROUP_ICON) != nullptr) - { - config.hInstance = exe_module; - config.pszMainIcon = IDI_APPLICATION; - } - else - { - config.pszMainIcon = TD_ERROR_ICON; - } - - int download_button_id = 1000; - TASKDIALOG_BUTTON download_button { download_button_id, _X("Download it now\n") _X("You will need to run the downloaded installer") }; - config.cButtons = 1; - config.pButtons = &download_button; - config.nDefaultButton = download_button_id; - - pal::string_t expanded_info(details); - expanded_info.append(DOC_LINK_INTRO _X("\n")); - append_hyperlink(expanded_info, DOTNET_APP_LAUNCH_FAILED_URL); - expanded_info.append(_X("\n\nDownload link:\n")); - append_hyperlink(expanded_info, url); - config.pszExpandedInformation = expanded_info.c_str(); - - // Callback to handle hyperlink clicks - config.pfCallback = [](HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) -> HRESULT - { - if (uNotification == TDN_HYPERLINK_CLICKED && lParam != NULL) - open_url(reinterpret_cast(lParam)); - - return S_OK; - }; - - int clicked_button; - bool succeeded = SUCCEEDED(task_dialog_indirect_func(&config, &clicked_button, nullptr, nullptr)); - if (succeeded && clicked_button == download_button_id) - open_url(url); - - ::FreeLibrary(comctl32); - return succeeded; - } - - void show_error_dialog(const pal::char_t* executable_name, int error_code) - { - pal::string_t gui_errors_disabled; - if (pal::getenv(_X("DOTNET_DISABLE_GUI_ERRORS"), &gui_errors_disabled) && pal::xtoi(gui_errors_disabled.c_str()) == 1) - return; - - const pal::char_t* instruction = nullptr; - pal::string_t details; - pal::string_t url; - if (error_code == StatusCode::CoreHostLibMissingFailure) - { - instruction = INSTALL_NET_DESKTOP_ERROR_MESSAGE; - details = get_apphost_details_message(); - pal::string_t line; - pal::stringstream_t ss(g_buffered_errors); - while (std::getline(ss, line, _X('\n'))) - { - if (try_get_url_from_line(line, url)) - { - break; - } - } - } - else if (error_code == StatusCode::FrameworkMissingFailure) - { - // We don't have a great way of passing out different kinds of detailed error info across components, so - // just match the expected error string. See fx_resolver.messages.cpp. - instruction = INSTALL_OR_UPDATE_NET_ERROR_MESSAGE; - pal::string_t line; - pal::stringstream_t ss(g_buffered_errors); - bool foundCustomMessage = false; - while (std::getline(ss, line, _X('\n'))) - { - const pal::char_t prefix[] = _X("Framework: '"); - const pal::char_t prefix_before_7_0[] = _X("The framework '"); - const pal::char_t suffix_before_7_0[] = _X(" was not found."); - const pal::char_t custom_prefix[] = _X(" _ "); - bool has_prefix = utils::starts_with(line, prefix, true); - if (has_prefix - || (utils::starts_with(line, prefix_before_7_0, true) && utils::ends_with(line, suffix_before_7_0, true))) - { - details.append(_X("Required: ")); - if (has_prefix) - { - details.append(line.substr(utils::strlen(prefix) - 1)); - } - else - { - size_t prefix_len = utils::strlen(prefix_before_7_0) - 1; - details.append(line.substr(prefix_len, line.length() - prefix_len - utils::strlen(suffix_before_7_0))); - } - - details.append(_X("\n\n")); - foundCustomMessage = true; - } - else if (utils::starts_with(line, custom_prefix, true)) - { - details.erase(); - details.append(line.substr(utils::strlen(custom_prefix))); - details.append(_X("\n\n")); - foundCustomMessage = true; - } - else if (try_get_url_from_line(line, url)) - { - break; - } - } - - if (!foundCustomMessage) - details.append(get_apphost_details_message()); - } - else if (error_code == StatusCode::BundleExtractionFailure) - { - pal::string_t line; - pal::stringstream_t ss(g_buffered_errors); - while (std::getline(ss, line, _X('\n'))) - { - if (utils::starts_with(line, _X("Bundle header version compatibility check failed."), true)) - { - instruction = INSTALL_NET_DESKTOP_ERROR_MESSAGE; - details = get_apphost_details_message(); - url = get_download_url(); - url.append(_X("&apphost_version=")); - url.append(_STRINGIFY(HOST_VERSION)); - } - } - - if (instruction == nullptr) - return; - } - else - { - return; - } - - assert(url.length() > 0); - assert(is_gui_application()); - url.append(_X("&gui=true")); - - trace::verbose(_X("Showing error dialog for application: '%s' - error code: 0x%x - url: '%s' - details: %s"), executable_name, error_code, url.c_str(), details.c_str()); - - if (enable_visual_styles()) - { - // Task dialog requires enabling visual styles - if (try_show_error_with_task_dialog(executable_name, instruction, details.c_str(), url.c_str())) - return; - } - - pal::string_t dialog_message(instruction); - dialog_message.append(_X("\n\n")); - dialog_message.append(details); - dialog_message.append(DOC_LINK_INTRO _X("\n") DOTNET_APP_LAUNCH_FAILED_URL _X("\n\n") - _X("Would you like to download it now?")); - if (::MessageBoxW(nullptr, dialog_message.c_str(), executable_name, MB_ICONERROR | MB_YESNO) == IDYES) - { - open_url(url.c_str()); - } - } -} - -void apphost::buffer_errors() -{ - trace::verbose(_X("Redirecting errors to custom writer.")); - trace::set_error_writer(buffering_trace_writer); -} - -void apphost::write_buffered_errors(int error_code) -{ - if (g_buffered_errors.empty()) - return; - - pal::string_t executable_path; - pal::string_t executable_name; - if (pal::get_own_executable_path(&executable_path)) - { - executable_name = get_filename(executable_path); - } - - write_errors_to_event_log(executable_path.c_str(), executable_name.c_str()); - - if (is_gui_application()) - show_error_dialog(executable_name.c_str(), error_code); -} diff --git a/src/native/corehost/apphost/apphost.windows.h b/src/native/corehost/apphost/apphost.windows.h index 65e1cd759c3dc6..276e0e854112bd 100644 --- a/src/native/corehost/apphost/apphost.windows.h +++ b/src/native/corehost/apphost/apphost.windows.h @@ -4,10 +4,15 @@ #ifndef __APPHOST_WINDOWS_H__ #define __APPHOST_WINDOWS_H__ -namespace apphost -{ - void buffer_errors(); - void write_buffered_errors(int error_code); +#ifdef __cplusplus +extern "C" { +#endif + +void apphost_buffer_errors(void); +void apphost_write_buffered_errors(int error_code); + +#ifdef __cplusplus } +#endif #endif // __APPHOST_WINDOWS_H__ diff --git a/src/native/corehost/apphost/apphost_hostfxr_resolver.h b/src/native/corehost/apphost/apphost_hostfxr_resolver.h new file mode 100644 index 00000000000000..865f78b54f44a2 --- /dev/null +++ b/src/native/corehost/apphost/apphost_hostfxr_resolver.h @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef APPHOST_HOSTFXR_RESOLVER_H +#define APPHOST_HOSTFXR_RESOLVER_H + +#include +#include +#include "hostfxr.h" +#include "error_codes.h" +#include "pal.h" // for pal_char_t + +// Hostfxr resolver state. +// +// Ownership: +// - dotnet_root and fxr_path are allocated by hostfxr_resolver_init and freed by hostfxr_resolver_cleanup. +typedef struct hostfxr_resolver +{ + pal_dll_t hostfxr_dll; + pal_char_t* dotnet_root; // dynamically allocated, NULL if not set + pal_char_t* fxr_path; // dynamically allocated, NULL if not set + int status_code; // StatusCode enum value +} hostfxr_resolver_t; + +#ifdef __cplusplus +extern "C" { +#endif + +// Initialize the resolver: find and load hostfxr. +void hostfxr_resolver_init(hostfxr_resolver_t* resolver, const pal_char_t* app_root); + +// Clean up the resolver: unload hostfxr if loaded. +void hostfxr_resolver_cleanup(hostfxr_resolver_t* resolver); + +// Resolve function pointers from the loaded hostfxr. +hostfxr_main_bundle_startupinfo_fn hostfxr_resolver_resolve_main_bundle_startupinfo(const hostfxr_resolver_t* resolver); +hostfxr_set_error_writer_fn hostfxr_resolver_resolve_set_error_writer(const hostfxr_resolver_t* resolver); +hostfxr_main_startupinfo_fn hostfxr_resolver_resolve_main_startupinfo(const hostfxr_resolver_t* resolver); +hostfxr_main_fn hostfxr_resolver_resolve_main_v1(const hostfxr_resolver_t* resolver); + +#ifdef __cplusplus +} +#endif + +#endif // APPHOST_HOSTFXR_RESOLVER_H diff --git a/src/native/corehost/apphost/bundle_marker.cpp b/src/native/corehost/apphost/bundle_marker.c similarity index 59% rename from src/native/corehost/apphost/bundle_marker.cpp rename to src/native/corehost/apphost/bundle_marker.c index aac23e5f328653..aa8b1f85dd519d 100644 --- a/src/native/corehost/apphost/bundle_marker.cpp +++ b/src/native/corehost/apphost/bundle_marker.c @@ -2,28 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "bundle_marker.h" -#include "pal.h" -#include "trace.h" -#include "utils.h" -int64_t bundle_marker_t::header_offset() +#include +#include + +#pragma pack(push, 1) +typedef union +{ + uint8_t placeholder[40]; + struct + { + int64_t bundle_header_offset; + uint8_t signature[32]; + } locator; +} bundle_marker_data_t; +#pragma pack(pop) + +int64_t bundle_marker_header_offset(void) { // Contains the bundle_placeholder default value at compile time. - // If this is a single-file bundle, the first 8 bytes are replaced + // If this is a single-file bundle, the first 8 bytes are replaced // by "dotnet publish" with the offset where the bundle_header is located. - static volatile uint8_t placeholder[] = + static volatile uint8_t placeholder[] = { - // 8 bytes represent the bundle header-offset + // 8 bytes represent the bundle header-offset // Zero for non-bundle apphosts (default). 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 64 bytes represent the bundle signature: SHA-256 for ".net core bundle" + // 32 bytes represent the bundle signature: SHA-256 for ".net core bundle" 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38, 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32, 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18, 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae }; - volatile bundle_marker_t* marker = reinterpret_cast(placeholder); + volatile bundle_marker_data_t* marker = (volatile bundle_marker_data_t*)placeholder; return marker->locator.bundle_header_offset; } diff --git a/src/native/corehost/apphost/bundle_marker.h b/src/native/corehost/apphost/bundle_marker.h index 7888dbcb1393f6..3563aa900c1920 100644 --- a/src/native/corehost/apphost/bundle_marker.h +++ b/src/native/corehost/apphost/bundle_marker.h @@ -1,29 +1,27 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#ifndef __BUNDLE_MARKER_H__ -#define __BUNDLE_MARKER_H__ +#ifndef BUNDLE_MARKER_H +#define BUNDLE_MARKER_H -#include +#include +#include -#pragma pack(push, 1) - union bundle_marker_t - { - public: - uint8_t placeholder[40]; - struct - { - int64_t bundle_header_offset; - uint8_t signature[32]; - } locator; +#ifdef __cplusplus +extern "C" { +#endif - static int64_t header_offset(); - static bool is_bundle() - { - return header_offset() != 0; - } - }; -#pragma pack(pop) +// Returns the bundle header offset. Zero for non-bundle apphosts. +int64_t bundle_marker_header_offset(void); +// Returns true if this is a bundled single-file app. +static inline bool bundle_marker_is_bundle(void) +{ + return bundle_marker_header_offset() != 0; +} -#endif // __BUNDLE_MARKER_H__ +#ifdef __cplusplus +} +#endif + +#endif // BUNDLE_MARKER_H diff --git a/src/native/corehost/apphost/standalone/CMakeLists.txt b/src/native/corehost/apphost/standalone/CMakeLists.txt index 17b114fd0c373c..c9ff46b61fb91f 100644 --- a/src/native/corehost/apphost/standalone/CMakeLists.txt +++ b/src/native/corehost/apphost/standalone/CMakeLists.txt @@ -4,44 +4,37 @@ include_directories(..) set(SOURCES - ../bundle_marker.cpp - ./hostfxr_resolver.cpp - ../../corehost.cpp + ../apphost.c + ../bundle_marker.c + ./apphost_hostfxr_resolver.c ) set(HEADERS ../bundle_marker.h - ../../hostfxr_resolver.h + ../../hostmisc/pal.h + ../apphost_hostfxr_resolver.h ) +add_compile_definitions(FEATURE_APPHOST) + if(CLR_CMAKE_TARGET_WIN32) - add_compile_definitions(UNICODE) list(APPEND SOURCES - ../apphost.windows.cpp) - + ../apphost.windows.c) list(APPEND HEADERS ../apphost.windows.h) -endif() -if(CLR_CMAKE_TARGET_WIN32) - list(APPEND SOURCES ${HEADERS}) + add_compile_definitions(UNICODE) endif() -add_compile_definitions(FEATURE_APPHOST) +list(APPEND SOURCES ${HEADERS}) add_executable(apphost ${SOURCES} ${RESOURCES}) -target_link_libraries(apphost PRIVATE hostmisc fxr_resolver) - -add_sanitizer_runtime_support(apphost) - -if(NOT CLR_CMAKE_TARGET_WIN32) - disable_pax_mprotect(apphost) -endif() - -install_with_stripped_symbols(apphost TARGETS corehost) +target_link_libraries(apphost PRIVATE fxr_resolver) if(CLR_CMAKE_TARGET_WIN32) + target_link_libraries(apphost PRIVATE hostmisc_c minipal_objects shell32) + # Disable manifest generation into the file .exe on Windows target_link_options(apphost PRIVATE "/MANIFEST:NO") @@ -49,11 +42,14 @@ if(CLR_CMAKE_TARGET_WIN32) if (CLR_CMAKE_HOST_ARCH_AMD64) target_link_options(apphost PRIVATE "/CETCOMPAT") endif() +else() + target_link_libraries(apphost PRIVATE hostmisc_c minipal_objects m) + disable_pax_mprotect(apphost) endif() -if (CLR_CMAKE_TARGET_WIN32) - target_link_libraries(apphost PRIVATE shell32) -endif() +add_sanitizer_runtime_support(apphost) + +install_with_stripped_symbols(apphost TARGETS corehost) if (CLR_CMAKE_HOST_APPLE) adhoc_sign_with_entitlements(apphost "${CLR_ENG_NATIVE_DIR}/entitlements.plist") diff --git a/src/native/corehost/apphost/standalone/apphost_hostfxr_resolver.c b/src/native/corehost/apphost/standalone/apphost_hostfxr_resolver.c new file mode 100644 index 00000000000000..02408275b6310c --- /dev/null +++ b/src/native/corehost/apphost/standalone/apphost_hostfxr_resolver.c @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "apphost_hostfxr_resolver.h" +#include "trace.h" +#include "utils.h" +#include "fxr_resolver.h" + +#include +#include + +// SHA-256 of "dotnet-search" in UTF-8 +#define EMBED_DOTNET_SEARCH_HI_PART_UTF8 "19ff3e9c3602ae8e841925bb461a0adb" +#define EMBED_DOTNET_SEARCH_LO_PART_UTF8 "064a1f1903667a5e0d87e8f608f425ac" + +// \0 +#define EMBED_DOTNET_SEARCH_FULL_UTF8 ("\0\0" EMBED_DOTNET_SEARCH_HI_PART_UTF8 EMBED_DOTNET_SEARCH_LO_PART_UTF8) + +// Size of the embedded .NET search options buffer. +#define EMBED_DOTNET_SEARCH_SIZE 512 + +// Get the .NET search options that should be used. +// Returns false if options are invalid. +// out_app_relative_dotnet is a pal_char_t buffer that receives the app-relative dotnet path. +static bool try_get_dotnet_search_options(fxr_search_location* out_search_location, pal_char_t* out_app_relative_dotnet, size_t out_app_relative_dotnet_len) +{ + // Contains the EMBED_DOTNET_SEARCH_FULL_UTF8 value at compile time or app-relative .NET path written by the SDK. + // Always a narrow UTF-8 string, regardless of platform. + static char embed[EMBED_DOTNET_SEARCH_SIZE] = EMBED_DOTNET_SEARCH_FULL_UTF8; + + *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_search_location_app_relative) == 0) + return true; + + // Get the embedded app-relative .NET path (always narrow UTF-8) + const char* binding = &embed[2]; + size_t binding_len = strlen(binding); + + // Check if the path exceeds the max allowed size + enum { EMBED_APP_RELATIVE_DOTNET_MAX_SIZE = EMBED_DOTNET_SEARCH_SIZE - 3 }; // -2 for search location + null, -1 for null terminator + if (binding_len > EMBED_APP_RELATIVE_DOTNET_MAX_SIZE) + { + trace_error(_X("The app-relative .NET path is longer than the max allowed length (%d)"), EMBED_APP_RELATIVE_DOTNET_MAX_SIZE); + return false; + } + + // Check if the value is empty or the same as the placeholder + static const char hi_part[] = EMBED_DOTNET_SEARCH_HI_PART_UTF8; + static const char lo_part[] = EMBED_DOTNET_SEARCH_LO_PART_UTF8; + size_t hi_len = sizeof(hi_part) - 1; + size_t lo_len = sizeof(lo_part) - 1; + if (binding_len == 0 + || (binding_len >= (hi_len + lo_len) + && memcmp(binding, hi_part, hi_len) == 0 + && memcmp(binding + hi_len, lo_part, lo_len) == 0)) + { + trace_error(_X("The app-relative .NET path is not embedded.")); + return false; + } + + if (!pal_utf8_to_palstr(binding, out_app_relative_dotnet, out_app_relative_dotnet_len)) + { + trace_error(_X("The app-relative .NET path could not be retrieved from the executable image.")); + return false; + } + + trace_info(_X("Embedded app-relative .NET path: '%s'"), out_app_relative_dotnet); + return true; +} + +hostfxr_main_bundle_startupinfo_fn hostfxr_resolver_resolve_main_bundle_startupinfo(const hostfxr_resolver_t* resolver) +{ + assert(resolver->hostfxr_dll != NULL); + return (hostfxr_main_bundle_startupinfo_fn)pal_get_symbol(resolver->hostfxr_dll, "hostfxr_main_bundle_startupinfo"); +} + +hostfxr_set_error_writer_fn hostfxr_resolver_resolve_set_error_writer(const hostfxr_resolver_t* resolver) +{ + assert(resolver->hostfxr_dll != NULL); + return (hostfxr_set_error_writer_fn)pal_get_symbol(resolver->hostfxr_dll, "hostfxr_set_error_writer"); +} + +hostfxr_main_startupinfo_fn hostfxr_resolver_resolve_main_startupinfo(const hostfxr_resolver_t* resolver) +{ + assert(resolver->hostfxr_dll != NULL); + return (hostfxr_main_startupinfo_fn)pal_get_symbol(resolver->hostfxr_dll, "hostfxr_main_startupinfo"); +} + +hostfxr_main_fn hostfxr_resolver_resolve_main_v1(const hostfxr_resolver_t* resolver) +{ + assert(resolver->hostfxr_dll != NULL); + return (hostfxr_main_fn)pal_get_symbol(resolver->hostfxr_dll, "hostfxr_main"); +} + +void hostfxr_resolver_init(hostfxr_resolver_t* resolver, const pal_char_t* app_root) +{ + resolver->hostfxr_dll = NULL; + resolver->dotnet_root = NULL; + resolver->fxr_path = NULL; + resolver->status_code = Success; + + fxr_search_location search_loc = fxr_search_location_default; + pal_char_t app_relative_dotnet[EMBED_DOTNET_SEARCH_SIZE]; + app_relative_dotnet[0] = _X('\0'); + + if (!try_get_dotnet_search_options(&search_loc, app_relative_dotnet, ARRAY_SIZE(app_relative_dotnet))) + { + resolver->status_code = AppHostExeNotBoundFailure; + return; + } + + trace_info(_X(".NET root search location options: %d"), search_loc); + + pal_char_t* app_relative_dotnet_path = NULL; + if (app_relative_dotnet[0] != _X('\0')) + { + app_relative_dotnet_path = utils_append_path_alloc(app_root, app_relative_dotnet); + if (app_relative_dotnet_path == NULL) + { + // Allocation failed - treat as hard error to preserve search semantics + resolver->status_code = CoreHostLibMissingFailure; + return; + } + } + + pal_char_t* dotnet_root = NULL; + pal_char_t* fxr_path = NULL; + if (!fxr_resolver_try_get_path(app_root, search_loc, app_relative_dotnet_path, + &dotnet_root, &fxr_path)) + { + resolver->status_code = CoreHostLibMissingFailure; + } + else if (!pal_is_path_fully_qualified(fxr_path)) + { + trace_error(_X("Path to %s must be fully qualified: [%s]"), LIBFXR_NAME, fxr_path); + free(dotnet_root); + free(fxr_path); + resolver->status_code = CoreHostLibMissingFailure; + } + else if (pal_load_library(fxr_path, &resolver->hostfxr_dll)) + { + resolver->dotnet_root = dotnet_root; + resolver->fxr_path = fxr_path; + resolver->status_code = Success; + } + else + { + trace_error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, fxr_path); + free(dotnet_root); + free(fxr_path); + resolver->status_code = CoreHostLibLoadFailure; + } + + free(app_relative_dotnet_path); +} + +void hostfxr_resolver_cleanup(hostfxr_resolver_t* resolver) +{ + if (resolver->hostfxr_dll != NULL) + { + pal_unload_library(resolver->hostfxr_dll); + resolver->hostfxr_dll = NULL; + } + free(resolver->dotnet_root); + resolver->dotnet_root = NULL; + free(resolver->fxr_path); + resolver->fxr_path = NULL; +} diff --git a/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp b/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp deleted file mode 100644 index 53fdfb5d26bbb2..00000000000000 --- a/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp +++ /dev/null @@ -1,166 +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 - -#include "pal.h" -#include "fxr_resolver.h" -#include "trace.h" -#include "utils.h" -#include "error_codes.h" -#include "hostfxr_resolver.h" - -namespace -{ - // SHA-256 of "dotnet-search" in UTF-8 - #define EMBED_DOTNET_SEARCH_HI_PART_UTF8 "19ff3e9c3602ae8e841925bb461a0adb" - #define EMBED_DOTNET_SEARCH_LO_PART_UTF8 "064a1f1903667a5e0d87e8f608f425ac" - - // \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_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"); - - // 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_search_location)embed[0]; - assert(embed[1] == 0); // NUL separates the search location and embedded .NET root value - if ((out_search_location & fxr_search_location_app_relative) == 0) - return true; - - // Get the embedded app-relative .NET path - std::string binding(&embed[2]); // Embedded path is null-terminated - - // Check if the path exceeds the max allowed size - constexpr int EMBED_APP_RELATIVE_DOTNET_MAX_SIZE = EMBED_SIZE - 3; // -2 for search location + null, -1 for null terminator - if (binding.size() > EMBED_APP_RELATIVE_DOTNET_MAX_SIZE) - { - trace::error(_X("The app-relative .NET path is longer than the max allowed length (%d)"), EMBED_APP_RELATIVE_DOTNET_MAX_SIZE); - return false; - } - - // Check if the value is empty or the same as the placeholder - // Since the single static string is replaced by editing the executable, a reference string is needed to do the compare. - // So use two parts of the string that will be unaffected by the edit. - static const char hi_part[] = EMBED_DOTNET_SEARCH_HI_PART_UTF8; - static const char lo_part[] = EMBED_DOTNET_SEARCH_LO_PART_UTF8; - size_t hi_len = (sizeof(hi_part) / sizeof(hi_part[0])) - 1; - size_t lo_len = (sizeof(lo_part) / sizeof(lo_part[0])) - 1; - if (binding.empty() - || (binding.size() >= (hi_len + lo_len) - && binding.compare(0, hi_len, &hi_part[0]) == 0 - && binding.compare(hi_len, lo_len, &lo_part[0]) == 0)) - { - trace::error(_X("The app-relative .NET path is not embedded.")); - return false; - } - - pal::string_t app_relative_dotnet; - if (!pal::clr_palstring(binding.c_str(), &app_relative_dotnet)) - { - trace::error(_X("The app-relative .NET path could not be retrieved from the executable image.")); - return false; - } - - trace::info(_X("Embedded app-relative .NET path: '%s'"), app_relative_dotnet.c_str()); - out_app_relative_dotnet = std::move(app_relative_dotnet); - return true; - } -} - -hostfxr_main_bundle_startupinfo_fn hostfxr_resolver_t::resolve_main_bundle_startupinfo() -{ - assert(m_hostfxr_dll != nullptr); - return reinterpret_cast(pal::get_symbol(m_hostfxr_dll, "hostfxr_main_bundle_startupinfo")); -} - -hostfxr_set_error_writer_fn hostfxr_resolver_t::resolve_set_error_writer() -{ - assert(m_hostfxr_dll != nullptr); - return reinterpret_cast(pal::get_symbol(m_hostfxr_dll, "hostfxr_set_error_writer")); -} - -hostfxr_main_startupinfo_fn hostfxr_resolver_t::resolve_main_startupinfo() -{ - assert(m_hostfxr_dll != nullptr); - return reinterpret_cast(pal::get_symbol(m_hostfxr_dll, "hostfxr_main_startupinfo")); -} - -hostfxr_main_fn hostfxr_resolver_t::resolve_main_v1() -{ - assert(m_hostfxr_dll != nullptr); - return reinterpret_cast(pal::get_symbol(m_hostfxr_dll, "hostfxr_main")); -} - -hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root) -{ - 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)) - { - m_status_code = StatusCode::AppHostExeNotBoundFailure; - return; - } - - trace::info(_X(".NET root search location options: %d"), search_location); - if (!app_relative_dotnet.empty()) - { - app_relative_dotnet_path = app_root; - append_path(&app_relative_dotnet_path, app_relative_dotnet.c_str()); - } - - bool resolved; - { - pal_char_t* dotnet_root = nullptr; - pal_char_t* fxr_path = nullptr; - 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; - } - else if (!pal::is_path_fully_qualified(m_fxr_path)) - { - // We should always be loading hostfxr from an absolute path - trace::error(_X("Path to %s must be fully qualified: [%s]"), LIBFXR_NAME, m_fxr_path.c_str()); - m_status_code = StatusCode::CoreHostLibMissingFailure; - } - else if (pal::load_library(&m_fxr_path, &m_hostfxr_dll)) - { - m_status_code = StatusCode::Success; - } - else - { - trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, m_fxr_path.c_str()); - m_status_code = StatusCode::CoreHostLibLoadFailure; - } -} - -hostfxr_resolver_t::~hostfxr_resolver_t() -{ - if (m_hostfxr_dll != nullptr) - { - pal::unload_library(m_hostfxr_dll); - } -} diff --git a/src/native/corehost/apphost/static/CMakeLists.txt b/src/native/corehost/apphost/static/CMakeLists.txt index cf936edf2cdcfe..0b1eaacd0f64e6 100644 --- a/src/native/corehost/apphost/static/CMakeLists.txt +++ b/src/native/corehost/apphost/static/CMakeLists.txt @@ -26,17 +26,16 @@ if ((NOT DEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RA endif() set(SOURCES - ../bundle_marker.cpp - ./hostfxr_resolver.cpp + ../bundle_marker.c + ./apphost_hostfxr_resolver.cpp ./hostpolicy_resolver.cpp ../../hostpolicy/static/coreclr_resolver.cpp - ../../fxr_resolver.c - ../../corehost.cpp + ../apphost.c ) set(HEADERS ../bundle_marker.h - ../../hostfxr_resolver.h + ../apphost_hostfxr_resolver.h ../../fxr_resolver.h ) @@ -62,7 +61,7 @@ endif() if(CLR_CMAKE_TARGET_WIN32) add_compile_definitions(UNICODE) list(APPEND SOURCES - ../apphost.windows.cpp + ../apphost.windows.c ) list(APPEND HEADERS @@ -147,6 +146,7 @@ if(CLR_CMAKE_TARGET_WIN32) else() if(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) LIST(APPEND NATIVE_LIBS createdump_static) + target_compile_definitions(singlefilehost PRIVATE FEATURE_STATIC_CREATEDUMP) endif() LIST(APPEND NATIVE_LIBS diff --git a/src/native/corehost/apphost/static/apphost_hostfxr_resolver.cpp b/src/native/corehost/apphost/static/apphost_hostfxr_resolver.cpp new file mode 100644 index 00000000000000..7748798c64e5bf --- /dev/null +++ b/src/native/corehost/apphost/static/apphost_hostfxr_resolver.cpp @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Static version of the hostfxr resolver for singlefilehost. +// Functions are statically linked, so no dynamic library loading is needed. +// This is a .cpp file providing extern "C" implementations of the C resolver +// interface, so it can also reference C++-linked functions like +// initialize_static_createdump and the statically-linked hostfxr functions. + +#include "pal.h" +#include "apphost_hostfxr_resolver.h" +#include "trace.h" + +#include +#include + +// Statically linked hostfxr functions +extern "C" +{ + int HOSTFXR_CALLTYPE hostfxr_main_bundle_startupinfo(const int argc, const pal_char_t* argv[], const pal_char_t* host_path, const pal_char_t* dotnet_root, const pal_char_t* app_path, int64_t bundle_header_offset); + int HOSTFXR_CALLTYPE hostfxr_main_startupinfo(const int argc, const pal_char_t* argv[], const pal_char_t* host_path, const pal_char_t* dotnet_root, const pal_char_t* app_path); + int HOSTFXR_CALLTYPE hostfxr_main(const int argc, const pal_char_t* argv[]); + hostfxr_error_writer_fn HOSTFXR_CALLTYPE hostfxr_set_error_writer(hostfxr_error_writer_fn error_writer); +} + +extern "C" hostfxr_main_bundle_startupinfo_fn hostfxr_resolver_resolve_main_bundle_startupinfo(const hostfxr_resolver_t* resolver) +{ + assert(resolver->hostfxr_dll == nullptr); + return hostfxr_main_bundle_startupinfo; +} + +extern "C" hostfxr_set_error_writer_fn hostfxr_resolver_resolve_set_error_writer(const hostfxr_resolver_t* resolver) +{ + assert(resolver->hostfxr_dll == nullptr); + return hostfxr_set_error_writer; +} + +extern "C" hostfxr_main_startupinfo_fn hostfxr_resolver_resolve_main_startupinfo(const hostfxr_resolver_t* resolver) +{ + assert(resolver->hostfxr_dll == nullptr); + return hostfxr_main_startupinfo; +} + +extern "C" hostfxr_main_fn hostfxr_resolver_resolve_main_v1(const hostfxr_resolver_t* resolver) +{ + assert(resolver->hostfxr_dll == nullptr); + assert(!"This function should not be called in a static host"); + return nullptr; +} + +extern "C" void hostfxr_resolver_init(hostfxr_resolver_t* resolver, const pal_char_t* app_root) +{ + resolver->hostfxr_dll = nullptr; + resolver->dotnet_root = nullptr; + resolver->fxr_path = nullptr; + resolver->status_code = Success; + + if (app_root == nullptr || app_root[0] == _X('\0')) + { + trace_info(_X("Application root path is empty. This shouldn't happen")); + resolver->status_code = CoreHostLibMissingFailure; + return; + } + + trace_info(_X("Using internal fxr")); + + pal_char_t* dotnet_root = pal_strdup(app_root); + pal_char_t* fxr_path = pal_strdup(app_root); + if (dotnet_root == nullptr || fxr_path == nullptr) + { + free(dotnet_root); + free(fxr_path); + resolver->status_code = CoreHostLibMissingFailure; + return; + } + + resolver->dotnet_root = dotnet_root; + resolver->fxr_path = fxr_path; +} + +extern "C" void hostfxr_resolver_cleanup(hostfxr_resolver_t* resolver) +{ + // No library to unload in a static host + free(resolver->dotnet_root); + resolver->dotnet_root = nullptr; + free(resolver->fxr_path); + resolver->fxr_path = nullptr; +} + +#if defined(FEATURE_STATIC_CREATEDUMP) +extern void initialize_static_createdump(); +#endif + +extern "C" void apphost_static_init(void) +{ +#if defined(FEATURE_STATIC_CREATEDUMP) + initialize_static_createdump(); +#endif +} diff --git a/src/native/corehost/apphost/static/hostfxr_resolver.cpp b/src/native/corehost/apphost/static/hostfxr_resolver.cpp deleted file mode 100644 index b402a65bd125eb..00000000000000 --- a/src/native/corehost/apphost/static/hostfxr_resolver.cpp +++ /dev/null @@ -1,62 +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 -#include "trace.h" -#include "hostfxr.h" -#include "hostfxr_resolver.h" - -extern "C" -{ - int HOSTFXR_CALLTYPE hostfxr_main_bundle_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path, int64_t bundle_header_offset); - int HOSTFXR_CALLTYPE hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path); - int HOSTFXR_CALLTYPE hostfxr_main(const int argc, const pal::char_t* argv[]); - hostfxr_error_writer_fn HOSTFXR_CALLTYPE hostfxr_set_error_writer(hostfxr_error_writer_fn error_writer); -} - -hostfxr_main_bundle_startupinfo_fn hostfxr_resolver_t::resolve_main_bundle_startupinfo() -{ - assert(m_hostfxr_dll == nullptr); - return hostfxr_main_bundle_startupinfo; -} - -hostfxr_set_error_writer_fn hostfxr_resolver_t::resolve_set_error_writer() -{ - assert(m_hostfxr_dll == nullptr); - return hostfxr_set_error_writer; -} - -hostfxr_main_startupinfo_fn hostfxr_resolver_t::resolve_main_startupinfo() -{ - assert(m_hostfxr_dll == nullptr); - return hostfxr_main_startupinfo; -} - -hostfxr_main_fn hostfxr_resolver_t::resolve_main_v1() -{ - assert(m_hostfxr_dll == nullptr); - assert(!"This function should not be called in a static host"); - return nullptr; -} - -hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root) -{ - if (app_root.length() == 0) - { - trace::info(_X("Application root path is empty. This shouldn't happen")); - m_status_code = StatusCode::CoreHostLibMissingFailure; - } - else - { - trace::info(_X("Using internal fxr")); - - m_dotnet_root.assign(app_root); - m_fxr_path.assign(app_root); - - m_status_code = StatusCode::Success; - } -} - -hostfxr_resolver_t::~hostfxr_resolver_t() -{ -} diff --git a/src/native/corehost/corehost.cpp b/src/native/corehost/corehost.cpp deleted file mode 100644 index 648fb86587f34d..00000000000000 --- a/src/native/corehost/corehost.cpp +++ /dev/null @@ -1,345 +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 "pal.h" -#include "hostfxr.h" -#include "fxr_resolver.h" -#include "error_codes.h" -#include "fx_ver.h" -#include "trace.h" -#include "utils.h" -#include "hostfxr_resolver.h" -#include - -#if defined(FEATURE_APPHOST) -#include "bundle_marker.h" - -#if defined(_WIN32) -#include "apphost/apphost.windows.h" -#endif - -#define CURHOST_TYPE _X("apphost") -#define CURHOST_EXE - -/** - * Detect if the apphost executable is allowed to load and execute a managed assembly. - * - * - The exe is built with a known hash string at some offset in the image - * - The exe is useless as is with the built-in hash value, and will fail with an error message - * - The hash value should be replaced with the managed DLL filename with optional relative path - * - The optional path is relative to the location of the apphost executable - * - The relative path plus filename are verified to reference a valid file - * - The filename should be "NUL terminated UTF-8" by "dotnet build" - * - The managed DLL filename does not have to be the same name as the apphost executable name - * - The exe may be signed at this point by the app publisher - * - Note: the maximum size of the filename and relative path is 1024 bytes in UTF-8 (not including NUL) - * o https://en.wikipedia.org/wiki/Comparison_of_file_systems - * has more details on maximum file name sizes. - */ -#define EMBED_HASH_HI_PART_UTF8 "c3ab8ff13720e8ad9047dd39466b3c89" // SHA-256 of "foobar" in UTF-8 -#define EMBED_HASH_LO_PART_UTF8 "74e592c2fa383d4a3960714caef0c4f2" -#define EMBED_HASH_FULL_UTF8 (EMBED_HASH_HI_PART_UTF8 EMBED_HASH_LO_PART_UTF8) // NUL terminated - -// This avoids compiler optimization which cause EMBED_HASH_HI_PART_UTF8 EMBED_HASH_LO_PART_UTF8 -// to be placed adjacent causing them to match EMBED_HASH_FULL_UTF8 when searched for replacing. -// See https://github.com/dotnet/runtime/issues/109611 for more details. -static bool compare_memory_nooptimization(volatile const char* a, volatile const char* b, size_t length) -{ - for (size_t i = 0; i < length; i++) - { - if (*a++ != *b++) - return false; - } - return true; -} - -bool is_exe_enabled_for_execution(pal::string_t* app_dll) -{ - constexpr int EMBED_SZ = sizeof(EMBED_HASH_FULL_UTF8) / sizeof(EMBED_HASH_FULL_UTF8[0]); - constexpr int EMBED_MAX = (EMBED_SZ > 1025 ? EMBED_SZ : 1025); // 1024 DLL name length, 1 NUL - - // Contains the EMBED_HASH_FULL_UTF8 value at compile time or the managed DLL name replaced by "dotnet build". - // Must not be 'const' because strlen below could be determined at compile time (=64) instead of the actual - // length of the string at runtime. - static char embed[EMBED_MAX] = EMBED_HASH_FULL_UTF8; // series of NULs followed by embed hash string - - static const char hi_part[] = EMBED_HASH_HI_PART_UTF8; - static const char lo_part[] = EMBED_HASH_LO_PART_UTF8; - - if (!pal::clr_palstring(embed, app_dll)) - { - trace::error(_X("The managed DLL bound to this executable could not be retrieved from the executable image.")); - return false; - } - - size_t binding_len = strlen(&embed[0]); - - // Check if the path exceeds the max allowed size - if (binding_len > EMBED_MAX - 1) // -1 for null terminator - { - trace::error(_X("The managed DLL bound to this executable is longer than the max allowed length (%d)"), EMBED_MAX - 1); - return false; - } - - // Check if the value is the same as the placeholder - // Since the single static string is replaced by editing the executable, a reference string is needed to do the compare. - // So use two parts of the string that will be unaffected by the edit. - size_t hi_len = (sizeof(hi_part) / sizeof(hi_part[0])) - 1; - size_t lo_len = (sizeof(lo_part) / sizeof(lo_part[0])) - 1; - if (binding_len >= (hi_len + lo_len) - && compare_memory_nooptimization(&embed[0], hi_part, hi_len) - && compare_memory_nooptimization(&embed[hi_len], lo_part, lo_len)) - { - trace::error(_X("This executable is not bound to a managed DLL to execute. The binding value is: '%s'"), app_dll->c_str()); - return false; - } - - trace::info(_X("The managed DLL bound to this executable is: '%s'"), app_dll->c_str()); - return true; -} - -#elif !defined(FEATURE_LIBHOST) -#define CURHOST_TYPE _X("dotnet") -#define CURHOST_EXE -#endif - -void need_newer_framework_error(const pal::string_t& dotnet_root, const pal::string_t& host_path) -{ - trace::error( - MISSING_RUNTIME_ERROR_FORMAT, - INSTALL_OR_UPDATE_NET_ERROR_MESSAGE, - host_path.c_str(), - get_current_arch_name(), - _STRINGIFY(HOST_VERSION), - dotnet_root.c_str(), - get_download_url().c_str(), - _STRINGIFY(HOST_VERSION)); -} - -#if defined(CURHOST_EXE) - -int exe_start(const int argc, const pal::char_t* argv[]) -{ -#if defined(FEATURE_STATIC_HOST) && (defined(TARGET_OSX) || (defined(TARGET_LINUX) && !defined(TARGET_ANDROID))) && !defined(TARGET_X86) - extern void initialize_static_createdump(); - initialize_static_createdump(); -#endif - - // Use realpath to find the path of the host, resolving any symlinks. - // hostfxr (for dotnet) and the app dll (for apphost) are found relative to the host. - pal::string_t host_path; - if (!pal::get_own_executable_path(&host_path) || !pal::fullpath(&host_path)) - { - trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str()); - return StatusCode::CurrentHostFindFailure; - } - - pal::string_t app_path; - pal::string_t app_root; - bool requires_hostfxr_startupinfo_interface = false; - -#if defined(FEATURE_APPHOST) - pal::string_t embedded_app_name; - if (!is_exe_enabled_for_execution(&embedded_app_name)) - { - return StatusCode::AppHostExeNotBoundFailure; - } - - if (_X('/') != DIR_SEPARATOR) - { - replace_char(&embedded_app_name, _X('/'), DIR_SEPARATOR); - } - - auto pos_path_char = embedded_app_name.find(DIR_SEPARATOR); - if (pos_path_char != pal::string_t::npos) - { - requires_hostfxr_startupinfo_interface = true; - } - - app_path.assign(get_directory(host_path)); - append_path(&app_path, embedded_app_name.c_str()); - - if (bundle_marker_t::is_bundle()) - { - trace::info(_X("Detected Single-File app bundle")); - } - else if (!pal::fullpath(&app_path)) - { - trace::error(_X("The application to execute does not exist: '%s'."), app_path.c_str()); - return StatusCode::AppPathFindFailure; - } - - app_root.assign(get_directory(app_path)); - -#else - pal::string_t own_name = strip_executable_ext(get_filename(host_path)); - - if (pal::strcasecmp(own_name.c_str(), CURHOST_TYPE) != 0) - { - // The reason for this check is security. - // dotnet.exe is signed by Microsoft. It is technically possible to rename the file MyApp.exe and include it in the application. - // Then one can create a shortcut for "MyApp.exe MyApp.dll" which works. The end result is that MyApp looks like it's signed by Microsoft. - // To prevent this dotnet.exe must not be renamed, otherwise it won't run. - trace::error(_X("Error: cannot execute %s when renamed to %s."), CURHOST_TYPE, own_name.c_str()); - return StatusCode::CoreHostEntryPointFailure; - } - - if (argc <= 1) - { - trace::println(); - trace::println(_X("Usage: dotnet [path-to-application]")); - trace::println(_X("Usage: dotnet [commands]")); - trace::println(); - trace::println(_X("path-to-application:")); - trace::println(_X(" The path to an application .dll file to execute.")); - trace::println(); - trace::println(_X("commands:")); - trace::println(_X(" -h|--help Display help.")); - trace::println(_X(" --info Display .NET information.")); - trace::println(_X(" --list-runtimes [--arch ] Display the installed runtimes matching the host or specified architecture. Example architectures: arm64, x64, x86.")); - trace::println(_X(" --list-sdks [--arch ] Display the installed SDKs matching the host or specified architecture. Example architectures: arm64, x64, x86.")); - return StatusCode::InvalidArgFailure; - } - - app_root.assign(host_path); - app_path.assign(get_directory(app_root)); - append_path(&app_path, own_name.c_str()); - app_path.append(_X(".dll")); -#endif - - hostfxr_resolver_t fxr{app_root}; - - // Obtain the entrypoints. - int rc = fxr.status_code(); - if (rc != StatusCode::Success) - return rc; - -#if defined(FEATURE_APPHOST) - if (bundle_marker_t::is_bundle()) - { - auto hostfxr_main_bundle_startupinfo = fxr.resolve_main_bundle_startupinfo(); - if (hostfxr_main_bundle_startupinfo != nullptr) - { - const pal::char_t* host_path_cstr = host_path.c_str(); - const pal::char_t* dotnet_root_cstr = fxr.dotnet_root().empty() ? nullptr : fxr.dotnet_root().c_str(); - const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str(); - int64_t bundle_header_offset = bundle_marker_t::header_offset(); - - trace::info(_X("Invoking fx resolver [%s] hostfxr_main_bundle_startupinfo"), fxr.fxr_path().c_str()); - trace::info(_X("Host path: [%s]"), host_path.c_str()); - trace::info(_X("Dotnet path: [%s]"), fxr.dotnet_root().c_str()); - trace::info(_X("App path: [%s]"), app_path.c_str()); - trace::info(_X("Bundle Header Offset: [%" PRId64 "]"), bundle_header_offset); - - auto set_error_writer = fxr.resolve_set_error_writer(); - propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer); - rc = hostfxr_main_bundle_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr, bundle_header_offset); - } - else - { - // An outdated hostfxr can only be found for framework-related apps. - trace::error(_X("The required library %s does not support single-file apps."), fxr.fxr_path().c_str()); - need_newer_framework_error(fxr.dotnet_root(), host_path); - rc = StatusCode::FrameworkMissingFailure; - } - } - else -#endif // defined(FEATURE_APPHOST) - { - auto hostfxr_main_startupinfo = fxr.resolve_main_startupinfo(); - if (hostfxr_main_startupinfo != nullptr) - { - const pal::char_t* host_path_cstr = host_path.c_str(); - const pal::char_t* dotnet_root_cstr = fxr.dotnet_root().empty() ? nullptr : fxr.dotnet_root().c_str(); - const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str(); - - trace::info(_X("Invoking fx resolver [%s] hostfxr_main_startupinfo"), fxr.fxr_path().c_str()); - trace::info(_X("Host path: [%s]"), host_path.c_str()); - trace::info(_X("Dotnet path: [%s]"), fxr.dotnet_root().c_str()); - trace::info(_X("App path: [%s]"), app_path.c_str()); - - auto set_error_writer = fxr.resolve_set_error_writer(); - propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer); - - rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr); - - // This check exists to provide an error message for apps when running 3.0 apps on 2.0 only hostfxr, which doesn't support error writer redirection. - // Note that this is not only for UI apps - on Windows we always write errors to event log as well (regardless of UI) and it uses - // the same mechanism of redirecting error writers. - if (trace::get_error_writer() != nullptr && rc == static_cast(StatusCode::FrameworkMissingFailure) && set_error_writer == nullptr) - { - need_newer_framework_error(fxr.dotnet_root(), host_path); - } - } -#if !defined(FEATURE_STATIC_HOST) - else - { - if (requires_hostfxr_startupinfo_interface) - { - trace::error(_X("The required library %s does not support relative app dll paths."), fxr.fxr_path().c_str()); - rc = StatusCode::CoreHostEntryPointFailure; - } - else - { - trace::info(_X("Invoking fx resolver [%s] v1"), fxr.fxr_path().c_str()); - - // Previous corehost trace messages must be printed before calling trace::setup in hostfxr - trace::flush(); - - // For compat, use the v1 interface. This requires additional file I\O to re-parse parameters and - // for apphost, does not support DOTNET_ROOT or dll with different name for exe. - auto main_fn_v1 = fxr.resolve_main_v1(); - if (main_fn_v1 != nullptr) - { - rc = main_fn_v1(argc, argv); - } - else - { - trace::error(_X("The required library %s does not contain the expected entry point."), fxr.fxr_path().c_str()); - rc = StatusCode::CoreHostEntryPointFailure; - } - } - } -#endif // defined(FEATURE_STATIC_HOST) - } - - return rc; -} - -#if defined(_WIN32) -int __cdecl wmain(const int argc, const pal::char_t* argv[]) -#else -int main(const int argc, const pal::char_t* argv[]) -#endif -{ - trace::setup(); - - if (trace::is_enabled()) - { - trace::info(_X("--- Invoked %s [version: %s] main = {"), CURHOST_TYPE, get_host_version_description().c_str()); - for (int i = 0; i < argc; ++i) - { - trace::info(_X("%s"), argv[i]); - } - trace::info(_X("}")); - } - -#if defined(_WIN32) && defined(FEATURE_APPHOST) - // Buffer errors to use them later. - apphost::buffer_errors(); -#endif - - int exit_code = exe_start(argc, argv); - - // Flush traces before exit - just to be sure - trace::flush(); - -#if defined(_WIN32) && defined(FEATURE_APPHOST) - // No need to unregister the error writer since we're exiting anyway. - apphost::write_buffered_errors(exit_code); -#endif - - return exit_code; -} - -#endif diff --git a/src/native/corehost/dotnet/CMakeLists.txt b/src/native/corehost/dotnet/CMakeLists.txt index 5704c911ac9568..4ebbd62bf628b9 100644 --- a/src/native/corehost/dotnet/CMakeLists.txt +++ b/src/native/corehost/dotnet/CMakeLists.txt @@ -8,8 +8,8 @@ if(CLR_CMAKE_TARGET_WIN32) endif() list(APPEND SOURCES - ../apphost/standalone/hostfxr_resolver.cpp - ../corehost.cpp + hostfxr_resolver.cpp + dotnet.cpp ) add_executable(dotnet ${SOURCES}) diff --git a/src/native/corehost/dotnet/dotnet.cpp b/src/native/corehost/dotnet/dotnet.cpp new file mode 100644 index 00000000000000..674369b0903534 --- /dev/null +++ b/src/native/corehost/dotnet/dotnet.cpp @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal.h" +#include "hostfxr.h" +#include "fxr_resolver.h" +#include "error_codes.h" +#include "fx_ver.h" +#include "trace.h" +#include "utils.h" +#include "hostfxr_resolver.h" + +int exe_start(const int argc, const pal::char_t* argv[]) +{ + // Use realpath to find the path of the host, resolving any symlinks. + // hostfxr (for dotnet) and the app dll (for apphost) are found relative to the host. + pal::string_t host_path; + if (!pal::get_own_executable_path(&host_path) || !pal::fullpath(&host_path)) + { + trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str()); + return StatusCode::CurrentHostFindFailure; + } + + pal::string_t app_path; + pal::string_t app_root; + pal::string_t own_name = strip_executable_ext(get_filename(host_path)); + + if (pal::strcasecmp(own_name.c_str(), _X("dotnet")) != 0) + { + // The reason for this check is security. + // dotnet.exe is signed by Microsoft. It is technically possible to rename the file MyApp.exe and include it in the application. + // Then one can create a shortcut for "MyApp.exe MyApp.dll" which works. The end result is that MyApp looks like it's signed by Microsoft. + // To prevent this dotnet.exe must not be renamed, otherwise it won't run. + trace::error(_X("Error: cannot execute %s when renamed to %s."), _X("dotnet"), own_name.c_str()); + return StatusCode::CoreHostEntryPointFailure; + } + + if (argc <= 1) + { + trace::println(); + trace::println(_X("Usage: dotnet [path-to-application]")); + trace::println(_X("Usage: dotnet [commands]")); + trace::println(); + trace::println(_X("path-to-application:")); + trace::println(_X(" The path to an application .dll file to execute.")); + trace::println(); + trace::println(_X("commands:")); + trace::println(_X(" -h|--help Display help.")); + trace::println(_X(" --info Display .NET information.")); + trace::println(_X(" --list-runtimes [--arch ] Display the installed runtimes matching the host or specified architecture. Example architectures: arm64, x64, x86.")); + trace::println(_X(" --list-sdks [--arch ] Display the installed SDKs matching the host or specified architecture. Example architectures: arm64, x64, x86.")); + return StatusCode::InvalidArgFailure; + } + + app_root.assign(host_path); + app_path.assign(get_directory(app_root)); + append_path(&app_path, own_name.c_str()); + app_path.append(_X(".dll")); + + hostfxr_resolver_t fxr{app_root}; + + // Obtain the entrypoints. + int rc = fxr.status_code(); + if (rc != StatusCode::Success) + return rc; + + auto hostfxr_main_startupinfo = fxr.resolve_main_startupinfo(); + if (hostfxr_main_startupinfo != nullptr) + { + const pal::char_t* host_path_cstr = host_path.c_str(); + const pal::char_t* dotnet_root_cstr = fxr.dotnet_root().empty() ? nullptr : fxr.dotnet_root().c_str(); + const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str(); + + trace::info(_X("Invoking fx resolver [%s] hostfxr_main_startupinfo"), fxr.fxr_path().c_str()); + trace::info(_X("Host path: [%s]"), host_path.c_str()); + trace::info(_X("Dotnet path: [%s]"), fxr.dotnet_root().c_str()); + trace::info(_X("App path: [%s]"), app_path.c_str()); + + auto set_error_writer = fxr.resolve_set_error_writer(); + propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer); + + rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr); + + // This check exists to provide an error message for apps when running 3.0 apps on 2.0 only hostfxr, which doesn't support error writer redirection. + // Note that this is not only for UI apps - on Windows we always write errors to event log as well (regardless of UI) and it uses + // the same mechanism of redirecting error writers. + if (trace::get_error_writer() != nullptr && rc == static_cast(StatusCode::FrameworkMissingFailure) && set_error_writer == nullptr) + { + trace::error( + MISSING_RUNTIME_ERROR_FORMAT, + INSTALL_OR_UPDATE_NET_ERROR_MESSAGE, + host_path.c_str(), + get_current_arch_name(), + _STRINGIFY(HOST_VERSION), + fxr.dotnet_root().c_str(), + get_download_url().c_str(), + _STRINGIFY(HOST_VERSION)); + } + } + else + { + trace::info(_X("Invoking fx resolver [%s] v1"), fxr.fxr_path().c_str()); + + // Previous corehost trace messages must be printed before calling trace::setup in hostfxr + trace::flush(); + + // For compat, use the v1 interface. This requires additional file I\O to re-parse parameters and + // for apphost, does not support DOTNET_ROOT or dll with different name for exe. + auto main_fn_v1 = fxr.resolve_main_v1(); + if (main_fn_v1 != nullptr) + { + rc = main_fn_v1(argc, argv); + } + else + { + trace::error(_X("The required library %s does not contain the expected entry point."), fxr.fxr_path().c_str()); + rc = StatusCode::CoreHostEntryPointFailure; + } + } + + return rc; +} + +#if defined(_WIN32) +int __cdecl wmain(const int argc, const pal::char_t* argv[]) +#else +int main(const int argc, const pal::char_t* argv[]) +#endif +{ + trace::setup(); + + if (trace::is_enabled()) + { + trace::info(_X("--- Invoked %s [version: %s] main = {"), _X("dotnet"), get_host_version_description().c_str()); + for (int i = 0; i < argc; ++i) + { + trace::info(_X("%s"), argv[i]); + } + trace::info(_X("}")); + } + + int exit_code = exe_start(argc, argv); + + // Flush traces before exit - just to be sure + trace::flush(); + + return exit_code; +} diff --git a/src/native/corehost/dotnet/hostfxr_resolver.cpp b/src/native/corehost/dotnet/hostfxr_resolver.cpp new file mode 100644 index 00000000000000..73a8351a48940f --- /dev/null +++ b/src/native/corehost/dotnet/hostfxr_resolver.cpp @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include + +#include "pal.h" +#include "fxr_resolver.h" +#include "trace.h" +#include "utils.h" +#include "error_codes.h" +#include "hostfxr_resolver.h" + +hostfxr_main_bundle_startupinfo_fn hostfxr_resolver_t::resolve_main_bundle_startupinfo() +{ + assert(m_hostfxr_dll != nullptr); + return reinterpret_cast(pal::get_symbol(m_hostfxr_dll, "hostfxr_main_bundle_startupinfo")); +} + +hostfxr_set_error_writer_fn hostfxr_resolver_t::resolve_set_error_writer() +{ + assert(m_hostfxr_dll != nullptr); + return reinterpret_cast(pal::get_symbol(m_hostfxr_dll, "hostfxr_set_error_writer")); +} + +hostfxr_main_startupinfo_fn hostfxr_resolver_t::resolve_main_startupinfo() +{ + assert(m_hostfxr_dll != nullptr); + return reinterpret_cast(pal::get_symbol(m_hostfxr_dll, "hostfxr_main_startupinfo")); +} + +hostfxr_main_fn hostfxr_resolver_t::resolve_main_v1() +{ + assert(m_hostfxr_dll != nullptr); + return reinterpret_cast(pal::get_symbol(m_hostfxr_dll, "hostfxr_main")); +} + +hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root) +{ + bool resolved; + { + pal_char_t* dotnet_root = nullptr; + pal_char_t* fxr_path = nullptr; + resolved = fxr_resolver_try_get_path(app_root.c_str(), fxr_search_location_default, nullptr, &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; + } + else if (!pal::is_path_fully_qualified(m_fxr_path)) + { + // We should always be loading hostfxr from an absolute path + trace::error(_X("Path to %s must be fully qualified: [%s]"), LIBFXR_NAME, m_fxr_path.c_str()); + m_status_code = StatusCode::CoreHostLibMissingFailure; + } + else if (pal::load_library(&m_fxr_path, &m_hostfxr_dll)) + { + m_status_code = StatusCode::Success; + } + else + { + trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, m_fxr_path.c_str()); + m_status_code = StatusCode::CoreHostLibLoadFailure; + } +} + +hostfxr_resolver_t::~hostfxr_resolver_t() +{ + if (m_hostfxr_dll != nullptr) + { + pal::unload_library(m_hostfxr_dll); + } +} diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 056a1ec9876a84..99101de8803686 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -52,17 +52,20 @@ typedef char pal_char_t; #include #include +typedef HMODULE pal_dll_t; +typedef FARPROC pal_proc_t; + #define DIR_SEPARATOR L'\\' #define DIR_SEPARATOR_STR L"\\" #define PATH_SEPARATOR L';' #define PATH_MAX MAX_PATH -// String operation macros (pal_char_t-based). Equivalent to the corresponding -// pal:: namespace inline functions, but usable from C source files. +// String operation macros (pal_char_t-based). #define pal_strlen(s) wcslen(s) #define pal_strchr(s, c) wcschr(s, c) #define pal_strrchr(s, c) wcsrchr(s, c) #define pal_strncmp(a, b, n) wcsncmp(a, b, n) +#define pal_strncasecmp(a, b, n) _wcsnicmp(a, b, n) #define pal_strtoul(s, e, b) wcstoul(s, e, b) #define pal_str_vprintf(buf, count, fmt, args) _vsnwprintf_s(buf, count, _TRUNCATE, fmt, args) #define pal_strlen_vprintf(fmt, args) _vscwprintf(fmt, args) @@ -76,6 +79,10 @@ typedef char pal_char_t; #include #include #include +#include // strncasecmp + +typedef void* pal_dll_t; +typedef void* pal_proc_t; #define DIR_SEPARATOR '/' #define DIR_SEPARATOR_STR "/" @@ -96,6 +103,7 @@ typedef char pal_char_t; #define pal_strchr(s, c) strchr(s, c) #define pal_strrchr(s, c) strrchr(s, c) #define pal_strncmp(a, b, n) strncmp(a, b, n) +#define pal_strncasecmp(a, b, n) strncasecmp(a, b, n) #define pal_strtoul(s, e, b) strtoul(s, e, b) #define pal_str_vprintf(buf, count, fmt, args) vsnprintf(buf, (size_t)(count), fmt, args) #define pal_strlen_vprintf(fmt, args) vsnprintf(NULL, 0, fmt, args) @@ -129,7 +137,7 @@ pal_char_t* pal_get_own_executable_path(void); bool pal_directory_exists(const pal_char_t* path); -// Returns true if the file or directory exists. Equivalent to pal::file_exists. +// Returns true if the file or directory exists. bool pal_file_exists(const pal_char_t* path); // Returns a heap-allocated, null-terminated copy of the given string, or @@ -203,12 +211,30 @@ 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 +// Returns true if path is fully qualified (absolute). +bool pal_is_path_fully_qualified(const pal_char_t* path); + +// Load the dynamic library at path. On success, stores the library handle in +// *dll and returns true; returns false on failure. +bool pal_load_library(const pal_char_t* path, pal_dll_t* dll); + +// Unload a library previously loaded with pal_load_library. +void pal_unload_library(pal_dll_t library); + +// Resolve an exported symbol from a loaded library, or NULL if not found. +pal_proc_t pal_get_symbol(pal_dll_t library, const char* name); + +// Convert a UTF-8 string into the platform character type +bool pal_utf8_to_palstr(const char* utf8, pal_char_t* out, size_t out_len); + +// Write message followed by a newline to stderr. +void pal_err_print_line(const pal_char_t* message); + +// Format and write to stdout followed by a newline. +void pal_out_vprint_line(const pal_char_t* format, va_list vl); + +// Format and write to the file f followed by a newline. +void pal_file_vprintf(FILE* f, const pal_char_t* format, va_list vl); // 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 @@ -318,7 +344,6 @@ namespace pal typedef std::wstringstream stringstream_t; typedef HRESULT hresult_t; typedef HMODULE dll_t; - typedef FARPROC proc_t; // Lockable object backed by CRITICAL_SECTION such that it does not pull in ConcRT. class mutex_t @@ -351,10 +376,6 @@ namespace pal inline FILE* file_open(const string_t& path, const char_t* mode) { return ::_wfsopen(path.c_str(), mode, _SH_DENYNO); } - void file_vprintf(FILE* f, const char_t* format, va_list vl); - void err_print_line(const char_t* message); - void out_vprint_line(const char_t* format, va_list vl); - inline int str_vprintf(char_t* buffer, size_t count, const char_t* format, va_list vl) { return ::_vsnwprintf_s(buffer, count, _TRUNCATE, format, vl); } inline int strlen_vprintf(const char_t* format, va_list vl) { return ::_vscwprintf(format, vl); } @@ -409,7 +430,6 @@ namespace pal typedef std::stringstream stringstream_t; typedef int hresult_t; typedef void* dll_t; - typedef void* proc_t; typedef std::mutex mutex_t; inline const pal::char_t* exe_suffix() { return nullptr; } @@ -424,9 +444,6 @@ namespace pal inline size_t strlen(const char_t* str) { return ::strlen(str); } inline FILE* file_open(const string_t& path, const char_t* mode) { return fopen(path.c_str(), mode); } - inline void file_vprintf(FILE* f, const char_t* format, va_list vl) { ::vfprintf(f, format, vl); ::fputc('\n', f); } - inline void err_print_line(const char_t* message) { ::fputs(message, stderr); ::fputc(_X('\n'), stderr); } - inline void out_vprint_line(const char_t* format, va_list vl) { ::vfprintf(stdout, format, vl); ::fputc('\n', stdout); } inline int str_vprintf(char_t* str, size_t size, const char_t* format, va_list vl) { return ::vsnprintf(str, size, format, vl); } inline int strlen_vprintf(const char_t* format, va_list vl) { return ::vsnprintf(nullptr, 0, format, vl); } @@ -560,7 +577,7 @@ namespace pal bool get_loaded_library(const char_t* library_name, const char* symbol_name, /*out*/ dll_t* dll, /*out*/ string_t* path); bool load_library(const string_t* path, dll_t* dll); - proc_t get_symbol(dll_t library, const char* name); + pal_proc_t get_symbol(dll_t library, const char* name); void unload_library(dll_t library); bool is_running_in_wow64(); diff --git a/src/native/corehost/hostmisc/pal.unix.c b/src/native/corehost/hostmisc/pal.unix.c index bb86b768d11e1c..73aebb83de2286 100644 --- a/src/native/corehost/hostmisc/pal.unix.c +++ b/src/native/corehost/hostmisc/pal.unix.c @@ -309,9 +309,52 @@ pal_char_t* pal_get_default_installation_dir(void) #endif } -static bool is_path_fully_qualified(const pal_char_t* path) +bool pal_is_path_fully_qualified(const pal_char_t* path) { - return path[0] == DIR_SEPARATOR; + return path != NULL && path[0] == DIR_SEPARATOR; +} + +bool pal_load_library(const pal_char_t* path, pal_dll_t* dll) +{ + *dll = dlopen(path, RTLD_LAZY); + if (*dll == NULL) + { + trace_error(_X("Failed to load %s, error: %s"), path, dlerror()); + return false; + } + return true; +} + +void pal_unload_library(pal_dll_t library) +{ + if (dlclose(library) != 0) + { + trace_warning(_X("Failed to unload library, error: %s"), dlerror()); + } +} + +pal_proc_t pal_get_symbol(pal_dll_t library, const char* name) +{ + void* result = dlsym(library, name); + if (result == NULL) + { + trace_info(_X("Probed for and did not find library symbol %s, error: %s"), name, dlerror()); + } + return result; +} + +bool pal_utf8_to_palstr(const char* utf8, pal_char_t* out, size_t out_len) +{ + // On Unix pal_char_t is char and the input is already UTF-8, so this is a + // length-checked copy rather than an encoding conversion. + size_t required = strlen(utf8) + 1; + if (required > out_len) + { + return false; + } + + memcpy(out, utf8, required); + return true; } // Two-level stringize so PATH_MAX's value (not its name) can be used as an @@ -384,7 +427,7 @@ bool pal_get_loaded_library( 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)) + if (!pal_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)); @@ -403,7 +446,7 @@ bool pal_get_loaded_library( if (dll_maybe == NULL) { - if (is_path_fully_qualified(library_name)) + if (pal_is_path_fully_qualified(library_name)) return false; return get_loaded_library_from_proc_maps(library_name, dll, out_path); @@ -437,3 +480,21 @@ bool pal_get_loaded_library( *out_path = path_copy; return true; } + +void pal_err_print_line(const pal_char_t* message) +{ + fputs(message, stderr); + fputc('\n', stderr); +} + +void pal_file_vprintf(FILE* f, const pal_char_t* format, va_list vl) +{ + vfprintf(f, format, vl); + fputc('\n', f); +} + +void pal_out_vprint_line(const pal_char_t* format, va_list vl) +{ + vfprintf(stdout, format, vl); + fputc('\n', stdout); +} diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index 9274cd1bba65cf..bebab75a10be59 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -155,32 +155,17 @@ bool pal::get_loaded_library( bool pal::load_library(const string_t* path, dll_t* dll) { - *dll = dlopen(path->c_str(), RTLD_LAZY); - if (*dll == nullptr) - { - trace::error(_X("Failed to load %s, error: %s"), path->c_str(), dlerror()); - return false; - } - return true; + return pal_load_library(path->c_str(), dll); } -pal::proc_t pal::get_symbol(dll_t library, const char* name) +pal_proc_t pal::get_symbol(dll_t library, const char* name) { - auto result = dlsym(library, name); - if (result == nullptr) - { - trace::info(_X("Probed for and did not find library symbol %s, error: %s"), name, dlerror()); - } - - return result; + return pal_get_symbol(library, name); } void pal::unload_library(dll_t library) { - if (dlclose(library) != 0) - { - trace::warning(_X("Failed to unload library, error: %s"), dlerror()); - } + pal_unload_library(library); } int pal::xtoi(const char_t* input) @@ -195,7 +180,7 @@ bool pal::is_path_rooted(const pal::string_t& path) bool pal::is_path_fully_qualified(const pal::string_t& path) { - return is_path_rooted(path); + return pal_is_path_fully_qualified(path.c_str()); } bool pal::get_default_breadcrumb_store(string_t* recv) diff --git a/src/native/corehost/hostmisc/pal.windows.c b/src/native/corehost/hostmisc/pal.windows.c index d83aea5b14f47c..1b662fdc2fcd17 100644 --- a/src/native/corehost/hostmisc/pal.windows.c +++ b/src/native/corehost/hostmisc/pal.windows.c @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -205,30 +206,6 @@ static bool is_dir_separator(pal_char_t c) return c == DIR_SEPARATOR || c == ALT_DIR_SEPARATOR; } -// Returns true if the path is relative to the current drive or working -// directory (i.e. not rooted at a specific drive or UNC share), and therefore -// must be canonicalized before it can be reliably used. -static bool is_path_not_fully_qualified(const pal_char_t* path) -{ - size_t len = pal_strlen(path); - - // Too short to encode a drive ("X:") or UNC ("\\") root. - if (len < 2) - return true; - - // Starts with a separator: fully qualified only if it's a UNC path, - // i.e. the second character is also a separator ("\\server\share"). - if (is_dir_separator(path[0])) - return !is_dir_separator(path[1]); - - // Otherwise it must be a drive-rooted path of the form "X:\": at least - // three characters, a volume separator at index 1, and a directory - // separator at index 2. - return len < 3 - || path[1] != VOLUME_SEPARATOR - || !is_dir_separator(path[2]); -} - // Returns true if the path needs normalization (canonicalization, and the \\?\ // prefix for long paths): it isn't already normalized and is either not fully // qualified or at least MAX_PATH characters long. @@ -237,7 +214,7 @@ static bool should_normalize_path(const pal_char_t* path) if (is_path_normalized(path)) return false; - if (!is_path_not_fully_qualified(path) && pal_strlen(path) < MAX_PATH) + if (pal_is_path_fully_qualified(path) && pal_strlen(path) < MAX_PATH) return false; return true; @@ -518,6 +495,121 @@ pal_char_t* pal_get_default_installation_dir(void) return result; } +bool pal_is_path_fully_qualified(const pal_char_t* path) +{ + if (path == NULL) + return false; + + size_t len = pal_strlen(path); + if (len < 2) + return false; + + // UNC and DOS device paths (e.g. \\server\share or \\?\C:\). + if (is_dir_separator(path[0])) + return path[1] == _X('?') || is_dir_separator(path[1]); + + // Drive absolute path (e.g. C:\). + return len >= 3 && path[1] == VOLUME_SEPARATOR && is_dir_separator(path[2]); +} + +bool pal_load_library(const pal_char_t* path, pal_dll_t* dll) +{ + *dll = NULL; + + pal_char_t* full = NULL; + const pal_char_t* load_path = path; + + // LoadLibraryEx with the search flags below requires a fully-qualified path. + if (!pal_is_path_fully_qualified(path)) + { + full = pal_fullpath(path, false); + if (full == NULL) + { + trace_error(_X("Failed to load [%s], HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError())); + return false; + } + load_path = full; + } + + HMODULE library = LoadLibraryExW(load_path, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + if (library == NULL) + { + DWORD error_code = GetLastError(); + trace_error(_X("Failed to load [%s], HRESULT: 0x%X"), load_path, HRESULT_FROM_WIN32(error_code)); + if (error_code == ERROR_BAD_EXE_FORMAT) + { + trace_error(_X(" - Ensure the library matches the current process architecture: ") _STRINGIFY(CURRENT_ARCH_NAME)); + } + free(full); + return false; + } + + // Pin the module so it is never unloaded (pal_unload_library is a no-op on Windows). + HMODULE pinned; + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, load_path, &pinned)) + { + trace_error(_X("Failed to pin library [%s] in [pal_load_library]"), load_path); + FreeLibrary(library); + free(full); + return false; + } + + if (trace_is_enabled()) + { + DWORD name_size = MAX_PATH / 2; + pal_char_t* name = NULL; + DWORD name_written = 0; + do + { + name_size *= 2; + pal_char_t* new_name = (pal_char_t*)realloc(name, name_size * sizeof(pal_char_t)); + if (new_name == NULL) + { + free(name); + name = NULL; + break; + } + name = new_name; + name_written = GetModuleFileNameW(library, name, name_size); + } while (name_written == name_size); + + if (name != NULL && name_written != 0) + trace_info(_X("Loaded library from %s"), name); + + free(name); + } + + *dll = library; + free(full); + return true; +} + +void pal_unload_library(pal_dll_t library) +{ + // No-op. On Windows the host pins loaded libraries so they are not unloaded. + (void)library; +} + +pal_proc_t pal_get_symbol(pal_dll_t library, const char* name) +{ + FARPROC proc = GetProcAddress(library, name); + if (proc == NULL) + { + trace_info(_X("Probed for and did not resolve library symbol %S"), name); + return NULL; + } + return proc; +} + +bool pal_utf8_to_palstr(const char* utf8, pal_char_t* out, size_t out_len) +{ + int required = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); + if (required <= 0 || (size_t)required > out_len) + return false; + + return MultiByteToWideChar(CP_UTF8, 0, utf8, -1, out, (int)out_len) > 0; +} + bool pal_get_loaded_library( const pal_char_t* library_name, const char* symbol_name, @@ -540,3 +632,56 @@ bool pal_get_loaded_library( *out_path = path; return true; } + +static void print_line_to_handle(const pal_char_t* message, HANDLE handle, FILE* fallback_file) +{ + // String functions like fwprintf convert wide to multi-byte characters as if wcrtomb were called - that is, using the current C locale (LC_TYPE). + // In order to properly print UTF-8 and GB18030 characters to the console without requiring the user to use chcp to a compatible locale, we use WriteConsoleW. + // However, WriteConsoleW will fail if the output is redirected to a file - in that case we write to the fallback file using a UTF-8 locale. + DWORD mode; + // GetConsoleMode returns FALSE when the output is redirected to a file. + if (GetConsoleMode(handle, &mode) == FALSE) + { + _locale_t loc = _create_locale(LC_ALL, ".utf8"); + _fwprintf_l(fallback_file, _X("%s\n"), loc, message); + _free_locale(loc); + } + else + { + WriteConsoleW(handle, message, (DWORD)wcslen(message), NULL, NULL); + WriteConsoleW(handle, _X("\n"), 1, NULL, NULL); + } +} + +void pal_err_print_line(const pal_char_t* message) +{ + print_line_to_handle(message, GetStdHandle(STD_ERROR_HANDLE), stderr); +} + +void pal_file_vprintf(FILE* f, const pal_char_t* format, va_list vl) +{ + // String functions like vfwprintf convert wide to multi-byte characters as if wcrtomb were called - that is, using the current C locale (LC_TYPE). + // In order to properly print UTF-8 and GB18030 characters, we need to use the version of vfwprintf that takes a locale. + _locale_t loc = _create_locale(LC_ALL, ".utf8"); + _vfwprintf_l(f, format, loc, vl); + fputwc(_X('\n'), f); + _free_locale(loc); +} + +void pal_out_vprint_line(const pal_char_t* format, va_list vl) +{ + va_list vl_copy; + va_copy(vl_copy, vl); + int len = 1 + pal_strlen_vprintf(format, vl_copy); + va_end(vl_copy); + if (len <= 0) + return; + + pal_char_t* buffer = (pal_char_t*)malloc((size_t)len * sizeof(pal_char_t)); + if (buffer == NULL) + return; + + pal_str_vprintf(buffer, len, format, vl); + print_line_to_handle(buffer, GetStdHandle(STD_OUTPUT_HANDLE), stdout); + free(buffer); +} diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index e95c633893949a..6545684a4d143b 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -10,72 +10,6 @@ #include #include -void pal::file_vprintf(FILE* f, const pal::char_t* format, va_list vl) -{ - // String functions like vfwprintf convert wide to multi-byte characters as if wcrtomb were called - that is, using the current C locale (LC_TYPE). - // In order to properly print UTF-8 and GB18030 characters, we need to use the version of vfwprintf that takes a locale. - _locale_t loc = _create_locale(LC_ALL, ".utf8"); - ::_vfwprintf_l(f, format, loc, vl); - ::fputwc(_X('\n'), f); - _free_locale(loc); -} - -namespace -{ - void file_printf(FILE* fallbackFileHandle, const pal::char_t* format, ...) - { - va_list args; - va_start(args, format); - pal::file_vprintf(fallbackFileHandle, format, args); - va_end(args); - } - - void print_line_to_handle(const pal::char_t* message, HANDLE handle, FILE* fallbackFileHandle) { - // String functions like vfwprintf convert wide to multi-byte characters as if wcrtomb were called - that is, using the current C locale (LC_TYPE). - // In order to properly print UTF-8 and GB18030 characters to the console without requiring the user to use chcp to a compatible locale, we use WriteConsoleW. - // However, WriteConsoleW will fail if the output is redirected to a file - in that case we will write to the fallbackFileHandle - DWORD output; - // GetConsoleMode returns FALSE when the output is redirected to a file, and we need to output to the fallback file handle. - BOOL isConsoleOutput = ::GetConsoleMode(handle, &output); - if (isConsoleOutput == FALSE) - { - // We use file_vprintf to handle UTF-8 formatting. The WriteFile api will output the bytes directly with Unicode bytes, - // while pal::file_vprintf will convert the characters to UTF-8. - file_printf(fallbackFileHandle, _X("%s"), message); - } - else { - ::WriteConsoleW(handle, message, (int)pal::strlen(message), NULL, NULL); - ::WriteConsoleW(handle, _X("\n"), 1, NULL, NULL); - } - } -} - -void pal::err_print_line(const pal::char_t* message) -{ - // Forward to helper to handle UTF-8 formatting and redirection - print_line_to_handle(message, ::GetStdHandle(STD_ERROR_HANDLE), stderr); -} - -void pal::out_vprint_line(const pal::char_t* format, va_list vl) -{ - va_list vl_copy; - va_copy(vl_copy, vl); - // Get the length of the formatted string + 1 for null terminator - int len = 1 + pal::strlen_vprintf(format, vl_copy); - if (len < 0) - { - return; - } - std::vector buffer(len); - int written = pal::str_vprintf(&buffer[0], len, format, vl); - if (written != len - 1) - { - return; - } - // Forward to helper to handle UTF-8 formatting and redirection - print_line_to_handle(&buffer[0], ::GetStdHandle(STD_OUTPUT_HANDLE), stdout); -} - namespace { typedef DWORD(WINAPI *get_temp_path_func_ptr)(DWORD buffer_len, LPWSTR buffer); @@ -266,69 +200,17 @@ bool pal::get_loaded_library( bool pal::load_library(const string_t* in_path, dll_t* dll) { - string_t path = *in_path; - - // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR: - // In framework-dependent apps, coreclr would come from another directory than the host, - // so make sure coreclr dependencies can be resolved from coreclr.dll load dir. - - if (LongFile::IsPathNotFullyQualified(path)) - { - if (!pal::fullpath(&path)) - { - trace::error(_X("Failed to load [%s], HRESULT: 0x%X"), path.c_str(), HRESULT_FROM_WIN32(GetLastError())); - 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)); - - *dll = ::LoadLibraryExW(path.c_str(), NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); - if (*dll == nullptr) - { - int error_code = ::GetLastError(); - trace::error(_X("Failed to load [%s], HRESULT: 0x%X"), path.c_str(), HRESULT_FROM_WIN32(error_code)); - if (error_code == ERROR_BAD_EXE_FORMAT) - { - trace::error(_X(" - Ensure the library matches the current process architecture: ") _STRINGIFY(CURRENT_ARCH_NAME)); - } - - return false; - } - - // Pin the module - HMODULE dummy_module; - if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path.c_str(), &dummy_module)) - { - trace::error(_X("Failed to pin library [%s] in [%s]"), path.c_str(), _STRINGIFY(__FUNCTION__)); - return false; - } - - if (trace::is_enabled()) - { - string_t buf; - GetModuleFileNameWrapper(*dll, &buf); - trace::info(_X("Loaded library from %s"), buf.c_str()); - } - - return true; + return pal_load_library(in_path->c_str(), dll); } -pal::proc_t pal::get_symbol(dll_t library, const char* name) +pal_proc_t pal::get_symbol(dll_t library, const char* name) { - auto result = ::GetProcAddress(library, name); - if (result == nullptr) - { - trace::info(_X("Probed for and did not resolve library symbol %S"), name); - } - - return result; + return pal_get_symbol(library, name); } void pal::unload_library(dll_t library) { - // No-op. On windows, we pin the library, so it can't be unloaded. + pal_unload_library(library); } static @@ -688,15 +570,7 @@ bool pal::is_path_rooted(const string_t& path) bool pal::is_path_fully_qualified(const string_t& path) { - if (path.length() < 2) - return false; - - // Check for UNC and DOS device paths - if (is_directory_separator(path[0])) - return path[1] == L'?' || is_directory_separator(path[1]); - - // Check for drive absolute path - for example C:\. - return path.length() >= 3 && path[1] == L':' && is_directory_separator(path[2]); + return pal_is_path_fully_qualified(path.c_str()); } // Returns true only if an env variable can be read successfully to be non-empty. @@ -828,20 +702,6 @@ bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir) return fullpath(&extraction_dir); } -static bool wchar_convert_helper(DWORD code_page, const char* cstr, size_t len, pal::string_t* out) -{ - out->clear(); - - // No need of explicit null termination, so pass in the actual length. - size_t size = ::MultiByteToWideChar(code_page, 0, cstr, static_cast(len), nullptr, 0); - if (size == 0) - { - return false; - } - out->resize(size, '\0'); - return ::MultiByteToWideChar(code_page, 0, cstr, static_cast(len), &(*out)[0], static_cast(out->size())) != 0; -} - size_t pal::pal_utf8string(const pal::string_t& str, char* out_buffer, size_t len) { // Pass -1 as we want explicit null termination in the char buffer. @@ -874,7 +734,19 @@ bool pal::pal_clrstring(const pal::string_t& str, std::vector* out) bool pal::clr_palstring(const char* cstr, pal::string_t* out) { - return wchar_convert_helper(CP_UTF8, cstr, ::strlen(cstr), out); + out->clear(); + + // Pass the explicit input length (excluding the terminating NUL) so the + // conversion writes only the content characters into the string's buffer. + // An empty input yields a length of 0, which MultiByteToWideChar reports as a + // failure - preserving the historical contract that empty input fails. + int len = static_cast(::strlen(cstr)); + int size = ::MultiByteToWideChar(CP_UTF8, 0, cstr, len, nullptr, 0); + if (size == 0) + return false; + + out->resize(static_cast(size)); + return ::MultiByteToWideChar(CP_UTF8, 0, cstr, len, &(*out)[0], size) != 0; } typedef std::unique_ptr::type, decltype(&::CloseHandle)> SmartHandle; diff --git a/src/native/corehost/hostmisc/trace.c b/src/native/corehost/hostmisc/trace.c index 347d04459fb3d6..25c2599704b111 100644 --- a/src/native/corehost/hostmisc/trace.c +++ b/src/native/corehost/hostmisc/trace.c @@ -118,79 +118,6 @@ static void trace_format_timestamp(pal_char_t* buffer, size_t buffer_len) #endif } -static void trace_err_print_line(const pal_char_t* message) -{ -#if defined(_WIN32) - // On Windows, use WriteConsoleW for proper Unicode output, fall back to - // file output if stderr is redirected. - HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE); - DWORD mode; - if (GetConsoleMode(hStdErr, &mode)) - { - WriteConsoleW(hStdErr, message, (DWORD)pal_strlen(message), NULL, NULL); - WriteConsoleW(hStdErr, L"\n", 1, NULL, NULL); - } - else - { - _locale_t loc = _create_locale(LC_ALL, ".utf8"); - _fwprintf_l(stderr, L"%s\n", loc, message); - _free_locale(loc); - } -#else - fputs(message, stderr); - fputc('\n', stderr); -#endif -} - -static void trace_file_vprintf(FILE* f, const pal_char_t* format, va_list vl) -{ -#if defined(_WIN32) - _locale_t loc = _create_locale(LC_ALL, ".utf8"); - _vfwprintf_l(f, format, loc, vl); - fputwc(L'\n', f); - _free_locale(loc); -#else - vfprintf(f, format, vl); - fputc('\n', f); -#endif -} - -static void trace_out_vprint_line(const pal_char_t* format, va_list vl) -{ -#if defined(_WIN32) - va_list vl_copy; - va_copy(vl_copy, vl); - int len = 1 + _vscwprintf(format, vl_copy); - va_end(vl_copy); - if (len <= 0) - return; - - pal_char_t* buffer = (pal_char_t*)malloc((size_t)len * sizeof(pal_char_t)); - if (buffer == NULL) - return; - - _vsnwprintf_s(buffer, len, _TRUNCATE, format, vl); - - HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD mode; - if (GetConsoleMode(hStdOut, &mode)) - { - WriteConsoleW(hStdOut, buffer, (DWORD)wcslen(buffer), NULL, NULL); - WriteConsoleW(hStdOut, L"\n", 1, NULL, NULL); - } - else - { - _locale_t loc = _create_locale(LC_ALL, ".utf8"); - _fwprintf_l(stdout, L"%s\n", loc, buffer); - _free_locale(loc); - } - free(buffer); -#else - vfprintf(stdout, format, vl); - fputc('\n', stdout); -#endif -} - // // Turn on tracing for the corehost based on DOTNET_HOST_TRACE and DOTNET_HOST_TRACEFILE env. // @@ -317,7 +244,7 @@ void trace_verbose_v(const pal_char_t* format, va_list args) return; trace_lock_acquire(); - trace_file_vprintf(g_trace_file, format, args); + pal_file_vprintf(g_trace_file, format, args); trace_lock_release(); } @@ -335,7 +262,7 @@ void trace_info_v(const pal_char_t* format, va_list args) return; trace_lock_acquire(); - trace_file_vprintf(g_trace_file, format, args); + pal_file_vprintf(g_trace_file, format, args); trace_lock_release(); } @@ -381,7 +308,7 @@ void trace_error_v(const pal_char_t* format, va_list args) trace_lock_acquire(); if (g_error_writer == NULL) { - trace_err_print_line(buffer); + pal_err_print_line(buffer); } else { @@ -389,7 +316,7 @@ void trace_error_v(const pal_char_t* format, va_list args) } if (g_trace_verbosity && ((g_trace_file != stderr) || g_error_writer != NULL)) - trace_file_vprintf(g_trace_file, format, trace_args); + pal_file_vprintf(g_trace_file, format, trace_args); trace_lock_release(); free(buffer); @@ -408,7 +335,7 @@ void trace_error(const pal_char_t* format, ...) void trace_println_v(const pal_char_t* format, va_list args) { trace_lock_acquire(); - trace_out_vprint_line(format, args); + pal_out_vprint_line(format, args); trace_lock_release(); } @@ -431,7 +358,7 @@ void trace_warning_v(const pal_char_t* format, va_list args) return; trace_lock_acquire(); - trace_file_vprintf(g_trace_file, format, args); + pal_file_vprintf(g_trace_file, format, args); trace_lock_release(); } diff --git a/src/native/corehost/hostmisc/utils.c b/src/native/corehost/hostmisc/utils.c index 88801fb9c5e523..a9e79309e8ab21 100644 --- a/src/native/corehost/hostmisc/utils.c +++ b/src/native/corehost/hostmisc/utils.c @@ -33,6 +33,28 @@ void utils_get_filename(const pal_char_t* path, pal_char_t* out_name, size_t out memcpy(out_name, name, (len + 1) * sizeof(pal_char_t)); } +bool utils_starts_with(const pal_char_t* value, size_t value_len, const pal_char_t* prefix, size_t prefix_len, bool match_case) +{ + // Cannot start with an empty string. + if (prefix_len == 0 || value_len < prefix_len) + return false; + + return match_case + ? pal_strncmp(value, prefix, prefix_len) == 0 + : pal_strncasecmp(value, prefix, prefix_len) == 0; +} + +bool utils_ends_with(const pal_char_t* value, size_t value_len, const pal_char_t* suffix, size_t suffix_len, bool match_case) +{ + if (value_len < suffix_len) + return false; + + const pal_char_t* tail = value + value_len - suffix_len; + return match_case + ? pal_strncmp(tail, suffix, suffix_len) == 0 + : pal_strncasecmp(tail, suffix, suffix_len) == 0; +} + void utils_append_path(pal_char_t* path_buffer, size_t path_buffer_len, const pal_char_t* component) { if (component == NULL || component[0] == _X('\0')) diff --git a/src/native/corehost/hostmisc/utils.cpp b/src/native/corehost/hostmisc/utils.cpp index 5dfd6b8af8a28e..81d28a4bbf5bfa 100644 --- a/src/native/corehost/hostmisc/utils.cpp +++ b/src/native/corehost/hostmisc/utils.cpp @@ -32,20 +32,12 @@ bool coreclr_exists_in_dir(const pal::string_t& candidate) bool utils::starts_with(const pal::string_t& value, const pal::char_t* prefix, size_t prefix_len, bool match_case) { - // Cannot start with an empty string. - if (prefix_len == 0) - return false; - - auto cmp = match_case ? pal::strncmp : pal::strncasecmp; - return (value.size() >= prefix_len) && - cmp(value.c_str(), prefix, prefix_len) == 0; + return utils_starts_with(value.c_str(), value.size(), prefix, prefix_len, match_case); } bool utils::ends_with(const pal::string_t& value, const pal::char_t* suffix, size_t suffix_len, bool match_case) { - auto cmp = match_case ? pal::strcmp : pal::strcasecmp; - return (value.size() >= suffix_len) && - cmp(value.c_str() + value.size() - suffix_len, suffix) == 0; + return utils_ends_with(value.c_str(), value.size(), suffix, suffix_len, match_case); } void append_path(pal::string_t* path1, const pal::char_t* path2) diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index e9952cf8e22901..c7371bf6b04392 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -216,6 +216,9 @@ extern "C" { void utils_get_filename(const pal_char_t* path, pal_char_t* out_name, size_t out_name_len); +bool utils_starts_with(const pal_char_t* value, size_t value_len, const pal_char_t* prefix, size_t prefix_len, bool match_case); +bool utils_ends_with(const pal_char_t* value, size_t value_len, const pal_char_t* suffix, size_t suffix_len, bool match_case); + void utils_append_path(pal_char_t* path_buffer, size_t path_buffer_len, const pal_char_t* component); // Caller should free() the returned pointer.