Skip to content

Commit

Permalink
Enable calling get_runtime_delegate from app context (dotnet#37473)
Browse files Browse the repository at this point in the history
* Add ability for hostfxr_get_runtime_delegate to block on invalid delegate types.

This is required so that the runtime isn't loaded if the request has no chance of succeeding.

Fix spelling of initialization_options_t.

Add context_contract_version_set flag to initialization_options_t.

This allows hostpolicy to know whether it can rely on the version field of corehost_context_contract to be valid.
Also always initialize corehost_context_contract to {}.

* Allow app-based context to call hostfxr_get_runtime_delegate.

Currently, it may only request the load_assembly_and_get_function_pointer delegate.

Fix discrepancy in design doc.

Add error code HostApiUnsupportedScenario for blocks that might be removed. This gives the native host better information on why the request failed.

Add tests for running a component from an app context.
  • Loading branch information
rseanhall committed Jun 9, 2020
1 parent 9ce5e48 commit 3b5a51a
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 36 deletions.
2 changes: 2 additions & 0 deletions docs/design/features/host-error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,5 @@ For example if `hostfxr_get_runtime_property_value` is called with the `host_con
* `HostPropertyNotFound` (`0x800080a4`) - property requested by `hostfxr_get_runtime_property_value` doesn't exist.

* `CoreHostIncompatibleConfig` (`0x800080a5`) - Error returned by `hostfxr_initialize_for_runtime_config` if the component being initialized requires framework which is not available or incompatible with the frameworks loaded by the runtime already in the process. For example trying to load a component which requires 3.0 into a process which is already running a 2.0 runtime.

* `HostApiUnsupportedScenario` (`0x800080a6`) - Error returned by `hostfxr_get_runtime_delegate` when `hostfxr` doesn't currently support requesting the given delegate type using the given context.
2 changes: 1 addition & 1 deletion docs/design/features/native-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ Starts the runtime and returns a function pointer to specified functionality of
* `delegate` - when successful, the native function pointer to the requested runtime functionality.
In .NET Core 3.0 the function only works if `hostfxr_initialize_for_runtime_config` was used to initialize the host context.
In .NET 5 the function also works if `hostfxr_initialize_for_dotnet_command_line` was used to initialize the host context. Also for .NET 5 it will only be allowed to request `hdt_get_function_pointer` on a context initialized via `hostfxr_initialize_for_dotnet_command_line`, all other runtime delegates will not be supported in this case.
In .NET 5 the function also works if `hostfxr_initialize_for_dotnet_command_line` was used to initialize the host context. Also for .NET 5 it will only be allowed to request `hdt_load_assembly_and_get_function_pointer` or `hdt_get_function_pointer` on a context initialized via `hostfxr_initialize_for_dotnet_command_line`, all other runtime delegates will not be supported in this case.
### Cleanup
Expand Down
18 changes: 13 additions & 5 deletions src/installer/corehost/cli/corehost_context_contract.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
#include "hostpolicy.h"
#include <pal.h>

enum intialization_options_t : int32_t
enum initialization_options_t : uint32_t
{
none = 0x0,
wait_for_initialized = 0x1, // Wait until initialization through a different request is completed
get_contract = 0x2, // Get the contract for the initialized hostpolicy
wait_for_initialized = 0x1, // Wait until initialization through a different request is completed
get_contract = 0x2, // Get the contract for the initialized hostpolicy
context_contract_version_set = 0x80000000, // The version field has been set in the corehost_context_contract
// on input indicating the maximum size of the buffer which can be filled
};

// Delegates for these types will have the stdcall calling convention unless otherwise specified
Expand All @@ -25,7 +27,9 @@ enum class coreclr_delegate_type
winrt_activation,
com_register,
com_unregister,
load_assembly_and_get_function_pointer
load_assembly_and_get_function_pointer,

__last, // Sentinel value for determining the last known delegate type
};

#pragma pack(push, _HOST_INTERFACE_PACK)
Expand All @@ -38,6 +42,7 @@ struct corehost_initialize_request_t
static_assert(offsetof(corehost_initialize_request_t, version) == 0 * sizeof(size_t), "Struct offset breaks backwards compatibility");
static_assert(offsetof(corehost_initialize_request_t, config_keys) == 1 * sizeof(size_t), "Struct offset breaks backwards compatibility");
static_assert(offsetof(corehost_initialize_request_t, config_values) == 3 * sizeof(size_t), "Struct offset breaks backwards compatibility");
static_assert(sizeof(corehost_initialize_request_t) == 5 * sizeof(size_t), "Did you add static asserts for the newly added fields?");

struct corehost_context_contract
{
Expand All @@ -59,6 +64,7 @@ struct corehost_context_contract
int (HOSTPOLICY_CALLTYPE *get_runtime_delegate)(
coreclr_delegate_type type,
/*out*/ void **delegate);
size_t last_known_delegate_type; // Added in 5.0
};
static_assert(offsetof(corehost_context_contract, version) == 0 * sizeof(size_t), "Struct offset breaks backwards compatibility");
static_assert(offsetof(corehost_context_contract, get_property_value) == 1 * sizeof(size_t), "Struct offset breaks backwards compatibility");
Expand All @@ -67,6 +73,8 @@ static_assert(offsetof(corehost_context_contract, get_properties) == 3 * sizeof(
static_assert(offsetof(corehost_context_contract, load_runtime) == 4 * sizeof(size_t), "Struct offset breaks backwards compatibility");
static_assert(offsetof(corehost_context_contract, run_app) == 5 * sizeof(size_t), "Struct offset breaks backwards compatibility");
static_assert(offsetof(corehost_context_contract, get_runtime_delegate) == 6 * sizeof(size_t), "Struct offset breaks backwards compatibility");
static_assert(offsetof(corehost_context_contract, last_known_delegate_type) == 7 * sizeof(size_t), "Struct offset breaks backwards compatibility");
static_assert(sizeof(corehost_context_contract) == 8 * sizeof(size_t), "Did you add static asserts for the newly added fields?");
#pragma pack(pop)

#endif // __COREHOST_CONTEXT_CONTRACT_H__
#endif // __COREHOST_CONTEXT_CONTRACT_H__
38 changes: 31 additions & 7 deletions src/installer/corehost/cli/fxr/fx_muxer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ int fx_muxer_t::initialize_for_app(
}

std::unique_ptr<host_context_t> context;
rc = initialize_context(hostpolicy_dir, *init, intialization_options_t::none, context);
rc = initialize_context(hostpolicy_dir, *init, initialization_options_t::none, context);
if (rc != StatusCode::Success)
{
trace::error(_X("Failed to initialize context for app: %s. Error code: 0x%x"), host_info.app_path.c_str(), rc);
Expand All @@ -756,7 +756,7 @@ int fx_muxer_t::initialize_for_runtime_config(
const pal::char_t *runtime_config_path,
hostfxr_handle *host_context_handle)
{
int32_t initialization_options = intialization_options_t::none;
uint32_t initialization_options = initialization_options_t::none;
const host_context_t *existing_context;
{
std::unique_lock<std::mutex> lock{ g_context_lock };
Expand All @@ -773,7 +773,7 @@ int fx_muxer_t::initialize_for_runtime_config(
}
else if (existing_context->type == host_context_type::empty)
{
initialization_options |= intialization_options_t::wait_for_initialized;
initialization_options |= initialization_options_t::wait_for_initialized;
}
}

Expand Down Expand Up @@ -870,8 +870,30 @@ int fx_muxer_t::run_app(host_context_t *context)

int fx_muxer_t::get_runtime_delegate(host_context_t *context, coreclr_delegate_type type, void **delegate)
{
if (context->is_app)
return StatusCode::InvalidArgFailure;
switch (type)
{
case coreclr_delegate_type::com_activation:
case coreclr_delegate_type::load_in_memory_assembly:
case coreclr_delegate_type::winrt_activation:
case coreclr_delegate_type::com_register:
case coreclr_delegate_type::com_unregister:
if (context->is_app)
return StatusCode::HostApiUnsupportedScenario;
break;
default:
// Always allowed
break;
}

// last_known_delegate_type was added in 5.0, so old versions won't set it and it will be zero.
// But when get_runtime_delegate was originally implemented in 3.0,
// it supported up to load_assembly_and_get_function_pointer so we check that first.
if (type > coreclr_delegate_type::load_assembly_and_get_function_pointer
&& (size_t)type > context->hostpolicy_context_contract.last_known_delegate_type)
{
trace::error(_X("The requested delegate type is not available in the target framework."));
return StatusCode::HostApiUnsupportedVersion;
}

const corehost_context_contract &contract = context->hostpolicy_context_contract;
{
Expand Down Expand Up @@ -908,10 +930,12 @@ const host_context_t* fx_muxer_t::get_active_host_context()
return nullptr;
}

corehost_context_contract hostpolicy_context_contract;
corehost_context_contract hostpolicy_context_contract = {};
{
hostpolicy_context_contract.version = sizeof(corehost_context_contract);
propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer);
int rc = hostpolicy_contract.initialize(nullptr, intialization_options_t::get_contract, &hostpolicy_context_contract);
uint32_t options = initialization_options_t::get_contract | initialization_options_t::context_contract_version_set;
int rc = hostpolicy_contract.initialize(nullptr, options, &hostpolicy_context_contract);
if (rc != StatusCode::Success)
{
trace::error(_X("Failed to get contract for existing initialized hostpolicy: 0x%x"), rc);
Expand Down
14 changes: 8 additions & 6 deletions src/installer/corehost/cli/fxr/host_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace
const hostpolicy_contract_t &hostpolicy_contract,
const host_interface_t *host_interface,
const corehost_initialize_request_t *init_request,
int32_t initialization_options,
uint32_t initialization_options,
bool already_loaded,
/*out*/ corehost_context_contract *hostpolicy_context_contract)
{
Expand All @@ -35,6 +35,8 @@ namespace

if (rc == StatusCode::Success)
{
initialization_options |= initialization_options_t::context_contract_version_set;
hostpolicy_context_contract->version = sizeof(corehost_context_contract);
rc = hostpolicy_contract.initialize(init_request, initialization_options, hostpolicy_context_contract);
}
}
Expand All @@ -46,11 +48,11 @@ namespace
int host_context_t::create(
const hostpolicy_contract_t &hostpolicy_contract,
corehost_init_t &init,
int32_t initialization_options,
uint32_t initialization_options,
/*out*/ std::unique_ptr<host_context_t> &context)
{
const host_interface_t &host_interface = init.get_host_init_data();
corehost_context_contract hostpolicy_context_contract;
corehost_context_contract hostpolicy_context_contract = {};
int rc = create_context_common(hostpolicy_contract, &host_interface, nullptr, initialization_options, /*already_loaded*/ false, &hostpolicy_context_contract);
if (rc == StatusCode::Success)
{
Expand All @@ -65,7 +67,7 @@ int host_context_t::create(
int host_context_t::create_secondary(
const hostpolicy_contract_t &hostpolicy_contract,
std::unordered_map<pal::string_t, pal::string_t> &config_properties,
int32_t initialization_options,
uint32_t initialization_options,
/*out*/ std::unique_ptr<host_context_t> &context)
{
std::vector<const pal::char_t*> config_keys;
Expand All @@ -83,7 +85,7 @@ int host_context_t::create_secondary(
init_request.config_values.len = config_values.size();
init_request.config_values.arr = config_values.data();

corehost_context_contract hostpolicy_context_contract;
corehost_context_contract hostpolicy_context_contract = {};
int rc = create_context_common(hostpolicy_contract, nullptr, &init_request, initialization_options, /*already_loaded*/ true, &hostpolicy_context_contract);
if (STATUS_CODE_SUCCEEDED(rc))
{
Expand Down Expand Up @@ -141,4 +143,4 @@ void host_context_t::initialize_frameworks(const corehost_init_t& init)
void host_context_t::close()
{
marker = closed_host_context_marker;
}
}
6 changes: 3 additions & 3 deletions src/installer/corehost/cli/fxr/host_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ struct host_context_t
static int create(
const hostpolicy_contract_t &hostpolicy_contract,
corehost_init_t &init,
int32_t initialization_options,
uint32_t initialization_options,
/*out*/ std::unique_ptr<host_context_t> &context);
static int create_secondary(
const hostpolicy_contract_t &hostpolicy_contract,
std::unordered_map<pal::string_t, pal::string_t> &config_properties,
int32_t initialization_options,
uint32_t initialization_options,
/*out*/ std::unique_ptr<host_context_t> &context);
static host_context_t* from_handle(const hostfxr_handle handle, bool allow_invalid_type = false);

Expand Down Expand Up @@ -67,4 +67,4 @@ struct host_context_t
void close();
};

#endif // __HOST_CONTEXT_H__
#endif // __HOST_CONTEXT_H__
12 changes: 10 additions & 2 deletions src/installer/corehost/cli/fxr/hostfxr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,11 @@ namespace
// Return value:
// The error code result.
//
// The host_context_handle must have been initialized using hostfxr_initialize_for_runtime_config.
// If the host_context_handle was initialized using hostfxr_initialize_for_runtime_config,
// then all delegate types are supported.
// If the host_context_handle was initialized using hostfxr_initialize_for_dotnet_command_line,
// then only the following delegate types are currently supported:
// hdt_load_assembly_and_get_function_pointer
//
SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_get_runtime_delegate(
const hostfxr_handle host_context_handle,
Expand All @@ -654,7 +658,11 @@ SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_get_runtime_delegate(
if (context == nullptr)
return StatusCode::InvalidArgFailure;

return fx_muxer_t::get_runtime_delegate(context, hostfxr_delegate_to_coreclr_delegate(type), delegate);
coreclr_delegate_type delegate_type = hostfxr_delegate_to_coreclr_delegate(type);
if (delegate_type == coreclr_delegate_type::invalid)
return StatusCode::InvalidArgFailure;

return fx_muxer_t::get_runtime_delegate(context, delegate_type, delegate);
}

//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,17 +190,21 @@ int hostpolicy_resolver::load(
}

// Obtain entrypoint symbols
g_hostpolicy_contract.corehost_main = reinterpret_cast<corehost_main_fn>(pal::get_symbol(g_hostpolicy, "corehost_main"));
g_hostpolicy_contract.load = reinterpret_cast<corehost_load_fn>(pal::get_symbol(g_hostpolicy, "corehost_load"));
g_hostpolicy_contract.unload = reinterpret_cast<corehost_unload_fn>(pal::get_symbol(g_hostpolicy, "corehost_unload"));
if ((g_hostpolicy_contract.load == nullptr) || (g_hostpolicy_contract.unload == nullptr))
return StatusCode::CoreHostEntryPointFailure;

g_hostpolicy_contract.corehost_main_with_output_buffer = reinterpret_cast<corehost_main_with_output_buffer_fn>(pal::get_symbol(g_hostpolicy, "corehost_main_with_output_buffer"));

// It's possible to not have corehost_main_with_output_buffer.
// This was introduced in 2.1, so 2.0 hostpolicy would not have the exports.
// Callers are responsible for checking that the function pointer is not null before using it.

g_hostpolicy_contract.set_error_writer = reinterpret_cast<corehost_set_error_writer_fn>(pal::get_symbol(g_hostpolicy, "corehost_set_error_writer"));
g_hostpolicy_contract.initialize = reinterpret_cast<corehost_initialize_fn>(pal::get_symbol(g_hostpolicy, "corehost_initialize"));

g_hostpolicy_contract.corehost_main = reinterpret_cast<corehost_main_fn>(pal::get_symbol(g_hostpolicy, "corehost_main"));
g_hostpolicy_contract.corehost_main_with_output_buffer = reinterpret_cast<corehost_main_with_output_buffer_fn>(pal::get_symbol(g_hostpolicy, "corehost_main_with_output_buffer"));

// It's possible to not have corehost_set_error_writer and corehost_initialize. These were
// introduced in 3.0, so 2.0 hostpolicy would not have the exports. In this case, we will
// not propagate the error writer and errors will still be reported to stderr. Callers are
Expand Down
17 changes: 14 additions & 3 deletions src/installer/corehost/cli/hostpolicy/hostpolicy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -630,13 +630,14 @@ namespace
// initializations. In the case of Success_DifferentRuntimeProperties, it is left to the consumer to verify that
// the difference in properties is acceptable.
//
SHARED_API int HOSTPOLICY_CALLTYPE corehost_initialize(const corehost_initialize_request_t *init_request, int32_t options, /*out*/ corehost_context_contract *context_contract)
SHARED_API int HOSTPOLICY_CALLTYPE corehost_initialize(const corehost_initialize_request_t *init_request, uint32_t options, /*out*/ corehost_context_contract *context_contract)
{
if (context_contract == nullptr)
return StatusCode::InvalidArgFailure;

bool wait_for_initialized = (options & intialization_options_t::wait_for_initialized) != 0;
bool get_contract = (options & intialization_options_t::get_contract) != 0;
bool version_set = (options & initialization_options_t::context_contract_version_set) != 0;
bool wait_for_initialized = (options & initialization_options_t::wait_for_initialized) != 0;
bool get_contract = (options & initialization_options_t::get_contract) != 0;
if (wait_for_initialized && get_contract)
{
trace::error(_X("Specifying both initialization options for wait_for_initialized and get_contract is not allowed"));
Expand Down Expand Up @@ -744,6 +745,8 @@ SHARED_API int HOSTPOLICY_CALLTYPE corehost_initialize(const corehost_initialize
rc = StatusCode::Success_DifferentRuntimeProperties;
}

// If version wasn't set, then it would have the original size of corehost_context_contract, which is 7 * sizeof(size_t).
size_t version_lo = version_set ? context_contract->version : 7 * sizeof(size_t);
context_contract->version = sizeof(corehost_context_contract);
context_contract->get_property_value = get_property;
context_contract->set_property_value = set_property;
Expand All @@ -752,6 +755,14 @@ SHARED_API int HOSTPOLICY_CALLTYPE corehost_initialize(const corehost_initialize
context_contract->run_app = run_app;
context_contract->get_runtime_delegate = get_delegate;

// An old hostfxr may not have provided enough space for these fields.
// The version_lo (sizeof) the old hostfxr saw at build time will be
// smaller and we should not attempt to write the fields in that case.
if (version_lo >= offsetof(corehost_context_contract, last_known_delegate_type) + sizeof(context_contract->last_known_delegate_type))
{
context_contract->last_known_delegate_type = (size_t)coreclr_delegate_type::__last - 1;
}

return rc;
}

Expand Down
10 changes: 5 additions & 5 deletions src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ bool hostpolicy_init_t::init(host_interface_t* input, hostpolicy_init_t* init)

trace::verbose(_X("Reading from host interface version: [0x%04x:%d] to initialize policy version: [0x%04x:%d]"), input->version_hi, input->version_lo, HOST_INTERFACE_LAYOUT_VERSION_HI, HOST_INTERFACE_LAYOUT_VERSION_LO);

//This check is to ensure is an old hostfxr can still load new hostpolicy.
//We should not read garbage due to potentially shorter struct size
// This check is to ensure is an old hostfxr can still load new hostpolicy.
// We should not read garbage due to potentially shorter struct size

pal::string_t fx_requested_ver;

Expand All @@ -51,9 +51,9 @@ bool hostpolicy_init_t::init(host_interface_t* input, hostpolicy_init_t* init)
offsetof(host_interface_t, host_mode) + sizeof(input->host_mode));
}

//An old hostfxr may not provide these fields.
//The version_lo (sizeof) the old hostfxr saw at build time will be
//smaller and we should not attempt to read the fields in that case.
// An old hostfxr may not provide these fields.
// The version_lo (sizeof) the old hostfxr saw at build time will be
// smaller and we should not attempt to read the fields in that case.
if (input->version_lo >= offsetof(host_interface_t, tfm) + sizeof(input->tfm))
{
init->tfm = input->tfm;
Expand Down
Loading

0 comments on commit 3b5a51a

Please sign in to comment.