Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mechanism for runtime to query host for information #78798

Merged
merged 5 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
vitek-karas marked this conversation as resolved.
Show resolved Hide resolved
*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;
vitek-karas marked this conversation as resolved.
Show resolved Hide resolved
}

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