Skip to content

Commit

Permalink
[mono] Add support for UnmanagedCallersOnlyAttribute (#38728)
Browse files Browse the repository at this point in the history
* [marshal] Add mono_method_has_unmanaged_callers_only_attribute

* [marshal] Allow calls to mono_marshal_get_managed_wrapped without a delegate class

   In that case, create a wrapper based on the signature of the method itself.

* [aot] Allow decode_method_ref to decode NATIVE_TO_MANAGED wrappers without a delegate class

   Bump the AOT file format

* [interp] ldftn will return a native-to-managed wrapper to UnmanagedCallersOnly methods

* aot: emit byte when we don't expect a class

* jit: create wrapper creation for ldftn in method-to-ir

   Do it at IR generation of the caller, not every time the ldftn is executed

* jit: don't create a jump trampoline for ldftn of a native-to-managed wrapper

   The wrapper might be called from a thread that's not attached to the runtime, and the jump trampoline will look at TLS vars that are not initialized

* interp: transform LDFTN into LDC of a create_method_pointer for UnmanagedCallersOnly method

* marshal: throw invalid program exception for instance and generic methods

* Emit IPE throw instead of aborting JIT or interp compilation for bad UnmanagedCallersOnly methods

   Instead of throwing while JITing (or transforming), throw when the LDFTN is executed.

* disallow delegate constructor calls on UnmanagedCallersOnly methods

* throw IPE if UnmanagedCallersOnly method has non-blittable args

* disallow DllImport and UnmanagedCallersOnly together

   throw NotSupportedException

Co-authored-by: Ryan Lucia <ryan@luciaonline.net>
  • Loading branch information
lambdageek and CoffeeFlux committed Jul 10, 2020
1 parent 838fcff commit 1e3c959
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 14 deletions.
3 changes: 3 additions & 0 deletions src/mono/mono/metadata/class-internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,9 @@ mono_method_is_constructor (MonoMethod *method);
gboolean
mono_class_has_default_constructor (MonoClass *klass, gboolean public_only);

gboolean
mono_method_has_unmanaged_callers_only_attribute (MonoMethod *method);

// There are many ways to do on-demand initialization.
// Some allow multiple concurrent initializations. Some do not.
// Some allow multiple concurrent writes to the global. Some do not.
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono/metadata/jit-icall-reg.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ MONO_JIT_ICALL (mono_threads_state_poll) \
MONO_JIT_ICALL (mono_throw_exception) \
MONO_JIT_ICALL (mono_throw_method_access) \
MONO_JIT_ICALL (mono_throw_bad_image) \
MONO_JIT_ICALL (mono_throw_not_supported) \
MONO_JIT_ICALL (mono_throw_invalid_program) \
MONO_JIT_ICALL (mono_trace_enter_method) \
MONO_JIT_ICALL (mono_trace_leave_method) \
MONO_JIT_ICALL (mono_trace_tail_method) \
Expand Down
131 changes: 123 additions & 8 deletions src/mono/mono/metadata/marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_function_pointer_attribute,

#ifdef ENABLE_NETCORE
static GENERATE_TRY_GET_CLASS_WITH_CACHE (suppress_gc_transition_attribute, "System.Runtime.InteropServices", "SuppressGCTransitionAttribute")
static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callers_only_attribute, "System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute")
#endif

static MonoImage*
Expand Down Expand Up @@ -3661,6 +3662,23 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions,

mb->method->save_lmf = 1;

if (G_UNLIKELY (pinvoke && mono_method_has_unmanaged_callers_only_attribute (method))) {
/* emit a wrapper that throws a NotSupportedException */
get_marshal_cb ()->mb_emit_exception (mb, "System", "NotSupportedException", "Method canot be marked with both DllImportAttribute and UnmanagedCallersOnlyAttribute");

info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NONE);
info->d.managed_to_native.method = method;

csig = mono_metadata_signature_dup_full (get_method_image (method), sig);
csig->pinvoke = 0;
res = mono_mb_create_and_cache_full (cache, method, mb, csig,
csig->param_count + 16, info, NULL);
mono_mb_free (mb);

return res;
}


