From 5d744ee86ae9f329e13160837b0ba938ed7f00c9 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 23 Nov 2022 16:34:00 -0800 Subject: [PATCH 1/5] Add mechanism for runtime to query host for information --- src/coreclr/dlls/mscoree/exports.cpp | 27 +++++++++-- src/coreclr/inc/hostinformation.h | 16 +++++++ src/coreclr/vm/CMakeLists.txt | 1 + src/coreclr/vm/hostinformation.cpp | 46 ++++++++++++++++++ src/native/corehost/host_runtime_contract.h | 41 ++++++++++++++++ src/native/corehost/hostmisc/pal.h | 11 +++++ src/native/corehost/hostmisc/pal.windows.cpp | 11 +++++ .../hostpolicy/hostpolicy_context.cpp | 48 ++++++++++++++++++- .../corehost/hostpolicy/hostpolicy_context.h | 3 ++ 9 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 src/coreclr/inc/hostinformation.h create mode 100644 src/coreclr/vm/hostinformation.cpp create mode 100644 src/native/corehost/host_runtime_contract.h diff --git a/src/coreclr/dlls/mscoree/exports.cpp b/src/coreclr/dlls/mscoree/exports.cpp index e8ee88275df8e..7cf1f629a6027 100644 --- a/src/coreclr/dlls/mscoree/exports.cpp +++ b/src/coreclr/dlls/mscoree/exports.cpp @@ -19,6 +19,8 @@ #endif // FEATURE_GDBJIT #include "bundle.h" #include "pinvokeoverride.h" +#include +#include #define ASSERTE_ALL_BUILDS(expr) _ASSERTE_ALL_BUILDS((expr)) @@ -122,7 +124,8 @@ static void ConvertConfigPropertiesToUnicode( LPCWSTR** propertyValuesWRef, BundleProbeFn** bundleProbe, PInvokeOverrideFn** pinvokeOverride, - bool* hostPolicyEmbedded) + bool* hostPolicyEmbedded, + host_runtime_contract** hostContract) { LPCWSTR* propertyKeysW = new (nothrow) LPCWSTR[propertyCount]; ASSERTE_ALL_BUILDS(propertyKeysW != nullptr); @@ -139,7 +142,8 @@ static void ConvertConfigPropertiesToUnicode( { // If this application is a single-file bundle, the bundle-probe callback // is passed in as the value of "BUNDLE_PROBE" property (encoded as a string). - *bundleProbe = (BundleProbeFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); + if (*bundleProbe == nullptr) + *bundleProbe = (BundleProbeFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); } else if (strcmp(propertyKeys[propertyIndex], "PINVOKE_OVERRIDE") == 0) { @@ -152,6 +156,14 @@ static void ConvertConfigPropertiesToUnicode( // The HOSTPOLICY_EMBEDDED property indicates if the executable has hostpolicy statically linked in *hostPolicyEmbedded = (wcscmp(propertyValuesW[propertyIndex], W("true")) == 0); } + else if (strcmp(propertyKeys[propertyIndex], HOST_PROPERTY_RUNTIME_CONTRACT) == 0) + { + // Host contract is passed in as the value of HOST_RUNTIME_CONTRACT property (encoded as a string). + host_runtime_contract* hostContractLocal = (host_runtime_contract*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); + *hostContract = hostContractLocal; + if (hostContractLocal->bundle_probe != nullptr) + *bundleProbe = hostContractLocal->bundle_probe; + } } *propertyKeysWRef = propertyKeysW; @@ -196,6 +208,7 @@ int coreclr_initialize( BundleProbeFn* bundleProbe = nullptr; bool hostPolicyEmbedded = false; PInvokeOverrideFn* pinvokeOverride = nullptr; + host_runtime_contract* hostContract = nullptr; ConvertConfigPropertiesToUnicode( propertyKeys, @@ -205,7 +218,8 @@ int coreclr_initialize( &propertyValuesW, &bundleProbe, &pinvokeOverride, - &hostPolicyEmbedded); + &hostPolicyEmbedded, + &hostContract); #ifdef TARGET_UNIX DWORD error = PAL_InitializeCoreCLR(exePath, g_coreclr_embedded); @@ -221,7 +235,12 @@ int coreclr_initialize( g_hostpolicy_embedded = hostPolicyEmbedded; - if (pinvokeOverride != nullptr) + if (hostContract != nullptr) + { + HostInformation::SetContract(hostContract); + } + + if (pinvokeOverride != nullptr && (hostContract == nullptr || hostContract->pinvoke_override == nullptr)) { PInvokeOverride::SetPInvokeOverride(pinvokeOverride, PInvokeOverride::Source::RuntimeConfiguration); } diff --git a/src/coreclr/inc/hostinformation.h b/src/coreclr/inc/hostinformation.h new file mode 100644 index 0000000000000..d57b4729d30e6 --- /dev/null +++ b/src/coreclr/inc/hostinformation.h @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef _HOSTINFORMATION_H_ +#define _HOSTINFORMATION_H_ + +#include + +class HostInformation +{ +public: + static void SetContract(_In_ host_runtime_contract* hostContract); + static bool GetProperty(_In_z_ const char* name, SString& value); +}; + +#endif // _HOSTINFORMATION_H_ diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 68169ccb4edea..7d7826b5a0d70 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -328,6 +328,7 @@ set(VM_SOURCES_WKS genanalysis.cpp genmeth.cpp hosting.cpp + hostinformation.cpp ilmarshalers.cpp interopconverter.cpp interoputil.cpp diff --git a/src/coreclr/vm/hostinformation.cpp b/src/coreclr/vm/hostinformation.cpp new file mode 100644 index 0000000000000..6ad189290b71c --- /dev/null +++ b/src/coreclr/vm/hostinformation.cpp @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "common.h" +#include "hostinformation.h" +#include "pinvokeoverride.h" + +namespace +{ + host_runtime_contract* s_hostContract = nullptr; +} + +void HostInformation::SetContract(_In_ host_runtime_contract* hostContract) +{ + _ASSERTE(s_hostContract == nullptr); + s_hostContract = hostContract; + + if (s_hostContract->pinvoke_override != nullptr) + PInvokeOverride::SetPInvokeOverride(s_hostContract->pinvoke_override, PInvokeOverride::Source::RuntimeConfiguration); +} + +bool HostInformation::GetProperty(_In_z_ const char* name, SString& value) +{ + if (s_hostContract == nullptr || s_hostContract->get_runtime_property == nullptr) + return false; + + size_t len = MAX_PATH + 1; + char* dest = value.OpenUTF8Buffer(static_cast(len)); + size_t lenActual = s_hostContract->get_runtime_property(name, dest, len, s_hostContract->context); + value.CloseBuffer(); + + // Doesn't exist or failed to get property + if (lenActual == (size_t)-1 || lenActual == 0) + return false; + + if (lenActual <= len) + return true; + + // Buffer was not large enough + len = lenActual; + dest = value.OpenUTF8Buffer(static_cast(len)); + lenActual = s_hostContract->get_runtime_property(name, dest, len, s_hostContract->context); + value.CloseBuffer(); + + return lenActual > 0 && lenActual <= len; +} diff --git a/src/native/corehost/host_runtime_contract.h b/src/native/corehost/host_runtime_contract.h new file mode 100644 index 0000000000000..ebdb5ab842766 --- /dev/null +++ b/src/native/corehost/host_runtime_contract.h @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __HOST_RUNTIME_CONTRACT_H__ +#define __HOST_RUNTIME_CONTRACT_H__ + +#include +#include + +#if defined(_WIN32) + #define HOST_CONTRACT_CALLTYPE __stdcall +#else + #define HOST_CONTRACT_CALLTYPE +#endif + +// Known host property names +#define HOST_PROPERTY_RUNTIME_CONTRACT "HOST_RUNTIME_CONTRACT" +#define HOST_PROPERTY_ENTRY_ASSEMBLY_NAME "ENTRY_ASSEMBLY_NAME" + +struct host_runtime_contract +{ + void* context; + + bool(HOST_CONTRACT_CALLTYPE* bundle_probe)( + const char* path, + int64_t* offset, + int64_t* size, + int64_t* compressedSize); + + const void* (HOST_CONTRACT_CALLTYPE* pinvoke_override)( + const char* library_name, + const char* entry_point_name); + + size_t(HOST_CONTRACT_CALLTYPE* get_runtime_property)( + const char* key, + char* value_buffer, + size_t value_buffer_size, + void* contract_context); +}; + +#endif // __HOST_RUNTIME_CONTRACT_H__ diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 2af1dbd214fd6..dee5bf3c94d44 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -180,6 +180,7 @@ namespace pal return buffer; } + size_t pal_utf8string(const string_t& str, char* out_buffer, size_t len); bool pal_utf8string(const string_t& str, std::vector* out); bool pal_clrstring(const string_t& str, std::vector* out); bool clr_palstring(const char* cstr, string_t* out); @@ -236,6 +237,16 @@ namespace pal inline const string_t strerror(int errnum) { return ::strerror(errnum); } + inline size_t pal_utf8string(const string_t& str, char* out_buffer, size_t buffer_len) + { + size_t len = str.size() + 1; + if (buffer_len < len) + return len; + + ::strncpy(out_buffer, str.c_str(), str.size()); + out_buffer[len - 1] = '\0'; + return len; + } inline bool pal_utf8string(const string_t& str, std::vector* out) { out->assign(str.begin(), str.end()); out->push_back('\0'); return true; } inline bool pal_clrstring(const string_t& str, std::vector* out) { return pal_utf8string(str, out); } inline bool clr_palstring(const char* cstr, string_t* out) { out->assign(cstr); return true; } diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index bbb6fabb30b79..4dd632ac88d33 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -689,6 +689,17 @@ static bool wchar_convert_helper(DWORD code_page, const char* cstr, size_t len, 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. + size_t size = ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, nullptr, 0, nullptr, nullptr); + if (size == 0 || size > len) + return size; + + // Pass -1 as we want explicit null termination in the char buffer. + return ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, out_buffer, static_cast(len), nullptr, nullptr); +} + bool pal::pal_utf8string(const pal::string_t& str, std::vector* out) { out->clear(); diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index e1cff0dc376ef..27f94f4e1c67b 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "hostpolicy_context.h" -#include "hostpolicy.h" +#include +#include #include "deps_resolver.h" #include @@ -104,6 +105,31 @@ namespace return nullptr; } #endif + + size_t HOST_CONTRACT_CALLTYPE get_runtime_property( + const char* key, + char* value_buffer, + size_t value_buffer_size, + void* contract_context) + { + hostpolicy_context_t* context = static_cast(contract_context); + if (::strcmp(key, HOST_PROPERTY_ENTRY_ASSEMBLY_NAME) == 0) + { + return pal::pal_utf8string(get_filename_without_ext(context->application), value_buffer, value_buffer_size); + } + + pal::string_t key_str; + if (pal::clr_palstring(key, &key_str)) + { + const pal::char_t* value; + if (context->coreclr_properties.try_get(key_str.c_str(), &value)) + { + return pal::pal_utf8string(value, value_buffer, value_buffer_size); + } + } + + return -1; + } } int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs) @@ -324,5 +350,25 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a } #endif + { + host_contract = { this }; + if (bundle::info_t::is_single_file_bundle()) + { + host_contract.bundle_probe = &bundle_probe; +#if defined(NATIVE_LIBS_EMBEDDED) + host_contract.pinvoke_override = &pinvoke_override; +#endif + } + + host_contract.get_runtime_property = &get_runtime_property; + pal::stringstream_t ptr_stream; + ptr_stream << "0x" << std::hex << (size_t)(&host_contract); + if (!coreclr_properties.add(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT), ptr_stream.str().c_str())) + { + log_duplicate_property_error(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT)); + return StatusCode::LibHostDuplicateProperty; + } + } + return StatusCode::Success; } diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.h b/src/native/corehost/hostpolicy/hostpolicy_context.h index 2853e6ae98fb1..bec4630762fe0 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.h +++ b/src/native/corehost/hostpolicy/hostpolicy_context.h @@ -9,6 +9,7 @@ #include "args.h" #include "coreclr.h" #include +#include #include "hostpolicy_init.h" struct hostpolicy_context_t @@ -27,6 +28,8 @@ struct hostpolicy_context_t std::unique_ptr coreclr; + host_runtime_contract host_contract; + int initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs); }; From 4e6645cbcb0762ce72ad9dc5cb09eb1a8fd53a06 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 23 Nov 2022 16:34:49 -0800 Subject: [PATCH 2/5] Host tests for using host-runtime contract --- .../HostApiInvokerApp.csproj | 5 +- .../HostApiInvokerApp/HostRuntimeContract.cs | 82 +++++++++++++++++++ .../TestProjects/HostApiInvokerApp/Program.cs | 21 ++--- .../HostActivation.Tests/NativeHostApis.cs | 15 ++++ 4 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostApiInvokerApp.csproj b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostApiInvokerApp.csproj index 087d0c77a536d..d040ee25bcfec 100644 --- a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostApiInvokerApp.csproj +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostApiInvokerApp.csproj @@ -5,10 +5,7 @@ Exe $(MNAVersion) WINDOWS;$(DefineConstants) + true - - - - diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs new file mode 100644 index 0000000000000..918ead63f54c4 --- /dev/null +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace HostApiInvokerApp +{ + public static unsafe class HostRuntimeContract + { + internal struct host_runtime_contract + { + public void* context; + public IntPtr bundle_probe; + public IntPtr pinvoke_override; + public delegate* unmanaged[Stdcall] get_runtime_property; + } + + private static host_runtime_contract GetContract() + { + string contractString = (string)AppContext.GetData("HOST_RUNTIME_CONTRACT"); + if (string.IsNullOrEmpty(contractString)) + throw new Exception("HOST_RUNTIME_CONTRACT not found"); + + host_runtime_contract* contract = (host_runtime_contract*)Convert.ToUInt64(contractString, 16); + return *contract; + } + + private static void Test_get_runtime_property(string[] args) + { + host_runtime_contract contract = GetContract(); + + foreach (string name in args) + { + string value = GetProperty(name, contract); + Console.WriteLine($"{nameof(host_runtime_contract.get_runtime_property)}: {name} = {(value == null ? "" : value)}"); + } + + static string GetProperty(string name, host_runtime_contract contract) + { + Span nameSpan = stackalloc byte[Encoding.UTF8.GetMaxByteCount(name.Length)]; + byte* namePtr = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(nameSpan)); + int nameLen = Encoding.UTF8.GetBytes(name, nameSpan); + nameSpan[nameLen] = 0; + + nint len = 256; + byte* buffer = stackalloc byte[(int)len]; + nint lenActual = contract.get_runtime_property(namePtr, buffer, len, contract.context); + if (lenActual <= 0) + { + Console.WriteLine($"No value for {name} - {nameof(host_runtime_contract.get_runtime_property)} returned {lenActual}"); + return null; + } + + if (lenActual <= len) + return Encoding.UTF8.GetString(buffer, (int)lenActual); + + len = lenActual; + byte* expandedBuffer = stackalloc byte[(int)len]; + lenActual = contract.get_runtime_property(namePtr, expandedBuffer, len, contract.context); + return Encoding.UTF8.GetString(expandedBuffer, (int)lenActual); + } + } + + public static bool RunTest(string apiToTest, string[] args) + { + switch (apiToTest) + { + case $"{nameof(host_runtime_contract)}.{nameof(host_runtime_contract.get_runtime_property)}": + Test_get_runtime_property(args); + break; + default: + return false; + } + + return true; + } + } + +} diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/Program.cs b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/Program.cs index 1455a80a358df..2831ed8c3d487 100644 --- a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/Program.cs +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/Program.cs @@ -31,16 +31,13 @@ public static void MainCore(string[] args) Console.WriteLine("Hello World!"); Console.WriteLine(string.Join(Environment.NewLine, args)); - // A small operation involving NewtonSoft.Json to ensure the assembly is loaded properly - var t = typeof(Newtonsoft.Json.JsonReader); - // Enable tracing so that test assertion failures are easier to diagnose. Environment.SetEnvironmentVariable("COREHOST_TRACE", "1"); // If requested, test multilevel lookup using fake Global SDK directories: // 1. using a fake ProgramFiles location // 2. using a fake SDK Self-Registered location - // Note that this has to be set here and not in the calling test process because + // Note that this has to be set here and not in the calling test process because // %ProgramFiles% gets reset on process creation. string testMultilevelLookupProgramFiles = Environment.GetEnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES"); string testMultilevelLookupSelfRegistered = Environment.GetEnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED"); @@ -65,17 +62,15 @@ public static void MainCore(string[] args) string apiToTest = args[0]; if (HostFXR.RunTest(apiToTest, args)) - { return; - } - else if (HostPolicy.RunTest(apiToTest, args)) - { + + if (HostPolicy.RunTest(apiToTest, args)) return; - } - else - { - throw new ArgumentException($"Invalid API to test passed as args[0]): {apiToTest}"); - } + + if (HostRuntimeContract.RunTest(apiToTest, args)) + return; + + throw new ArgumentException($"Invalid API to test passed as args[0]): {apiToTest}"); } } } diff --git a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs index 213b3790335ce..1f1380421e258 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs @@ -473,6 +473,21 @@ public void Hostpolicy_corehost_set_error_writer_test() .Should().Pass(); } + [Fact] + public void HostRuntimeContract_get_runtime_property() + { + var fixture = sharedTestState.HostApiInvokerAppFixture; + + fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, "host_runtime_contract.get_runtime_property", "APP_CONTEXT_BASE_DIRECTORY", "ENTRY_ASSEMBLY_NAME", "DOES_NOT_EXIST") + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining($"APP_CONTEXT_BASE_DIRECTORY = {Path.GetDirectoryName(fixture.TestProject.AppDll)}") + .And.HaveStdOutContaining($"ENTRY_ASSEMBLY_NAME = {fixture.TestProject.AssemblyName}") + .And.HaveStdOutContaining($"DOES_NOT_EXIST = "); + } + public class SharedTestState : IDisposable { public TestProjectFixture HostApiInvokerAppFixture { get; } From 30599709d0114644e9030179e4e8f5458a29ff44 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 28 Nov 2022 22:16:30 -0800 Subject: [PATCH 3/5] Add size to host_runtime_contract --- .../HostApiInvokerApp/HostRuntimeContract.cs | 3 ++- src/native/corehost/host_runtime_contract.h | 13 +++++++------ .../corehost/hostpolicy/hostpolicy_context.cpp | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs index 918ead63f54c4..975a636576b06 100644 --- a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs @@ -12,10 +12,11 @@ public static unsafe class HostRuntimeContract { internal struct host_runtime_contract { + public nint size; public void* context; + public delegate* unmanaged[Stdcall] get_runtime_property; public IntPtr bundle_probe; public IntPtr pinvoke_override; - public delegate* unmanaged[Stdcall] get_runtime_property; } private static host_runtime_contract GetContract() diff --git a/src/native/corehost/host_runtime_contract.h b/src/native/corehost/host_runtime_contract.h index ebdb5ab842766..7017f960b4c69 100644 --- a/src/native/corehost/host_runtime_contract.h +++ b/src/native/corehost/host_runtime_contract.h @@ -19,8 +19,15 @@ struct host_runtime_contract { + size_t size; void* context; + size_t(HOST_CONTRACT_CALLTYPE* get_runtime_property)( + const char* key, + char* value_buffer, + size_t value_buffer_size, + void* contract_context); + bool(HOST_CONTRACT_CALLTYPE* bundle_probe)( const char* path, int64_t* offset, @@ -30,12 +37,6 @@ struct host_runtime_contract const void* (HOST_CONTRACT_CALLTYPE* pinvoke_override)( const char* library_name, const char* entry_point_name); - - size_t(HOST_CONTRACT_CALLTYPE* get_runtime_property)( - const char* key, - char* value_buffer, - size_t value_buffer_size, - void* contract_context); }; #endif // __HOST_RUNTIME_CONTRACT_H__ diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index 27f94f4e1c67b..2c4c6d67d65fa 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -351,7 +351,7 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a #endif { - host_contract = { this }; + host_contract = { sizeof(host_runtime_contract), this }; if (bundle::info_t::is_single_file_bundle()) { host_contract.bundle_probe = &bundle_probe; From a9d370283162c11bdaf9987b8043525f0bed5662 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 29 Nov 2022 22:24:53 -0800 Subject: [PATCH 4/5] Clean-up --- src/coreclr/dlls/mscoree/exports.cpp | 14 +++++++---- src/coreclr/vm/corhost.cpp | 10 ++++---- src/coreclr/vm/hostinformation.cpp | 4 ---- .../HostActivation.Tests/NativeHostApis.cs | 3 +-- src/native/corehost/host_runtime_contract.h | 24 +++++++++++++++---- .../hostpolicy/hostpolicy_context.cpp | 4 ---- 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/coreclr/dlls/mscoree/exports.cpp b/src/coreclr/dlls/mscoree/exports.cpp index 7cf1f629a6027..1c64d4ead2efb 100644 --- a/src/coreclr/dlls/mscoree/exports.cpp +++ b/src/coreclr/dlls/mscoree/exports.cpp @@ -138,20 +138,21 @@ static void ConvertConfigPropertiesToUnicode( propertyKeysW[propertyIndex] = StringToUnicode(propertyKeys[propertyIndex]); propertyValuesW[propertyIndex] = StringToUnicode(propertyValues[propertyIndex]); - if (strcmp(propertyKeys[propertyIndex], "BUNDLE_PROBE") == 0) + if (strcmp(propertyKeys[propertyIndex], HOST_PROPERTY_BUNDLE_PROBE) == 0) { // If this application is a single-file bundle, the bundle-probe callback // is passed in as the value of "BUNDLE_PROBE" property (encoded as a string). if (*bundleProbe == nullptr) *bundleProbe = (BundleProbeFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); } - else if (strcmp(propertyKeys[propertyIndex], "PINVOKE_OVERRIDE") == 0) + else if (strcmp(propertyKeys[propertyIndex], HOST_PROPERTY_PINVOKE_OVERRIDE) == 0) { // If host provides a PInvoke override (typically in a single-file bundle), // the override callback is passed in as the value of "PINVOKE_OVERRIDE" property (encoded as a string). - *pinvokeOverride = (PInvokeOverrideFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); + if (*pinvokeOverride == nullptr) + *pinvokeOverride = (PInvokeOverrideFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); } - else if (strcmp(propertyKeys[propertyIndex], "HOSTPOLICY_EMBEDDED") == 0) + else if (strcmp(propertyKeys[propertyIndex], HOST_PROPERTY_HOSTPOLICY_EMBEDDED) == 0) { // The HOSTPOLICY_EMBEDDED property indicates if the executable has hostpolicy statically linked in *hostPolicyEmbedded = (wcscmp(propertyValuesW[propertyIndex], W("true")) == 0); @@ -163,6 +164,9 @@ static void ConvertConfigPropertiesToUnicode( *hostContract = hostContractLocal; if (hostContractLocal->bundle_probe != nullptr) *bundleProbe = hostContractLocal->bundle_probe; + + if (hostContractLocal->pinvoke_override != nullptr) + *pinvokeOverride = hostContractLocal->pinvoke_override; } } @@ -240,7 +244,7 @@ int coreclr_initialize( HostInformation::SetContract(hostContract); } - if (pinvokeOverride != nullptr && (hostContract == nullptr || hostContract->pinvoke_override == nullptr)) + if (pinvokeOverride != nullptr) { PInvokeOverride::SetPInvokeOverride(pinvokeOverride, PInvokeOverride::Source::RuntimeConfiguration); } diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index 72fe94761c1db..052e6a0480c18 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -38,6 +38,8 @@ #ifndef DACCESS_COMPILE +#include + extern void STDMETHODCALLTYPE EEShutDown(BOOL fIsDllUnloading); //*************************************************************************** @@ -578,22 +580,22 @@ HRESULT CorHost2::CreateAppDomainWithManager( for (int i = 0; i < nProperties; i++) { - if (wcscmp(pPropertyNames[i], W("NATIVE_DLL_SEARCH_DIRECTORIES")) == 0) + if (wcscmp(pPropertyNames[i], _T(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES)) == 0) { pwzNativeDllSearchDirectories = pPropertyValues[i]; } else - if (wcscmp(pPropertyNames[i], W("TRUSTED_PLATFORM_ASSEMBLIES")) == 0) + if (wcscmp(pPropertyNames[i], _T(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES)) == 0) { pwzTrustedPlatformAssemblies = pPropertyValues[i]; } else - if (wcscmp(pPropertyNames[i], W("PLATFORM_RESOURCE_ROOTS")) == 0) + if (wcscmp(pPropertyNames[i], _T(HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS)) == 0) { pwzPlatformResourceRoots = pPropertyValues[i]; } else - if (wcscmp(pPropertyNames[i], W("APP_PATHS")) == 0) + if (wcscmp(pPropertyNames[i], _T(HOST_PROPERTY_APP_PATHS)) == 0) { pwzAppPaths = pPropertyValues[i]; } diff --git a/src/coreclr/vm/hostinformation.cpp b/src/coreclr/vm/hostinformation.cpp index 6ad189290b71c..b440f6f2168ab 100644 --- a/src/coreclr/vm/hostinformation.cpp +++ b/src/coreclr/vm/hostinformation.cpp @@ -3,7 +3,6 @@ #include "common.h" #include "hostinformation.h" -#include "pinvokeoverride.h" namespace { @@ -14,9 +13,6 @@ void HostInformation::SetContract(_In_ host_runtime_contract* hostContract) { _ASSERTE(s_hostContract == nullptr); s_hostContract = hostContract; - - if (s_hostContract->pinvoke_override != nullptr) - PInvokeOverride::SetPInvokeOverride(s_hostContract->pinvoke_override, PInvokeOverride::Source::RuntimeConfiguration); } bool HostInformation::GetProperty(_In_z_ const char* name, SString& value) diff --git a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs index 1f1380421e258..1649b2afeb33f 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs @@ -478,13 +478,12 @@ public void HostRuntimeContract_get_runtime_property() { var fixture = sharedTestState.HostApiInvokerAppFixture; - fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, "host_runtime_contract.get_runtime_property", "APP_CONTEXT_BASE_DIRECTORY", "ENTRY_ASSEMBLY_NAME", "DOES_NOT_EXIST") + fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, "host_runtime_contract.get_runtime_property", "APP_CONTEXT_BASE_DIRECTORY", "DOES_NOT_EXIST") .CaptureStdOut() .CaptureStdErr() .Execute() .Should().Pass() .And.HaveStdOutContaining($"APP_CONTEXT_BASE_DIRECTORY = {Path.GetDirectoryName(fixture.TestProject.AppDll)}") - .And.HaveStdOutContaining($"ENTRY_ASSEMBLY_NAME = {fixture.TestProject.AssemblyName}") .And.HaveStdOutContaining($"DOES_NOT_EXIST = "); } diff --git a/src/native/corehost/host_runtime_contract.h b/src/native/corehost/host_runtime_contract.h index 7017f960b4c69..919766eae8fa3 100644 --- a/src/native/corehost/host_runtime_contract.h +++ b/src/native/corehost/host_runtime_contract.h @@ -15,25 +15,39 @@ // Known host property names #define HOST_PROPERTY_RUNTIME_CONTRACT "HOST_RUNTIME_CONTRACT" -#define HOST_PROPERTY_ENTRY_ASSEMBLY_NAME "ENTRY_ASSEMBLY_NAME" +#define HOST_PROPERTY_APP_PATHS "APP_PATHS" +#define HOST_PROPERTY_BUNDLE_PROBE "BUNDLE_PROBE" +#define HOST_PROPERTY_HOSTPOLICY_EMBEDDED "HOSTPOLICY_EMBEDDED" +#define HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES "NATIVE_DLL_SEARCH_DIRECTORIES" +#define HOST_PROPERTY_PINVOKE_OVERRIDE "PINVOKE_OVERRIDE" +#define HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS "PLATFORM_RESOURCE_ROOTS" +#define HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES "TRUSTED_PLATFORM_ASSEMBLIES" struct host_runtime_contract { size_t size; + + // Context for the contract. Pass to functions taking a contract context. void* context; + // Get the value of a runtime property. + // Returns the length of the property including a terminating null or -1 if not found. size_t(HOST_CONTRACT_CALLTYPE* get_runtime_property)( const char* key, - char* value_buffer, + /*out*/ char* value_buffer, size_t value_buffer_size, void* contract_context); + // Probe an app bundle for `path`. Sets its location (`offset`, `size`) in the bundle if found. + // Returns true if found, false otherwise. bool(HOST_CONTRACT_CALLTYPE* bundle_probe)( const char* path, - int64_t* offset, - int64_t* size, - int64_t* compressedSize); + /*out*/ int64_t* offset, + /*out*/ int64_t* size, + /*out*/ int64_t* compressedSize); + // Get the function overriding the specified p/invoke (`library_name`, `entry_point_name`). + // Returns a pointer to the function if the p/invoke is overridden, nullptr otherwise. const void* (HOST_CONTRACT_CALLTYPE* pinvoke_override)( const char* library_name, const char* entry_point_name); diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index 2c4c6d67d65fa..7d27951ed226f 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -113,10 +113,6 @@ namespace void* contract_context) { hostpolicy_context_t* context = static_cast(contract_context); - if (::strcmp(key, HOST_PROPERTY_ENTRY_ASSEMBLY_NAME) == 0) - { - return pal::pal_utf8string(get_filename_without_ext(context->application), value_buffer, value_buffer_size); - } pal::string_t key_str; if (pal::clr_palstring(key, &key_str)) From 4a887ee633a66e9c8d7213e1b639afe2ed68e525 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 5 Dec 2022 13:33:45 -0800 Subject: [PATCH 5/5] PR feedback --- src/coreclr/dlls/mscoree/exports.cpp | 7 +++++++ .../TestProjects/HostApiInvokerApp/HostRuntimeContract.cs | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/coreclr/dlls/mscoree/exports.cpp b/src/coreclr/dlls/mscoree/exports.cpp index 1c64d4ead2efb..1d9cc711e06e2 100644 --- a/src/coreclr/dlls/mscoree/exports.cpp +++ b/src/coreclr/dlls/mscoree/exports.cpp @@ -142,6 +142,8 @@ static void ConvertConfigPropertiesToUnicode( { // If this application is a single-file bundle, the bundle-probe callback // is passed in as the value of "BUNDLE_PROBE" property (encoded as a string). + // The function in HOST_RUNTIME_CONTRACT is given priority over this property, + // so we only set the bundle probe if it has not already been set. if (*bundleProbe == nullptr) *bundleProbe = (BundleProbeFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); } @@ -149,6 +151,8 @@ static void ConvertConfigPropertiesToUnicode( { // If host provides a PInvoke override (typically in a single-file bundle), // the override callback is passed in as the value of "PINVOKE_OVERRIDE" property (encoded as a string). + // The function in HOST_RUNTIME_CONTRACT is given priority over this property, + // so we only set the p/invoke override if it has not already been set. if (*pinvokeOverride == nullptr) *pinvokeOverride = (PInvokeOverrideFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); } @@ -162,6 +166,9 @@ static void ConvertConfigPropertiesToUnicode( // Host contract is passed in as the value of HOST_RUNTIME_CONTRACT property (encoded as a string). host_runtime_contract* hostContractLocal = (host_runtime_contract*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); *hostContract = hostContractLocal; + + // Functions in HOST_RUNTIME_CONTRACT have priority over the individual properties + // for callbacks, so we set them as long as the contract has a non-null function. if (hostContractLocal->bundle_probe != nullptr) *bundleProbe = hostContractLocal->bundle_probe; diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs index 975a636576b06..a2a4d6161acea 100644 --- a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs @@ -26,6 +26,9 @@ private static host_runtime_contract GetContract() throw new Exception("HOST_RUNTIME_CONTRACT not found"); host_runtime_contract* contract = (host_runtime_contract*)Convert.ToUInt64(contractString, 16); + if (contract->size != sizeof(host_runtime_contract)) + throw new Exception($"Unexpected contract size {contract->size}. Expected: {sizeof(host_runtime_contract)}"); + return *contract; }