Skip to content

Commit

Permalink
Add mechanism for runtime to query host for information (#78798)
Browse files Browse the repository at this point in the history
  • Loading branch information
elinor-fung committed Dec 6, 2022
1 parent 360d405 commit 45f5e85
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 29 deletions.
44 changes: 37 additions & 7 deletions src/coreclr/dlls/mscoree/exports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#endif // FEATURE_GDBJIT
#include "bundle.h"
#include "pinvokeoverride.h"
#include <hostinformation.h>
#include <corehost/host_runtime_contract.h>

#define ASSERTE_ALL_BUILDS(expr) _ASSERTE_ALL_BUILDS((expr))

Expand Down Expand Up @@ -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);
Expand All @@ -135,23 +138,43 @@ 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).
*bundleProbe = (BundleProbeFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0);
// 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);
}
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);
// 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);
}
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);
}
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;

// 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;

if (hostContractLocal->pinvoke_override != nullptr)
*pinvokeOverride = hostContractLocal->pinvoke_override;
}
}

*propertyKeysWRef = propertyKeysW;
Expand Down Expand Up @@ -196,6 +219,7 @@ int coreclr_initialize(
BundleProbeFn* bundleProbe = nullptr;
bool hostPolicyEmbedded = false;
PInvokeOverrideFn* pinvokeOverride = nullptr;
host_runtime_contract* hostContract = nullptr;

ConvertConfigPropertiesToUnicode(
propertyKeys,
Expand All @@ -205,7 +229,8 @@ int coreclr_initialize(
&propertyValuesW,
&bundleProbe,
&pinvokeOverride,
&hostPolicyEmbedded);
&hostPolicyEmbedded,
&hostContract);

#ifdef TARGET_UNIX
DWORD error = PAL_InitializeCoreCLR(exePath, g_coreclr_embedded);
Expand All @@ -221,6 +246,11 @@ int coreclr_initialize(

g_hostpolicy_embedded = hostPolicyEmbedded;

if (hostContract != nullptr)
{
HostInformation::SetContract(hostContract);
}

if (pinvokeOverride != nullptr)
{
PInvokeOverride::SetPInvokeOverride(pinvokeOverride, PInvokeOverride::Source::RuntimeConfiguration);
Expand Down
16 changes: 16 additions & 0 deletions src/coreclr/inc/hostinformation.h
Original file line number Diff line number Diff line change
@@ -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 <corehost/host_runtime_contract.h>

class HostInformation
{
public:
static void SetContract(_In_ host_runtime_contract* hostContract);
static bool GetProperty(_In_z_ const char* name, SString& value);
};

#endif // _HOSTINFORMATION_H_
1 change: 1 addition & 0 deletions src/coreclr/vm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ set(VM_SOURCES_WKS
genanalysis.cpp
genmeth.cpp
hosting.cpp
hostinformation.cpp
ilmarshalers.cpp
interopconverter.cpp
interoputil.cpp
Expand Down
10 changes: 6 additions & 4 deletions src/coreclr/vm/corhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@

#ifndef DACCESS_COMPILE

#include <corehost/host_runtime_contract.h>

extern void STDMETHODCALLTYPE EEShutDown(BOOL fIsDllUnloading);

//***************************************************************************
Expand Down Expand Up @@ -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];
}
Expand Down
42 changes: 42 additions & 0 deletions src/coreclr/vm/hostinformation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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"

namespace
{
host_runtime_contract* s_hostContract = nullptr;
}

void HostInformation::SetContract(_In_ host_runtime_contract* hostContract)
{
_ASSERTE(s_hostContract == nullptr);
s_hostContract = hostContract;
}

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<COUNT_T>(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<COUNT_T>(len));
lenActual = s_hostContract->get_runtime_property(name, dest, len, s_hostContract->context);
value.CloseBuffer();

return lenActual > 0 && lenActual <= len;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
<OutputType>Exe</OutputType>
<RuntimeFrameworkVersion>$(MNAVersion)</RuntimeFrameworkVersion>
<DefineConstants Condition="'$(OS)' == 'Windows_NT'">WINDOWS;$(DefineConstants)</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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 nint size;
public void* context;
public delegate* unmanaged[Stdcall]<byte*, byte*, nint, void*, nint> get_runtime_property;
public IntPtr bundle_probe;
public IntPtr pinvoke_override;
}

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);
if (contract->size != sizeof(host_runtime_contract))
throw new Exception($"Unexpected contract size {contract->size}. Expected: {sizeof(host_runtime_contract)}");

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 ? "<none>" : value)}");
}

static string GetProperty(string name, host_runtime_contract contract)
{
Span<byte> 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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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}");
}
}
}
14 changes: 14 additions & 0 deletions src/installer/tests/HostActivation.Tests/NativeHostApis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,20 @@ 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", "DOES_NOT_EXIST")
.CaptureStdOut()
.CaptureStdErr()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining($"APP_CONTEXT_BASE_DIRECTORY = {Path.GetDirectoryName(fixture.TestProject.AppDll)}")
.And.HaveStdOutContaining($"DOES_NOT_EXIST = <none>");
}

public class SharedTestState : IDisposable
{
public TestProjectFixture HostApiInvokerAppFixture { get; }
Expand Down
Loading

0 comments on commit 45f5e85

Please sign in to comment.