/*
* In AOT mode and embedding scenarios, it is possible that the icall is not
* registered in the runtime doing the AOT compilation.
Expand Down Expand Up @@ -3940,10 +3958,54 @@ emit_managed_wrapper_noilgen (MonoMethodBuilder *mb, MonoMethodSignature *invoke
}
#endif

static gboolean
type_is_blittable (MonoType *type)
{
if (type->byref)
return FALSE;
type = mono_type_get_underlying_type (type);
switch (type->type) {
case MONO_TYPE_I1:
case MONO_TYPE_U1:
case MONO_TYPE_I2:
case MONO_TYPE_U2:
case MONO_TYPE_I4:
case MONO_TYPE_U4:
case MONO_TYPE_I8:
case MONO_TYPE_U8:
case MONO_TYPE_R4:
case MONO_TYPE_R8:
case MONO_TYPE_I:
case MONO_TYPE_U:
case MONO_TYPE_PTR:
case MONO_TYPE_FNPTR:
return TRUE;
default:
return m_class_is_blittable (mono_class_from_mono_type_internal (type));
}
}

static gboolean
method_signature_is_blittable (MonoMethodSignature *sig)
{
if (!type_is_blittable (sig->ret))
return FALSE;

for (int i = 0; i < sig->param_count; ++i) {
MonoType *type = sig->params [i];
if (!type_is_blittable (type))
return FALSE;
}
return TRUE;
}

/**
* mono_marshal_get_managed_wrapper:
* Generates IL code to call managed methods from unmanaged code
* If \p target_handle is \c 0, the wrapper info will be a \c WrapperInfo structure.
*
* If \p delegate_klass is \c NULL, we're creating a wrapper for a function pointer to a method marked with
* UnamangedCallersOnlyAttribute.
*/
MonoMethod *
mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass, MonoGCHandle target_handle, MonoError *error)
Expand Down Expand Up @@ -3975,11 +4037,33 @@ mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass,
if (!target_handle && (res = mono_marshal_find_in_cache (cache, method)))
return res;

invoke = mono_get_delegate_invoke_internal (delegate_klass);
invoke_sig = mono_method_signature_internal (invoke);

mspecs = g_new0 (MonoMarshalSpec*, mono_method_signature_internal (invoke)->param_count + 1);
mono_method_get_marshal_info (invoke, mspecs);
if (G_UNLIKELY (!delegate_klass)) {
/* creating a wrapper for a function pointer with UnmanagedCallersOnlyAttribute */
if (mono_method_has_marshal_info (method)) {
mono_error_set_invalid_program (error, "method %s with UnmanadedCallersOnlyAttribute has marshal specs", mono_method_full_name (method, TRUE));
return NULL;
}
invoke = NULL;
invoke_sig = mono_method_signature_internal (method);
if (invoke_sig->hasthis) {
mono_error_set_invalid_program (error, "method %s with UnamanagedCallersOnlyAttribute is an instance method", mono_method_full_name (method, TRUE));
return NULL;
}
if (method->is_generic || method->is_inflated || mono_class_is_ginst (method->klass)) {
mono_error_set_invalid_program (error, "method %s with UnamangedCallersOnlyAttribute is generic", mono_method_full_name (method, TRUE));
return NULL;
}
if (!method_signature_is_blittable (invoke_sig)) {
mono_error_set_invalid_program (error, "method %s with UnmanagedCallersOnlyAttribute has non-blittable parameters or return type", mono_method_full_name (method, TRUE));
return NULL;
}
mspecs = g_new0 (MonoMarshalSpec*, invoke_sig->param_count + 1);
} else {
invoke = mono_get_delegate_invoke_internal (delegate_klass);
invoke_sig = mono_method_signature_internal (invoke);
mspecs = g_new0 (MonoMarshalSpec*, invoke_sig->param_count + 1);
mono_method_get_marshal_info (invoke, mspecs);
}

sig = mono_method_signature_internal (method);

Expand All @@ -4005,10 +4089,11 @@ mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass,
m.csig = csig;
m.image = get_method_image (method);

mono_marshal_set_callconv_from_modopt (invoke, csig, TRUE);
if (invoke)
mono_marshal_set_callconv_from_modopt (invoke, csig, TRUE);

/* The attribute is only available in Net 2.0 */
if (mono_class_try_get_unmanaged_function_pointer_attribute_class ()) {
if (delegate_klass && mono_class_try_get_unmanaged_function_pointer_attribute_class ()) {
MonoCustomAttrInfo *cinfo;
MonoCustomAttrEntry *attr;

Expand Down Expand Up @@ -4097,7 +4182,7 @@ mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass,
}
mono_mb_free (mb);

for (i = mono_method_signature_internal (invoke)->param_count; i >= 0; i--)
for (i = invoke_sig->param_count; i >= 0; i--)
if (mspecs [i])
mono_metadata_free_marshal_spec (mspecs [i]);
g_free (mspecs);
Expand Down Expand Up @@ -6755,3 +6840,33 @@ get_marshal_cb (void)
}
return &marshal_cb;
}

