From 94164297293d85e7565770d32c8a03435abe79e4 Mon Sep 17 00:00:00 2001 From: Mike Oliphant Date: Sun, 21 Jul 2024 10:04:33 -0700 Subject: [PATCH 1/6] Add support for isolated load context in LoadInMemoryAssemblyInContext by passing -1 as loadContext --- .../Runtime/InteropServices/InMemoryAssemblyLoader.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 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")] From 8d431710bbcdc5f2a8055123dadde127a9cc6f23 Mon Sep 17 00:00:00 2001 From: Mike Oliphant Date: Sun, 21 Jul 2024 10:05:49 -0700 Subject: [PATCH 2/6] Have ijwhost check a runtime config parameter ("System.Runtime.InteropServices.IJWHost.LoadComponentInIsolatedContext") to determine if it should run in an isolated load context --- src/native/corehost/ijwhost/ijwhost.cpp | 14 ++++++++++++-- src/native/corehost/ijwhost/ijwhost.h | 4 +++- src/native/corehost/ijwhost/ijwthunk.cpp | 6 ++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/native/corehost/ijwhost/ijwhost.cpp b/src/native/corehost/ijwhost/ijwhost.cpp index 4d9925366e6b9..49005755707fd 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.IJWHost.LoadComponentInIsolatedContext"), &value) == StatusCode::Success + && pal::strcasecmp(value, _X("true")) == 0) + { + *load_context = ISOLATED_CONTEXT; // Isolated load context + } + }, reinterpret_cast(&get_function_pointer), true // ignore missing config file if there's an active context ); 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()); From 7e7201955f5b5f29f46de1e8d1d4535d5ccb5dd9 Mon Sep 17 00:00:00 2001 From: Mike Oliphant Date: Sun, 21 Jul 2024 10:06:14 -0700 Subject: [PATCH 3/6] Added test for ijwhost isolated load context runtime config option --- .../NativeHosting/Ijwhost.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs index 44d04bafb7777..1039fcbda40ae 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs @@ -19,9 +19,10 @@ public Ijwhost(SharedTestState sharedTestState) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void LoadLibrary(bool no_runtimeconfig) + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(false, true)] + public void LoadLibrary(bool no_runtimeconfig, bool load_isolated) { // make a copy of a portion of the shared state because we will modify it using (var app = sharedState.IjwApp.Copy()) @@ -31,6 +32,14 @@ public void LoadLibrary(bool no_runtimeconfig) app.AppDll, "NativeEntryPoint" }; + + if (load_isolated) + { + RuntimeConfig.FromFile(app.RuntimeConfigJson) + .WithProperty("System.Runtime.InteropServices.IJWHost.LoadComponentInIsolatedContext", "true") + .Save(); + } + if (no_runtimeconfig) { File.Delete(app.RuntimeConfigJson); @@ -46,9 +55,18 @@ public void LoadLibrary(bool no_runtimeconfig) } else { - result.Should().Pass() - .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") - .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); + if (load_isolated) // Assembly should be loaded in an isolated context + { + result.Should().Pass() + .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") + .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext"); + } + else + { + result.Should().Pass() // Assembly should be loaded in the default context + .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") + .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); + } } } } From 302edba366d2a6c641571a9b352645b3a305a7c5 Mon Sep 17 00:00:00 2001 From: Mike Oliphant Date: Tue, 23 Jul 2024 08:56:13 -0700 Subject: [PATCH 4/6] IJWHost->CppCLI in isloated load context config flag name. Split out isolated context flag tests into a separate test method. --- .../NativeHosting/Ijwhost.cs | 69 ++++++++++++------- src/native/corehost/ijwhost/ijwhost.cpp | 2 +- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs index 1039fcbda40ae..6146b6865b282 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs @@ -19,10 +19,9 @@ public Ijwhost(SharedTestState sharedTestState) } [Theory] - [InlineData(true, false)] - [InlineData(false, false)] - [InlineData(false, true)] - public void LoadLibrary(bool no_runtimeconfig, bool load_isolated) + [InlineData(true)] + [InlineData(false)] + public void LoadLibrary(bool no_runtimeconfig) { // make a copy of a portion of the shared state because we will modify it using (var app = sharedState.IjwApp.Copy()) @@ -33,13 +32,6 @@ public void LoadLibrary(bool no_runtimeconfig, bool load_isolated) "NativeEntryPoint" }; - if (load_isolated) - { - RuntimeConfig.FromFile(app.RuntimeConfigJson) - .WithProperty("System.Runtime.InteropServices.IJWHost.LoadComponentInIsolatedContext", "true") - .Save(); - } - if (no_runtimeconfig) { File.Delete(app.RuntimeConfigJson); @@ -55,22 +47,53 @@ public void LoadLibrary(bool no_runtimeconfig, bool load_isolated) } else { - if (load_isolated) // Assembly should be loaded in an isolated context - { - result.Should().Pass() - .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") - .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext"); - } - else - { - result.Should().Pass() // Assembly should be loaded in the default context - .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") - .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); - } + result.Should().Pass() + .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") + .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" + }; + + if (load_isolated) + { + RuntimeConfig.FromFile(app.RuntimeConfigJson) + .WithProperty("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext", "true") + .Save(); + } + + CommandResult result = sharedState.CreateNativeHostCommand(args, TestContext.BuiltDotNet.BinPath) + .Execute(); + + if (load_isolated) // Assembly should be loaded in an isolated context + { + result.Should().Pass() + .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") + .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext"); + } + else // Assembly should be loaded in the default context + { + result.Should().Pass() + .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") + .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); + } + } + } + + [Fact] public void LoadLibraryWithoutRuntimeConfigButActiveRuntime() { diff --git a/src/native/corehost/ijwhost/ijwhost.cpp b/src/native/corehost/ijwhost/ijwhost.cpp index 49005755707fd..e64e01a56580f 100644 --- a/src/native/corehost/ijwhost/ijwhost.cpp +++ b/src/native/corehost/ijwhost/ijwhost.cpp @@ -45,7 +45,7 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m *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.IJWHost.LoadComponentInIsolatedContext"), &value) == StatusCode::Success + 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 From c939b0b202ae1cf1fc447052b40accaa6072da99 Mon Sep 17 00:00:00 2001 From: Mike Oliphant Date: Tue, 23 Jul 2024 10:16:23 -0700 Subject: [PATCH 5/6] Fixed isolated load context flag not useing load_isolated parameter --- .../tests/HostActivation.Tests/NativeHosting/Ijwhost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs index 6146b6865b282..57624c972262a 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs @@ -71,7 +71,7 @@ public void LoadLibrary_ContextConfig(bool load_isolated) if (load_isolated) { RuntimeConfig.FromFile(app.RuntimeConfigJson) - .WithProperty("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext", "true") + .WithProperty("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext", load_isolated.ToString()) .Save(); } From a49a98f824c7420c8fde71fb91d265183c828634 Mon Sep 17 00:00:00 2001 From: Mike Oliphant Date: Tue, 23 Jul 2024 11:15:02 -0700 Subject: [PATCH 6/6] Fixed ijwhost test only altering config if load_isolated was true. Consolidate shared test checks. --- .../NativeHosting/Ijwhost.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs index 57624c972262a..fa00b0419a85e 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs @@ -68,27 +68,23 @@ public void LoadLibrary_ContextConfig(bool load_isolated) "NativeEntryPoint" }; - if (load_isolated) - { - RuntimeConfig.FromFile(app.RuntimeConfigJson) + 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().Pass() - .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") - .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext"); + result.Should().HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext"); } else // Assembly should be loaded in the default context { - result.Should().Pass() - .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") - .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); + result.Should().HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); } } }