From e16de04bf1a2fe9864667c56edf0eeee774e0fb8 Mon Sep 17 00:00:00 2001 From: Mike Oliphant Date: Wed, 24 Jul 2024 13:44:18 -0700 Subject: [PATCH] Add runtime config parameter to force ijwhost to load assemblies in an isolated context (#105337) * Add support for isolated load context in LoadInMemoryAssemblyInContext by passing -1 as loadContext * Have ijwhost check a runtime config parameter to determine if it should run in an isolated load context * Added test for ijwhost isolated load context runtime config option --- .../InteropServices/InMemoryAssemblyLoader.cs | 9 +++-- .../NativeHosting/Ijwhost.cs | 35 +++++++++++++++++++ src/native/corehost/ijwhost/ijwhost.cpp | 14 ++++++-- src/native/corehost/ijwhost/ijwhost.h | 4 ++- src/native/corehost/ijwhost/ijwthunk.cpp | 6 ++-- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs index dc8d8ccb4c7cc..55e4227481724 100644 --- a/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs +++ b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs @@ -49,7 +49,7 @@ private static unsafe void LoadInMemoryAssemblyInContextWhenSupported(IntPtr mod /// /// The native module handle for the assembly. /// The path to the assembly (as a pointer to a UTF-16 C string). - /// Load context (currently must be IntPtr.Zero) + /// Load context (currently must be either IntPtr.Zero for default ALC or -1 for isolated ALC) [UnmanagedCallersOnly] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "The same C++/CLI feature switch applies to LoadInMemoryAssembly and this function. We rely on the warning from LoadInMemoryAssembly.")] @@ -58,9 +58,12 @@ public static unsafe void LoadInMemoryAssemblyInContext(IntPtr moduleHandle, Int if (!IsSupported) throw new NotSupportedException(SR.NotSupported_CppCli); - ArgumentOutOfRangeException.ThrowIfNotEqual(loadContext, IntPtr.Zero); + if ((loadContext != IntPtr.Zero) && (loadContext != -1)) + { + throw new ArgumentOutOfRangeException(nameof(loadContext)); + } - LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath, AssemblyLoadContext.Default); + LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath, (loadContext == IntPtr.Zero) ? AssemblyLoadContext.Default : null); } [RequiresUnreferencedCode("C++/CLI is not trim-compatible", Url = "https://aka.ms/dotnet-illink/nativehost")] diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs index e7efe5eaafff8..34a76016ecfb4 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs @@ -38,6 +38,41 @@ public void LoadLibrary() .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LoadLibrary_ContextConfig(bool load_isolated) + { + // make a copy of a portion of the shared state because we will modify it + using (var app = sharedState.IjwApp.Copy()) + { + string[] args = { + "ijwhost", + app.AppDll, + "NativeEntryPoint" + }; + + RuntimeConfig.FromFile(app.RuntimeConfigJson) + .WithProperty("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext", load_isolated.ToString()) + .Save(); + + CommandResult result = sharedState.CreateNativeHostCommand(args, TestContext.BuiltDotNet.BinPath) + .Execute(); + + result.Should().Pass() + .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class"); + + if (load_isolated) // Assembly should be loaded in an isolated context + { + result.Should().HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext"); + } + else // Assembly should be loaded in the default context + { + result.Should().HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); + } + } + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/src/native/corehost/ijwhost/ijwhost.cpp b/src/native/corehost/ijwhost/ijwhost.cpp index 7f1684cca6263..c2e1d22a32cc5 100644 --- a/src/native/corehost/ijwhost/ijwhost.cpp +++ b/src/native/corehost/ijwhost/ijwhost.cpp @@ -20,7 +20,7 @@ #define IJW_API SHARED_API #endif // _WIN32 -pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate) +pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate, void **load_context) { get_function_pointer_fn get_function_pointer; int status = load_fxr_and_get_delegate( @@ -40,7 +40,17 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m return StatusCode::Success; }, - [](pal::dll_t fxr, hostfxr_handle context){ }, + [load_context](pal::dll_t fxr, hostfxr_handle context) + { + *load_context = nullptr; // default load context + auto get_runtime_property_value = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_get_runtime_property_value")); + const pal::char_t* value; + if (get_runtime_property_value(context, _X("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext"), &value) == StatusCode::Success + && pal::strcasecmp(value, _X("true")) == 0) + { + *load_context = ISOLATED_CONTEXT; // Isolated load context + } + }, reinterpret_cast(&get_function_pointer) ); if (status != StatusCode::Success) diff --git a/src/native/corehost/ijwhost/ijwhost.h b/src/native/corehost/ijwhost/ijwhost.h index c3698a4d4acb6..adfd52f71a99c 100644 --- a/src/native/corehost/ijwhost/ijwhost.h +++ b/src/native/corehost/ijwhost/ijwhost.h @@ -15,8 +15,10 @@ bool are_thunks_installed_for_module(HMODULE instance); using load_in_memory_assembly_fn = void(STDMETHODCALLTYPE*)(pal::dll_t handle, const pal::char_t* path, void* load_context); -pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate); +pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate, void **load_context); extern HANDLE g_heapHandle; +#define ISOLATED_CONTEXT (void*)-1 + #endif diff --git a/src/native/corehost/ijwhost/ijwthunk.cpp b/src/native/corehost/ijwhost/ijwthunk.cpp index eeddf10fd931c..2c24f4d6f9207 100644 --- a/src/native/corehost/ijwhost/ijwthunk.cpp +++ b/src/native/corehost/ijwhost/ijwthunk.cpp @@ -119,7 +119,9 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui bootstrap_thunk *pThunk = bootstrap_thunk::get_thunk_from_cookie(cookie); load_in_memory_assembly_fn loadInMemoryAssembly; pal::dll_t moduleHandle = pThunk->get_dll_handle(); - pal::hresult_t status = get_load_in_memory_assembly_delegate(moduleHandle, &loadInMemoryAssembly); + + void* load_context = nullptr; + pal::hresult_t status = get_load_in_memory_assembly_delegate(moduleHandle, &loadInMemoryAssembly, &load_context); if (status != StatusCode::Success) { @@ -145,7 +147,7 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui #pragma warning (pop) } - loadInMemoryAssembly(moduleHandle, app_path.c_str(), nullptr); + loadInMemoryAssembly(moduleHandle, app_path.c_str(), load_context); std::uintptr_t thunkAddress = *(pThunk->get_slot_address());