/**
* mono_method_has_unmanaged_callers_only_attribute:
*
* Returns \c TRUE if \p method has the \c UnmanagedCallersOnlyAttribute
*/
gboolean
mono_method_has_unmanaged_callers_only_attribute (MonoMethod *method)
{
#ifndef ENABLE_NETCORE
return FALSE;
#else
ERROR_DECL (attr_error);
MonoClass *attr_klass = NULL;
attr_klass = mono_class_try_get_unmanaged_callers_only_attribute_class ();
if (!attr_klass)
return FALSE;
MonoCustomAttrInfo *cinfo;
cinfo = mono_custom_attrs_from_method_checked (method, attr_error);
if (!is_ok (attr_error) || !cinfo) {
mono_error_cleanup (attr_error);
return FALSE;
}
gboolean result;
result = mono_custom_attrs_has_attr (cinfo, attr_klass);
if (!cinfo->cached)
mono_custom_attrs_free (cinfo);
return result;
#endif
}
8 changes: 7 additions & 1 deletion src/mono/mono/mini/aot-compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -3761,7 +3761,13 @@ encode_method_ref (MonoAotCompile *acfg, MonoMethod *method, guint8 *buf, guint8
case MONO_WRAPPER_NATIVE_TO_MANAGED: {
g_assert (info);
encode_method_ref (acfg, info->d.native_to_managed.method, p, &p);
encode_klass_ref (acfg, info->d.native_to_managed.klass, p, &p);
MonoClass *klass = info->d.native_to_managed.klass;
if (!klass) {
encode_value (0, p, &p);
} else {
encode_value (1, p, &p);
encode_klass_ref (acfg, klass, p, &p);
}
break;
}
default:
Expand Down
10 changes: 7 additions & 3 deletions src/mono/mono/mini/aot-runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -1312,9 +1312,13 @@ decode_method_ref_with_target (MonoAotModule *module, MethodRef *ref, MonoMethod
m = decode_resolve_method_ref (module, p, &p, error);
if (!m)
return FALSE;
klass = decode_klass_ref (module, p, &p, error);
if (!klass)
return FALSE;
gboolean has_class = decode_value (p, &p);
if (has_class) {
klass = decode_klass_ref (module, p, &p, error);
if (!klass)
return FALSE;
} else
klass = NULL;
ref->method = mono_marshal_get_managed_wrapper (m, klass, 0, error);
if (!is_ok (error))
return FALSE;
Expand Down
2 changes: 1 addition & 1 deletion src/mono/mono/mini/aot-runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include "mini.h"

/* Version number of the AOT file format */
#define MONO_AOT_FILE_VERSION 178
#define MONO_AOT_FILE_VERSION 179

#define MONO_AOT_TRAMP_PAGE_SIZE 16384

Expand Down
81 changes: 81 additions & 0 deletions src/mono/mono/mini/interp/transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,32 @@ interp_generate_bie_throw (TransformData *td)
td->last_ins->data [0] = get_data_item_index (td, (gpointer)info->func);
}

static void
interp_generate_not_supported_throw (TransformData *td)
{
MonoJitICallInfo *info = &mono_get_jit_icall_info ()->mono_throw_not_supported;

interp_add_ins (td, MINT_ICALL_V_V);
td->last_ins->data [0] = get_data_item_index (td, (gpointer)info->func);
}

static void
interp_generate_ipe_throw_with_msg (TransformData *td, MonoError *error_msg)
{
MonoJitICallInfo *info = &mono_get_jit_icall_info ()->mono_throw_invalid_program;

char *msg = mono_mempool_strdup (td->rtm->domain->mp, mono_error_get_message (error_msg));

interp_add_ins (td, MINT_MONO_LDPTR);
td->last_ins->data [0] = get_data_item_index (td, msg);
PUSH_SIMPLE_TYPE (td, STACK_TYPE_I);

interp_add_ins (td, MINT_ICALL_P_V);
td->last_ins->data [0] = get_data_item_index (td, (gpointer)info->func);

td->sp -= 1;
}

/*
* These are additional locals that can be allocated as we transform the code.
* They are allocated past the method locals so they are accessed in the same
Expand Down Expand Up @@ -6203,6 +6229,61 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header,
if (method->wrapper_type == MONO_WRAPPER_NONE && m->iflags & METHOD_IMPL_ATTRIBUTE_SYNCHRONIZED)
m = mono_marshal_get_synchronized_wrapper (m);

if (G_UNLIKELY (*td->ip == CEE_LDFTN &&
m->wrapper_type == MONO_WRAPPER_NONE &&
mono_method_has_unmanaged_callers_only_attribute (m))) {

if (m->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL) {
interp_generate_not_supported_throw (td);
interp_add_ins (td, MINT_LDNULL);
td->ip += 5;
PUSH_SIMPLE_TYPE (td, STACK_TYPE_MP);
break;
}

MonoMethod *ctor_method;

const unsigned char *next_ip = td->ip + 5;
/* check for
* ldftn method_sig
* newobj Delegate::.ctor
*/
if (next_ip < end &&
*next_ip == CEE_NEWOBJ &&
((ctor_method = interp_get_method (method, read32 (next_ip + 1), image, generic_context, error))) &&
is_ok (error) &&
m_class_get_parent (ctor_method->klass) == mono_defaults.multicastdelegate_class &&
!strcmp (ctor_method->name, ".ctor")) {
mono_error_set_not_supported (error, "Cannot create delegate from method with UnmanagedCallersOnlyAttribute");
goto exit;
}

MonoClass *delegate_klass = NULL;
MonoGCHandle target_handle = 0;
ERROR_DECL (wrapper_error);
m = mono_marshal_get_managed_wrapper (m, delegate_klass, target_handle, wrapper_error);
if (!is_ok (wrapper_error)) {
/* Generate a call that will throw an exception if the
* UnmanagedCallersOnly attribute is used incorrectly */
interp_generate_ipe_throw_with_msg (td, wrapper_error);
mono_error_cleanup (wrapper_error);
interp_add_ins (td, MINT_LDNULL);
} else {
/* push a pointer to a trampoline that calls m */
gpointer entry = mini_get_interp_callbacks ()->create_method_pointer (m, TRUE, error);
#if SIZEOF_VOID_P == 8
interp_add_ins (td, MINT_LDC_I8);
WRITE64_INS (td->last_ins, 0, &entry);
#else
interp_add_ins (td, MINT_LDC_I4);
WRITE32_INS (td->last_ins, 0, &entry);
#endif
}
td->ip += 5;
PUSH_SIMPLE_TYPE (td, STACK_TYPE_MP);
break;
}

interp_add_ins (td, *td->ip == CEE_LDFTN ? MINT_LDFTN : MINT_LDVIRTFTN);
td->last_ins->data [0] = get_data_item_index (td, mono_interp_get_imethod (domain, m, error));
goto_if_nok (error, exit);
Expand Down
23 changes: 22 additions & 1 deletion src/mono/mono/mini/jit-icalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ mono_ldftn (MonoMethod *method)
return addr;
}

addr = mono_create_jump_trampoline (mono_domain_get (), method, FALSE, error);
/* if we need the address of a native-to-managed wrapper, just compile it now, trampoline needs thread local
* variables that won't be there if we run on a thread that's not attached yet. */
if (method->wrapper_type == MONO_WRAPPER_NATIVE_TO_MANAGED)
addr = mono_compile_method_checked (method, error);
else
addr = mono_create_jump_trampoline (mono_domain_get (), method, FALSE, error);
if (!is_ok (error)) {
mono_error_set_pending_exception (error);
return NULL;
Expand Down Expand Up @@ -1590,6 +1595,22 @@ mono_throw_bad_image ()
mono_error_set_pending_exception (error);
}

void
mono_throw_not_supported ()
{
ERROR_DECL (error);
mono_error_set_generic_error (error, "System", "NotSupportedException", "");
mono_error_set_pending_exception (error);
}

void
mono_throw_invalid_program (const char *msg)
{
ERROR_DECL (error);
mono_error_set_invalid_program (error, "Invalid IL due to: %s", msg);
mono_error_set_pending_exception (error);
}

void
mono_dummy_jit_icall (void)
{
Expand Down
4 changes: 4 additions & 0 deletions src/mono/mono/mini/jit-icalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ ICALL_EXTERN_C void mono_throw_method_access (MonoMethod *caller, MonoMethod *ca

ICALL_EXTERN_C void mono_throw_bad_image (void);

ICALL_EXTERN_C void mono_throw_not_supported (void);

ICALL_EXTERN_C void mono_throw_invalid_program (const char *msg);

ICALL_EXTERN_C void mono_dummy_jit_icall (void);

#endif /* __MONO_JIT_ICALLS_H__ */
Loading

0 comments on commit 1e3c959

Please sign in to comment.