From 10ea00de7f392e372936871b866fdaab2db2a8bb Mon Sep 17 00:00:00 2001 From: Ryan Lucia Date: Tue, 8 Oct 2019 18:02:05 -0400 Subject: [PATCH] [loader] Move PInvoke and DllMap code into separate source file Commit migrated from https://github.com/mono/mono/commit/bf3afd6cad3974706287d0943c3a5d0e823c1e5f --- src/mono/mono/metadata/Makefile.am | 4 +- src/mono/mono/metadata/loader-internals.h | 24 + src/mono/mono/metadata/loader.c | 1077 ++------------------ src/mono/mono/metadata/loader.h | 2 +- src/mono/mono/metadata/metadata-internals.h | 2 - src/mono/mono/metadata/mono-config.c | 5 +- src/mono/mono/metadata/native-library.c | 890 ++++++++++++++++ src/mono/msvc/libmonoruntime-common.targets | 1 + .../msvc/libmonoruntime-common.targets.filters | 3 + 9 files changed, 1018 insertions(+), 990 deletions(-) create mode 100644 src/mono/mono/metadata/native-library.c diff --git a/src/mono/mono/metadata/Makefile.am b/src/mono/mono/metadata/Makefile.am index 0ca4f7a..c23570a 100644 --- a/src/mono/mono/metadata/Makefile.am +++ b/src/mono/mono/metadata/Makefile.am @@ -421,8 +421,8 @@ common_sources = \ fdhandle.c \ callspec.h \ callspec.c \ - abi.c - + abi.c \ + native-library.c # These source files have compile time dependencies on GC code gc_dependent_sources = \ diff --git a/src/mono/mono/metadata/loader-internals.h b/src/mono/mono/metadata/loader-internals.h index 9d61ed4..4837040 100644 --- a/src/mono/mono/metadata/loader-internals.h +++ b/src/mono/mono/metadata/loader-internals.h @@ -16,6 +16,15 @@ typedef struct _MonoLoadedImages MonoLoadedImages; typedef struct _MonoAssemblyLoadContext MonoAssemblyLoadContext; +typedef struct _MonoDllMap MonoDllMap; +struct _MonoDllMap { + char *dll; + char *target; + char *func; + char *target_func; + MonoDllMap *next; +}; + #ifdef ENABLE_NETCORE /* FIXME: this probably belongs somewhere else */ struct _MonoAssemblyLoadContext { @@ -30,9 +39,24 @@ struct _MonoAssemblyLoadContext { }; #endif /* ENABLE_NETCORE */ +void +mono_global_loader_data_lock (void); + +void +mono_global_loader_data_unlock (void); + gpointer mono_lookup_pinvoke_call_internal (MonoMethod *method, MonoError *error); +void +mono_dllmap_insert_internal (MonoImage *assembly, const char *dll, const char *func, const char *tdll, const char *tfunc); + +void +mono_global_dllmap_cleanup (void); + +void +mono_cached_module_cleanup (void); + #ifdef ENABLE_NETCORE void mono_set_pinvoke_search_directories (int dir_count, char **dirs); diff --git a/src/mono/mono/metadata/loader.c b/src/mono/mono/metadata/loader.c index baf7241..1919ef4 100644 --- a/src/mono/mono/metadata/loader.c +++ b/src/mono/mono/metadata/loader.c @@ -51,8 +51,6 @@ #include #include -MonoDefaults mono_defaults; - /* * This lock protects the hash tables inside MonoImage used by the metadata * loading functions in class.c and loader.c. @@ -63,12 +61,7 @@ MonoDefaults mono_defaults; static MonoCoopMutex loader_mutex; static mono_mutex_t global_loader_data_mutex; static gboolean loader_lock_inited; - -/* Statistics */ -static gint32 inflated_signatures_size; -static gint32 memberref_sig_cache_size; -static gint32 methods_size; -static gint32 signatures_size; +static gboolean loader_lock_track_ownership = FALSE; /* * This TLS variable holds how many times the current thread has acquired the loader @@ -76,33 +69,13 @@ static gint32 signatures_size; */ static MonoNativeTlsKey loader_lock_nest_id; -#if ENABLE_NETCORE -static int pinvoke_search_directories_count; -static char **pinvoke_search_directories; -#endif - -static void dllmap_cleanup (void); -static void cached_module_cleanup(void); - -static void dllmap_insert_global (const char *dll, const char *func, const char *tdll, const char *tfunc); -static void dllmap_insert_image (MonoImage *assembly, const char *dll, const char *func, const char *tdll, const char *tfun); - - -/* Class lazy loading functions */ -GENERATE_GET_CLASS_WITH_CACHE (appdomain_unloaded_exception, "System", "AppDomainUnloadedException") -GENERATE_TRY_GET_CLASS_WITH_CACHE (appdomain_unloaded_exception, "System", "AppDomainUnloadedException") - -static void -global_loader_data_lock (void) -{ - mono_locks_os_acquire (&global_loader_data_mutex, LoaderGlobalDataLock); -} +MonoDefaults mono_defaults; -static void -global_loader_data_unlock (void) -{ - mono_locks_os_release (&global_loader_data_mutex, LoaderGlobalDataLock); -} +/* Statistics */ +static gint32 inflated_signatures_size; +static gint32 memberref_sig_cache_size; +static gint32 methods_size; +static gint32 signatures_size; void mono_loader_init () @@ -133,8 +106,8 @@ mono_loader_init () void mono_loader_cleanup (void) { - dllmap_cleanup (); - cached_module_cleanup (); + mono_global_dllmap_cleanup (); + mono_cached_module_cleanup (); mono_native_tls_free (loader_lock_nest_id); @@ -143,6 +116,91 @@ mono_loader_cleanup (void) loader_lock_inited = FALSE; } +void +mono_global_loader_data_lock (void) +{ + mono_locks_os_acquire (&global_loader_data_mutex, LoaderGlobalDataLock); +} + +void +mono_global_loader_data_unlock (void) +{ + mono_locks_os_release (&global_loader_data_mutex, LoaderGlobalDataLock); +} + +/** + * mono_loader_lock: + * + * See \c docs/thread-safety.txt for the locking strategy. + */ +void +mono_loader_lock (void) +{ + mono_locks_coop_acquire (&loader_mutex, LoaderLock); + if (G_UNLIKELY (loader_lock_track_ownership)) { + mono_native_tls_set_value (loader_lock_nest_id, GUINT_TO_POINTER (GPOINTER_TO_UINT (mono_native_tls_get_value (loader_lock_nest_id)) + 1)); + } +} + +/** + * mono_loader_unlock: + */ +void +mono_loader_unlock (void) +{ + mono_locks_coop_release (&loader_mutex, LoaderLock); + if (G_UNLIKELY (loader_lock_track_ownership)) { + mono_native_tls_set_value (loader_lock_nest_id, GUINT_TO_POINTER (GPOINTER_TO_UINT (mono_native_tls_get_value (loader_lock_nest_id)) - 1)); + } +} + +/* + * mono_loader_lock_track_ownership: + * + * Set whenever the runtime should track ownership of the loader lock. If set to TRUE, + * the mono_loader_lock_is_owned_by_self () can be called to query whenever the current + * thread owns the loader lock. + */ +void +mono_loader_lock_track_ownership (gboolean track) +{ + loader_lock_track_ownership = track; +} + +/* + * mono_loader_lock_is_owned_by_self: + * + * Return whenever the current thread owns the loader lock. + * This is useful to avoid blocking operations while holding the loader lock. + */ +gboolean +mono_loader_lock_is_owned_by_self (void) +{ + g_assert (loader_lock_track_ownership); + + return GPOINTER_TO_UINT (mono_native_tls_get_value (loader_lock_nest_id)) > 0; +} + +/* + * mono_loader_lock_if_inited: + * + * Acquire the loader lock if it has been initialized, no-op otherwise. This can + * be used in runtime initialization code which can be executed before mono_loader_init (). + */ +void +mono_loader_lock_if_inited (void) +{ + if (loader_lock_inited) + mono_loader_lock (); +} + +void +mono_loader_unlock_if_inited (void) +{ + if (loader_lock_inited) + mono_loader_unlock (); +} + /* * find_cached_memberref_sig: * @@ -965,878 +1023,6 @@ method_from_methodspec (MonoImage *image, MonoGenericContext *context, guint32 i return method; } -struct _MonoDllMap { - char *dll; - char *target; - char *func; - char *target_func; - MonoDllMap *next; -}; - -static MonoDllMap *global_dll_map; - -static int -mono_dllmap_lookup_list (MonoDllMap *dll_map, const char *dll, const char* func, const char **rdll, const char **rfunc) { - int found = 0; - - *rdll = dll; - - if (!dll_map) - return 0; - - global_loader_data_lock (); - - /* - * we use the first entry we find that matches, since entries from - * the config file are prepended to the list and we document that the - * later entries win. - */ - for (; dll_map; dll_map = dll_map->next) { - if (dll_map->dll [0] == 'i' && dll_map->dll [1] == ':') { - if (g_ascii_strcasecmp (dll_map->dll + 2, dll)) - continue; - } else if (strcmp (dll_map->dll, dll)) { - continue; - } - if (!found && dll_map->target) { - *rdll = dll_map->target; - found = 1; - /* we don't quit here, because we could find a full - * entry that matches also function and that has priority. - */ - } - if (dll_map->func && strcmp (dll_map->func, func) == 0) { - *rdll = dll_map->target; - *rfunc = dll_map->target_func; - break; - } - } - - global_loader_data_unlock (); - return found; -} - -static int -mono_dllmap_lookup (MonoImage *assembly, const char *dll, const char* func, const char **rdll, const char **rfunc) -{ - int res; - if (assembly && assembly->dll_map) { - res = mono_dllmap_lookup_list (assembly->dll_map, dll, func, rdll, rfunc); - if (res) - return res; - } - return mono_dllmap_lookup_list (global_dll_map, dll, func, rdll, rfunc); -} - -/** - * mono_dllmap_insert: - * \param assembly if NULL, this is a global mapping, otherwise the remapping of the dynamic library will only apply to the specified assembly - * \param dll The name of the external library, as it would be found in the \c DllImport declaration. If prefixed with i: the matching of the library name is done without case sensitivity - * \param func if not null, the mapping will only applied to the named function (the value of EntryPoint) - * \param tdll The name of the library to map the specified \p dll if it matches. - * \param tfunc The name of the function that replaces the invocation. If NULL, it is replaced with a copy of \p func. - * - * LOCKING: Acquires the loader lock. - * - * This function is used to programatically add \c DllImport remapping in either - * a specific assembly, or as a global remapping. This is done by remapping - * references in a \c DllImport attribute from the \p dll library name into the \p tdll - * name. If the \p dll name contains the prefix i:, the comparison of the - * library name is done without case sensitivity. - * - * If you pass \p func, this is the name of the \c EntryPoint in a \c DllImport if specified - * or the name of the function as determined by \c DllImport. If you pass \p func, you - * must also pass \p tfunc which is the name of the target function to invoke on a match. - * - * Example: - * - * mono_dllmap_insert (NULL, "i:libdemo.dll", NULL, relocated_demo_path, NULL); - * - * The above will remap \c DllImport statements for \c libdemo.dll and \c LIBDEMO.DLL to - * the contents of \c relocated_demo_path for all assemblies in the Mono process. - * - * NOTE: This can be called before the runtime is initialized, for example from - * \c mono_config_parse. - */ -void -mono_dllmap_insert (MonoImage *assembly, const char *dll, const char *func, const char *tdll, const char *tfunc) -{ - if (!assembly) - dllmap_insert_global (dll, func, tdll, tfunc); - else { - MONO_ENTER_GC_UNSAFE; - dllmap_insert_image (assembly, dll, func, tdll, tfunc); - MONO_EXIT_GC_UNSAFE; - } -} - -void -dllmap_insert_global (const char *dll, const char *func, const char *tdll, const char *tfunc) -{ - MonoDllMap *entry; - - mono_loader_init (); - - entry = (MonoDllMap *)g_malloc0 (sizeof (MonoDllMap)); - entry->dll = dll? g_strdup (dll): NULL; - entry->target = tdll? g_strdup (tdll): NULL; - entry->func = func? g_strdup (func): NULL; - entry->target_func = tfunc? g_strdup (tfunc): (func? g_strdup (func): NULL); - - global_loader_data_lock (); - entry->next = global_dll_map; - global_dll_map = entry; - global_loader_data_unlock (); -} - -void -dllmap_insert_image (MonoImage *assembly, const char *dll, const char *func, const char *tdll, const char *tfunc) -{ - MonoDllMap *entry; - g_assert (assembly != NULL); - - mono_loader_init (); - - entry = (MonoDllMap *)mono_image_alloc0 (assembly, sizeof (MonoDllMap)); - entry->dll = dll? mono_image_strdup (assembly, dll): NULL; - entry->target = tdll? mono_image_strdup (assembly, tdll): NULL; - entry->func = func? mono_image_strdup (assembly, func): NULL; - entry->target_func = tfunc? mono_image_strdup (assembly, tfunc): (func? mono_image_strdup (assembly, func): NULL); - - mono_image_lock (assembly); - entry->next = assembly->dll_map; - assembly->dll_map = entry; - mono_image_unlock (assembly); -} - -static void -free_dllmap (MonoDllMap *map) -{ - while (map) { - MonoDllMap *next = map->next; - - g_free (map->dll); - g_free (map->target); - g_free (map->func); - g_free (map->target_func); - g_free (map); - map = next; - } -} - -static void -dllmap_cleanup (void) -{ - free_dllmap (global_dll_map); - global_dll_map = NULL; -} - -static GHashTable *global_module_map; - -static MonoDl* -cached_module_load (const char *name, int flags, char **err) -{ - MonoDl *res; - - if (err) - *err = NULL; - global_loader_data_lock (); - if (!global_module_map) - global_module_map = g_hash_table_new (g_str_hash, g_str_equal); - res = (MonoDl *)g_hash_table_lookup (global_module_map, name); - if (res) { - global_loader_data_unlock (); - return res; - } - res = mono_dl_open (name, flags, err); - if (res) - g_hash_table_insert (global_module_map, g_strdup (name), res); - global_loader_data_unlock (); - return res; -} - -void -mono_loader_register_module (const char *name, MonoDl *module) -{ - if (!global_module_map) - global_module_map = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (global_module_map, g_strdup (name), module); -} - -static void -remove_cached_module(gpointer key, gpointer value, gpointer user_data) -{ - mono_dl_close((MonoDl*)value); -} - -static void -cached_module_cleanup(void) -{ - if (global_module_map != NULL) { - g_hash_table_foreach(global_module_map, remove_cached_module, NULL); - - g_hash_table_destroy(global_module_map); - global_module_map = NULL; - } -} - -static MonoDl *internal_module; - -static gboolean -is_absolute_path (const char *path) -{ -#ifdef HOST_DARWIN - if (!strncmp (path, "@executable_path/", 17) || !strncmp (path, "@loader_path/", 13) || - !strncmp (path, "@rpath/", 7)) - return TRUE; -#endif - return g_path_is_absolute (path); -} - -typedef enum { - LOOKUP_PINVOKE_ERR_OK = 0, /* No error */ - LOOKUP_PINVOKE_ERR_NO_LIB, /* DllNotFoundException */ - LOOKUP_PINVOKE_ERR_NO_SYM, /* EntryPointNotFoundException */ -} MonoLookupPInvokeErr; - -/* We should just use a MonoError, but mono_lookup_pinvoke_call has this legacy - * error reporting mechanism where it returns an exception class and a string - * message. So instead we return an error code and message, and for internal - * callers convert it to a MonoError. - * - * Don't expose this type to the runtime. It's just an implementation - * detail for backward compatability. - */ -typedef struct MonoLookupPInvokeStatus { - MonoLookupPInvokeErr err_code; - char *err_arg; -} MonoLookupPInvokeStatus; - -static gpointer -lookup_pinvoke_call_impl (MonoMethod *method, MonoLookupPInvokeStatus *status_out); - -static void -pinvoke_probe_convert_status_for_api (MonoLookupPInvokeStatus *status, const char **exc_class, const char **exc_arg) -{ - if (!exc_class) - return; - switch (status->err_code) { - case LOOKUP_PINVOKE_ERR_OK: - *exc_class = NULL; - *exc_arg = NULL; - break; - case LOOKUP_PINVOKE_ERR_NO_LIB: - *exc_class = "DllNotFoundException"; - *exc_arg = status->err_arg; - status->err_arg = NULL; - break; - case LOOKUP_PINVOKE_ERR_NO_SYM: - *exc_class = "EntryPointNotFoundException"; - *exc_arg = status->err_arg; - status->err_arg = NULL; - break; - default: - g_assert_not_reached (); - } -} - -static void -pinvoke_probe_convert_status_to_error (MonoLookupPInvokeStatus *status, MonoError *error) -{ - /* Note: this has to return a MONO_ERROR_GENERIC because mono_mb_emit_exception_for_error only knows how to decode generic errors. */ - switch (status->err_code) { - case LOOKUP_PINVOKE_ERR_OK: - return; - case LOOKUP_PINVOKE_ERR_NO_LIB: - mono_error_set_generic_error (error, "System", "DllNotFoundException", "%s", status->err_arg); - g_free (status->err_arg); - status->err_arg = NULL; - break; - case LOOKUP_PINVOKE_ERR_NO_SYM: - mono_error_set_generic_error (error, "System", "EntryPointNotFoundException", "%s", status->err_arg); - g_free (status->err_arg); - status->err_arg = NULL; - break; - default: - g_assert_not_reached (); - } -} - -/** - * mono_lookup_pinvoke_call: - */ -gpointer -mono_lookup_pinvoke_call (MonoMethod *method, const char **exc_class, const char **exc_arg) -{ - gpointer result; - MONO_ENTER_GC_UNSAFE; - MonoLookupPInvokeStatus status; - memset (&status, 0, sizeof (status)); - result = lookup_pinvoke_call_impl (method, &status); - pinvoke_probe_convert_status_for_api (&status, exc_class, exc_arg); - MONO_EXIT_GC_UNSAFE; - return result; -} - -static MonoDl* -pinvoke_probe_for_module (MonoImage *image, const char*new_scope, const char *import, char **found_name_out, char **error_msg_out); - -static MonoDl* -pinvoke_probe_for_module_relative_directories (MonoImage *image, const char *file_name, char **found_name_out); - -static gpointer -pinvoke_probe_for_symbol (MonoDl *module, MonoMethodPInvoke *piinfo, const char *import, char **error_msg_out); - -gpointer -mono_lookup_pinvoke_call_internal (MonoMethod *method, MonoError *error) -{ - gpointer result; - MonoLookupPInvokeStatus status; - memset (&status, 0, sizeof (status)); - result = lookup_pinvoke_call_impl (method, &status); - if (status.err_code) - pinvoke_probe_convert_status_to_error (&status, error); - return result; -} - -gpointer -lookup_pinvoke_call_impl (MonoMethod *method, MonoLookupPInvokeStatus *status_out) -{ - MonoImage *image = m_class_get_image (method->klass); - MonoMethodPInvoke *piinfo = (MonoMethodPInvoke *)method; - MonoTableInfo *tables = image->tables; - MonoTableInfo *im = &tables [MONO_TABLE_IMPLMAP]; - MonoTableInfo *mr = &tables [MONO_TABLE_MODULEREF]; - guint32 im_cols [MONO_IMPLMAP_SIZE]; - guint32 scope_token; - const char *import = NULL; - const char *orig_scope; - const char *new_scope; - char *error_msg; - char *found_name = NULL; - MonoDl *module = NULL; - gboolean cached = FALSE; - gpointer addr = NULL; - - g_assert (method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL); - - g_assert (status_out); - - if (piinfo->addr) - return piinfo->addr; - - if (image_is_dynamic (m_class_get_image (method->klass))) { - MonoReflectionMethodAux *method_aux = - (MonoReflectionMethodAux *)g_hash_table_lookup ( - ((MonoDynamicImage*)m_class_get_image (method->klass))->method_aux_hash, method); - if (!method_aux) - return NULL; - - import = method_aux->dllentry; - orig_scope = method_aux->dll; - } - else { - if (!piinfo->implmap_idx || piinfo->implmap_idx > im->rows) - return NULL; - - mono_metadata_decode_row (im, piinfo->implmap_idx - 1, im_cols, MONO_IMPLMAP_SIZE); - - if (!im_cols [MONO_IMPLMAP_SCOPE] || im_cols [MONO_IMPLMAP_SCOPE] > mr->rows) - return NULL; - - piinfo->piflags = im_cols [MONO_IMPLMAP_FLAGS]; - import = mono_metadata_string_heap (image, im_cols [MONO_IMPLMAP_NAME]); - scope_token = mono_metadata_decode_row_col (mr, im_cols [MONO_IMPLMAP_SCOPE] - 1, MONO_MODULEREF_NAME); - orig_scope = mono_metadata_string_heap (image, scope_token); - } - -#ifndef ENABLE_NETCORE - // FIXME: The dllmap remaps System.Native to mono-native - mono_dllmap_lookup (image, orig_scope, import, &new_scope, &import); -#else - /* AK: FIXME: dllmap, above doesn't strdup the results, so these leak - * since there's no free() */ - new_scope = g_strdup (orig_scope); - import = g_strdup (import); -#endif - - if (!module) { - mono_image_lock (image); - if (!image->pinvoke_scopes) { - image->pinvoke_scopes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - image->pinvoke_scope_filenames = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - } - module = (MonoDl *)g_hash_table_lookup (image->pinvoke_scopes, new_scope); - found_name = (char *)g_hash_table_lookup (image->pinvoke_scope_filenames, new_scope); - mono_image_unlock (image); - if (module) - cached = TRUE; - if (found_name) - found_name = g_strdup (found_name); - } - - if (!module) - module = pinvoke_probe_for_module (image, new_scope, import, &found_name, &error_msg); - - if (!module) { - mono_trace (G_LOG_LEVEL_WARNING, MONO_TRACE_DLLIMPORT, - "DllImport unable to load library '%s'.", - error_msg); - g_free (error_msg); - - status_out->err_code = LOOKUP_PINVOKE_ERR_NO_LIB; - status_out->err_arg = g_strdup (new_scope); - return NULL; - } - - if (!cached) { - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "DllImport loaded library '%s'.", found_name); - mono_image_lock (image); - if (!g_hash_table_lookup (image->pinvoke_scopes, new_scope)) { - g_hash_table_insert (image->pinvoke_scopes, g_strdup (new_scope), module); - g_hash_table_insert (image->pinvoke_scope_filenames, g_strdup (new_scope), g_strdup (found_name)); - } - mono_image_unlock (image); - } - - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "DllImport searching in: '%s' ('%s').", new_scope, found_name); - g_free (found_name); - - addr = pinvoke_probe_for_symbol (module, piinfo, import, &error_msg); - - if (!addr) { - g_free (error_msg); - status_out->err_code = LOOKUP_PINVOKE_ERR_NO_SYM; - status_out->err_arg = g_strdup (import); - return NULL; - } - piinfo->addr = addr; - return addr; -} - -/** - * pinvoke_probe_transform_path: - * - * Try transforming the library path given in \p new_scope in different ways - * depending on \p phase - * - * \returns \c TRUE if a transformation was applied and the transformed path - * components are written to the out arguments, or \c FALSE if a transformation - * did not apply. - */ -static gboolean -pinvoke_probe_transform_path (const char *new_scope, int phase, char **file_name_out, char **base_name_out, char **dir_name_out, gboolean *is_absolute_out) -{ - char *file_name = NULL, *base_name = NULL, *dir_name = NULL; - gboolean changed = FALSE; - gboolean is_absolute = is_absolute_path (new_scope); - switch (phase) { - case 0: - /* Try the original name */ - file_name = g_strdup (new_scope); - changed = TRUE; - break; - case 1: - /* Try trimming the .dll extension */ - if (strstr (new_scope, ".dll") == (new_scope + strlen (new_scope) - 4)) { - file_name = g_strdup (new_scope); - file_name [strlen (new_scope) - 4] = '\0'; - changed = TRUE; - } - break; - case 2: - if (is_absolute) { - dir_name = g_path_get_dirname (new_scope); - base_name = g_path_get_basename (new_scope); - if (strstr (base_name, "lib") != base_name) { - char *tmp = g_strdup_printf ("lib%s", base_name); - g_free (base_name); - base_name = tmp; - file_name = g_strdup_printf ("%s%s%s", dir_name, G_DIR_SEPARATOR_S, base_name); - changed = TRUE; - } - } else if (strstr (new_scope, "lib") != new_scope) { - file_name = g_strdup_printf ("lib%s", new_scope); - changed = TRUE; - } - break; - case 3: - if (!is_absolute && mono_dl_get_system_dir ()) { - dir_name = (char*)mono_dl_get_system_dir (); - file_name = g_path_get_basename (new_scope); - base_name = NULL; - changed = TRUE; - } - break; - default: -#ifndef TARGET_WIN32 - if (!g_ascii_strcasecmp ("user32.dll", new_scope) || - !g_ascii_strcasecmp ("kernel32.dll", new_scope) || - !g_ascii_strcasecmp ("user32", new_scope) || - !g_ascii_strcasecmp ("kernel", new_scope)) { - file_name = g_strdup ("libMonoSupportW.so"); - changed = TRUE; - } -#endif - break; - } - if (changed && is_absolute) { - if (!dir_name) - dir_name = g_path_get_dirname (file_name); - if (!base_name) - base_name = g_path_get_basename (file_name); - } - *file_name_out = file_name; - *base_name_out = base_name; - *dir_name_out = dir_name; - *is_absolute_out = is_absolute; - return changed; -} - -static MonoDl* -pinvoke_probe_for_module (MonoImage *image, const char*new_scope, const char *import, char **found_name_out, char **error_msg_out) -{ - char *full_name, *file_name; - char *error_msg = NULL; - char *found_name = NULL; - int i; - MonoDl *module = NULL; - - g_assert (found_name_out); - g_assert (error_msg_out); - - if (!module) { - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "DllImport attempting to load: '%s'.", new_scope); - - /* we allow a special name to dlopen from the running process namespace */ - if (strcmp (new_scope, "__Internal") == 0){ - if (internal_module == NULL) - internal_module = mono_dl_open (NULL, MONO_DL_LAZY, &error_msg); - module = internal_module; - } - } - - /* - * Try loading the module using a variety of names - */ - for (i = 0; i < 5; ++i) { - char *base_name = NULL, *dir_name = NULL; - gboolean is_absolute; - - gboolean changed = pinvoke_probe_transform_path (new_scope, i, &file_name, &base_name, &dir_name, &is_absolute); - if (!changed) - continue; - - if (!module && is_absolute) { - module = cached_module_load (file_name, MONO_DL_LAZY, &error_msg); - if (!module) { - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "DllImport error loading library '%s': '%s'.", - file_name, error_msg); - g_free (error_msg); - } else { - found_name = g_strdup (file_name); - } - } - - if (!module && !is_absolute) { - module = pinvoke_probe_for_module_relative_directories (image, file_name, &found_name); - } - - if (!module) { - void *iter = NULL; - char *file_or_base = is_absolute ? base_name : file_name; - while ((full_name = mono_dl_build_path (dir_name, file_or_base, &iter))) { - module = cached_module_load (full_name, MONO_DL_LAZY, &error_msg); - if (!module) { - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "DllImport error loading library '%s': '%s'.", - full_name, error_msg); - g_free (error_msg); - } else { - found_name = g_strdup (full_name); - } - g_free (full_name); - if (module) - break; - } - } - - if (!module) { - module = cached_module_load (file_name, MONO_DL_LAZY, &error_msg); - if (!module) { - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "DllImport error loading library '%s': '%s'.", - file_name, error_msg); - } else { - found_name = g_strdup (file_name); - } - } - - g_free (file_name); - if (is_absolute) { - g_free (base_name); - g_free (dir_name); - } - - if (module) - break; - } - - *found_name_out = found_name; - *error_msg_out = error_msg; - return module; -} - -#if ENABLE_NETCORE -void -mono_set_pinvoke_search_directories (int dir_count, char **dirs) -{ - pinvoke_search_directories_count = dir_count; - pinvoke_search_directories = dirs; -} -#endif - -static MonoDl* -pinvoke_probe_for_module_in_directory (const char *mdirname, const char *file_name, char **found_name_out) -{ - void *iter = NULL; - char *full_name; - MonoDl* module = NULL; - - while ((full_name = mono_dl_build_path (mdirname, file_name, &iter)) && module == NULL) { - char *error_msg; - module = cached_module_load (full_name, MONO_DL_LAZY, &error_msg); - if (!module) { - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, "DllImport error loading library '%s': '%s'.", full_name, error_msg); - g_free (error_msg); - } else { - *found_name_out = g_strdup (full_name); - } - g_free (full_name); - } - g_free (full_name); - - return module; -} - -static MonoDl* -pinvoke_probe_for_module_relative_directories (MonoImage *image, const char *file_name, char **found_name_out) -{ - char *found_name = NULL; - MonoDl* module = NULL; - - g_assert (found_name_out); - -#if ENABLE_NETCORE - mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "netcore DllImport handler: wanted '%s'", file_name); - - // Search in predefined directories first - for (int j = 0; j < pinvoke_search_directories_count && module == NULL; ++j) { - module = pinvoke_probe_for_module_in_directory (pinvoke_search_directories[j], file_name, &found_name); - } - - // Fallback to image directory - if (module == NULL) { - // TODO: Check DefaultDllImportSearchPathsAttribute, NativeLibrary callback - char *mdirname = g_path_get_dirname (image->name); - if (mdirname) - module = pinvoke_probe_for_module_in_directory (mdirname, file_name, &found_name); - g_free (mdirname); - } -#else - for (int j = 0; j < 3; ++j) { - char *mdirname = NULL; - switch (j) { - case 0: - mdirname = g_path_get_dirname (image->name); - break; - case 1: /* @executable_path@/../lib */ - { - char buf [4096]; - int binl; - binl = mono_dl_get_executable_path (buf, sizeof (buf)); - if (binl != -1) { - char *base, *newbase; - char *resolvedname; - buf [binl] = 0; - resolvedname = mono_path_resolve_symlinks (buf); - - base = g_path_get_dirname (resolvedname); - newbase = g_path_get_dirname(base); - - // On Android the executable for the application is going to be /system/bin/app_process{32,64} depending on - // the application's architecture. However, libraries for the different architectures live in different - // subdirectories of `/system`: `lib` for 32-bit apps and `lib64` for 64-bit ones. Thus appending `/lib` below - // will fail to load the DSO for a 64-bit app, even if it exists there, because it will have a different - // architecture. This is the cause of https://github.com/xamarin/xamarin-android/issues/2780 and the ifdef - // below is the fix. - mdirname = g_strdup_printf ( -#if defined(TARGET_ANDROID) && (defined(TARGET_ARM64) || defined(TARGET_AMD64)) - "%s/lib64", -#else - "%s/lib", -#endif - newbase); - g_free (resolvedname); - g_free (base); - g_free (newbase); - } - break; - } -#ifdef __MACH__ - case 2: /* @executable_path@/../Libraries */ - { - char buf [4096]; - int binl; - binl = mono_dl_get_executable_path (buf, sizeof (buf)); - if (binl != -1) { - char *base, *newbase; - char *resolvedname; - buf [binl] = 0; - resolvedname = mono_path_resolve_symlinks (buf); - - base = g_path_get_dirname (resolvedname); - newbase = g_path_get_dirname(base); - mdirname = g_strdup_printf ("%s/Libraries", newbase); - - g_free (resolvedname); - g_free (base); - g_free (newbase); - } - break; - } -#endif - } - - if (!mdirname) - continue; - - module = pinvoke_probe_for_module_in_directory (mdirname, file_name, &found_name); - g_free (mdirname); - if (module) - break; - } -#endif - - *found_name_out = found_name; - return module; -} - - -static gpointer -pinvoke_probe_for_symbol (MonoDl *module, MonoMethodPInvoke *piinfo, const char *import, char **error_msg_out) -{ - char *error_msg = NULL; - gpointer addr = NULL; - - g_assert (error_msg_out); - -#ifdef HOST_WIN32 - if (import && import [0] == '#' && isdigit (import [1])) { - char *end; - long id; - - id = strtol (import + 1, &end, 10); - if (id > 0 && *end == '\0') - import++; - } -#endif - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "Searching for '%s'.", import); - - if (piinfo->piflags & PINVOKE_ATTRIBUTE_NO_MANGLE) { - error_msg = mono_dl_symbol (module, import, &addr); - } else { - /* - * Search using a variety of mangled names - */ - for (int mangle_stdcall = 0; mangle_stdcall <= 1 && addr == NULL; mangle_stdcall++) { -#if HOST_WIN32 && HOST_X86 - const int max_managle_param_count = (mangle_stdcall == 0) ? 0 : 256; -#else - const int max_managle_param_count = 0; -#endif - for (int mangle_charset = 0; mangle_charset <= 1 && addr == NULL; mangle_charset ++) { - for (int mangle_param_count = 0; mangle_param_count <= max_managle_param_count && addr == NULL; mangle_param_count += 4) { - - char *mangled_name = (char*)import; - switch (piinfo->piflags & PINVOKE_ATTRIBUTE_CHAR_SET_MASK) { - case PINVOKE_ATTRIBUTE_CHAR_SET_UNICODE: - /* Try the mangled name first */ - if (mangle_charset == 0) - mangled_name = g_strconcat (import, "W", (const char*)NULL); - break; - case PINVOKE_ATTRIBUTE_CHAR_SET_AUTO: -#ifdef HOST_WIN32 - if (mangle_charset == 0) - mangled_name = g_strconcat (import, "W", (const char*)NULL); -#else - /* Try the mangled name last */ - if (mangle_charset == 1) - mangled_name = g_strconcat (import, "A", (const char*)NULL); -#endif - break; - case PINVOKE_ATTRIBUTE_CHAR_SET_ANSI: - default: - /* Try the mangled name last */ - if (mangle_charset == 1) - mangled_name = g_strconcat (import, "A", (const char*)NULL); - break; - } - -#if HOST_WIN32 && HOST_X86 - /* Try the stdcall mangled name */ - /* - * gcc under windows creates mangled names without the underscore, but MS.NET - * doesn't support it, so we doesn't support it either. - */ - if (mangle_stdcall == 1) { - MonoMethod *method = &piinfo->method; - int param_count; - if (mangle_param_count == 0) - param_count = mono_method_signature_internal (method)->param_count * sizeof (gpointer); - else - /* Try brute force, since it would be very hard to compute the stack usage correctly */ - param_count = mangle_param_count; - - char *mangled_stdcall_name = g_strdup_printf ("_%s@%d", mangled_name, param_count); - - if (mangled_name != import) - g_free (mangled_name); - - mangled_name = mangled_stdcall_name; - } -#endif - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "Probing '%s'.", mangled_name); - - error_msg = mono_dl_symbol (module, mangled_name, &addr); - - if (addr) - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "Found as '%s'.", mangled_name); - else - mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, - "Could not find '%s' due to '%s'.", mangled_name, error_msg); - - g_free (error_msg); - error_msg = NULL; - - if (mangled_name != import) - g_free (mangled_name); - } - } - } - } - - *error_msg_out = error_msg; - return addr; -} - /* * LOCKING: assumes the loader lock to be taken. */ @@ -2597,81 +1783,6 @@ mono_method_get_last_managed (void) return m; } -static gboolean loader_lock_track_ownership = FALSE; - -/** - * mono_loader_lock: - * - * See \c docs/thread-safety.txt for the locking strategy. - */ -void -mono_loader_lock (void) -{ - mono_locks_coop_acquire (&loader_mutex, LoaderLock); - if (G_UNLIKELY (loader_lock_track_ownership)) { - mono_native_tls_set_value (loader_lock_nest_id, GUINT_TO_POINTER (GPOINTER_TO_UINT (mono_native_tls_get_value (loader_lock_nest_id)) + 1)); - } -} - -/** - * mono_loader_unlock: - */ -void -mono_loader_unlock (void) -{ - mono_locks_coop_release (&loader_mutex, LoaderLock); - if (G_UNLIKELY (loader_lock_track_ownership)) { - mono_native_tls_set_value (loader_lock_nest_id, GUINT_TO_POINTER (GPOINTER_TO_UINT (mono_native_tls_get_value (loader_lock_nest_id)) - 1)); - } -} - -/* - * mono_loader_lock_track_ownership: - * - * Set whenever the runtime should track ownership of the loader lock. If set to TRUE, - * the mono_loader_lock_is_owned_by_self () can be called to query whenever the current - * thread owns the loader lock. - */ -void -mono_loader_lock_track_ownership (gboolean track) -{ - loader_lock_track_ownership = track; -} - -/* - * mono_loader_lock_is_owned_by_self: - * - * Return whenever the current thread owns the loader lock. - * This is useful to avoid blocking operations while holding the loader lock. - */ -gboolean -mono_loader_lock_is_owned_by_self (void) -{ - g_assert (loader_lock_track_ownership); - - return GPOINTER_TO_UINT (mono_native_tls_get_value (loader_lock_nest_id)) > 0; -} - -/* - * mono_loader_lock_if_inited: - * - * Acquire the loader lock if it has been initialized, no-op otherwise. This can - * be used in runtime initialization code which can be executed before mono_loader_init (). - */ -void -mono_loader_lock_if_inited (void) -{ - if (loader_lock_inited) - mono_loader_lock (); -} - -void -mono_loader_unlock_if_inited (void) -{ - if (loader_lock_inited) - mono_loader_unlock (); -} - /** * mono_method_signature_checked_slow: * diff --git a/src/mono/mono/metadata/loader.h b/src/mono/mono/metadata/loader.h index ad70c77..42c8928 100644 --- a/src/mono/mono/metadata/loader.h +++ b/src/mono/mono/metadata/loader.h @@ -68,7 +68,7 @@ mono_lookup_internal_call (MonoMethod *method); MONO_API const char* mono_lookup_icall_symbol (MonoMethod *m); -MONO_API void +MONO_API MONO_RT_EXTERNAL_ONLY void mono_dllmap_insert (MonoImage *assembly, const char *dll, const char *func, const char *tdll, const char *tfunc); MONO_API MONO_RT_EXTERNAL_ONLY void* diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index b033439..eb2b4f8 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -298,8 +298,6 @@ struct _MonoTableInfo { #define REFERENCE_MISSING ((gpointer) -1) -typedef struct _MonoDllMap MonoDllMap; - typedef struct { gboolean (*match) (MonoImage*); gboolean (*load_pe_data) (MonoImage*); diff --git a/src/mono/mono/metadata/mono-config.c b/src/mono/mono/metadata/mono-config.c index aa9e554..29590ad 100644 --- a/src/mono/mono/metadata/mono-config.c +++ b/src/mono/mono/metadata/mono-config.c @@ -15,6 +15,7 @@ #include "mono/metadata/assembly.h" #include "mono/metadata/loader.h" +#include "mono/metadata/loader-internals.h" #include "mono/metadata/mono-config.h" #include "mono/metadata/mono-config-internals.h" #include "mono/metadata/metadata-internals.h" @@ -342,7 +343,7 @@ dllmap_start (gpointer user_data, info->ignore = TRUE; } if (!info->ignore) - mono_dllmap_insert (info->assembly, info->dll, NULL, info->target, NULL); + mono_dllmap_insert_internal (info->assembly, info->dll, NULL, info->target, NULL); } else if (strcmp (element_name, "dllentry") == 0) { const char *name = NULL, *target = NULL, *dll = NULL; int ignore = FALSE; @@ -363,7 +364,7 @@ dllmap_start (gpointer user_data, if (!dll) dll = info->dll; if (!info->ignore && !ignore) - mono_dllmap_insert (info->assembly, info->dll, name, dll, target); + mono_dllmap_insert_internal (info->assembly, info->dll, name, dll, target); } } diff --git a/src/mono/mono/metadata/native-library.c b/src/mono/mono/metadata/native-library.c new file mode 100644 index 0000000..0f72894 --- /dev/null +++ b/src/mono/mono/metadata/native-library.c @@ -0,0 +1,890 @@ +#include "config.h" +#include "mono/utils/mono-compiler.h" +#include "mono/metadata/loader-internals.h" +#include "mono/metadata/loader.h" +#include "mono/metadata/class-internals.h" +#include "mono/utils/mono-logger-internals.h" +#include "mono/utils/mono-path.h" + +#ifdef ENABLE_NETCORE +static int pinvoke_search_directories_count; +static char **pinvoke_search_directories; +#endif + +static MonoDllMap *global_dll_map; +static GHashTable *global_module_map; + +static MonoDl *internal_module; + +typedef enum { + LOOKUP_PINVOKE_ERR_OK = 0, /* No error */ + LOOKUP_PINVOKE_ERR_NO_LIB, /* DllNotFoundException */ + LOOKUP_PINVOKE_ERR_NO_SYM, /* EntryPointNotFoundException */ +} MonoLookupPInvokeErr; + +/* We should just use a MonoError, but mono_lookup_pinvoke_call has this legacy + * error reporting mechanism where it returns an exception class and a string + * message. So instead we return an error code and message, and for internal + * callers convert it to a MonoError. + * + * Don't expose this type to the runtime. It's just an implementation + * detail for backward compatability. + */ +typedef struct MonoLookupPInvokeStatus { + MonoLookupPInvokeErr err_code; + char *err_arg; +} MonoLookupPInvokeStatus; + +/* Class lazy loading functions */ +GENERATE_GET_CLASS_WITH_CACHE (appdomain_unloaded_exception, "System", "AppDomainUnloadedException") +GENERATE_TRY_GET_CLASS_WITH_CACHE (appdomain_unloaded_exception, "System", "AppDomainUnloadedException") + +#ifndef ENABLE_NETCORE +/* + * LOCKING: Assumes the relevant lock is held. + * For the global DllMap, this is `global_loader_data_mutex`, and for images it's their internal lock. + */ +static int +mono_dllmap_lookup_list (MonoDllMap *dll_map, const char *dll, const char* func, const char **rdll, const char **rfunc) { + int found = 0; + + *rdll = dll; + + if (!dll_map) + return 0; + + mono_global_loader_data_lock (); + + /* + * we use the first entry we find that matches, since entries from + * the config file are prepended to the list and we document that the + * later entries win. + */ + for (; dll_map; dll_map = dll_map->next) { + if (dll_map->dll [0] == 'i' && dll_map->dll [1] == ':') { + if (g_ascii_strcasecmp (dll_map->dll + 2, dll)) + continue; + } else if (strcmp (dll_map->dll, dll)) { + continue; + } + if (!found && dll_map->target) { + *rdll = dll_map->target; + found = 1; + /* we don't quit here, because we could find a full + * entry that matches also function and that has priority. + */ + } + if (dll_map->func && strcmp (dll_map->func, func) == 0) { + *rdll = dll_map->target; + *rfunc = dll_map->target_func; + break; + } + } + + mono_global_loader_data_unlock (); + return found; +} + +static int +mono_dllmap_lookup (MonoImage *assembly, const char *dll, const char* func, const char **rdll, const char **rfunc) +{ + int res; + if (assembly && assembly->dll_map) { + res = mono_dllmap_lookup_list (assembly->dll_map, dll, func, rdll, rfunc); + if (res) + return res; + } + return mono_dllmap_lookup_list (global_dll_map, dll, func, rdll, rfunc); +} +#endif + +static void +dllmap_insert_global (const char *dll, const char *func, const char *tdll, const char *tfunc) +{ + MonoDllMap *entry; + + mono_loader_init (); + + entry = (MonoDllMap *)g_malloc0 (sizeof (MonoDllMap)); + entry->dll = dll? g_strdup (dll): NULL; + entry->target = tdll? g_strdup (tdll): NULL; + entry->func = func? g_strdup (func): NULL; + entry->target_func = tfunc? g_strdup (tfunc): (func? g_strdup (func): NULL); + + mono_global_loader_data_lock (); + entry->next = global_dll_map; + global_dll_map = entry; + mono_global_loader_data_unlock (); +} + +static void +dllmap_insert_image (MonoImage *assembly, const char *dll, const char *func, const char *tdll, const char *tfunc) +{ + MonoDllMap *entry; + g_assert (assembly != NULL); + + mono_loader_init (); + + entry = (MonoDllMap *)mono_image_alloc0 (assembly, sizeof (MonoDllMap)); + entry->dll = dll? mono_image_strdup (assembly, dll): NULL; + entry->target = tdll? mono_image_strdup (assembly, tdll): NULL; + entry->func = func? mono_image_strdup (assembly, func): NULL; + entry->target_func = tfunc? mono_image_strdup (assembly, tfunc): (func? mono_image_strdup (assembly, func): NULL); + + mono_image_lock (assembly); + entry->next = assembly->dll_map; + assembly->dll_map = entry; + mono_image_unlock (assembly); +} + +static void +free_dllmap (MonoDllMap *map) +{ + while (map) { + MonoDllMap *next = map->next; + + g_free (map->dll); + g_free (map->target); + g_free (map->func); + g_free (map->target_func); + g_free (map); + map = next; + } +} + +void +mono_dllmap_insert_internal (MonoImage *assembly, const char *dll, const char *func, const char *tdll, const char *tfunc) +{ + if (!assembly) + dllmap_insert_global (dll, func, tdll, tfunc); + else { + MONO_ENTER_GC_UNSAFE; + dllmap_insert_image (assembly, dll, func, tdll, tfunc); + MONO_EXIT_GC_UNSAFE; + } +} + +/** + * mono_dllmap_insert: + * \param assembly if NULL, this is a global mapping, otherwise the remapping of the dynamic library will only apply to the specified assembly + * \param dll The name of the external library, as it would be found in the \c DllImport declaration. If prefixed with i: the matching of the library name is done without case sensitivity + * \param func if not null, the mapping will only applied to the named function (the value of EntryPoint) + * \param tdll The name of the library to map the specified \p dll if it matches. + * \param tfunc The name of the function that replaces the invocation. If NULL, it is replaced with a copy of \p func. + * + * LOCKING: Acquires the loader lock. + * + * This function is used to programatically add \c DllImport remapping in either + * a specific assembly, or as a global remapping. This is done by remapping + * references in a \c DllImport attribute from the \p dll library name into the \p tdll + * name. If the \p dll name contains the prefix i:, the comparison of the + * library name is done without case sensitivity. + * + * If you pass \p func, this is the name of the \c EntryPoint in a \c DllImport if specified + * or the name of the function as determined by \c DllImport. If you pass \p func, you + * must also pass \p tfunc which is the name of the target function to invoke on a match. + * + * Example: + * + * mono_dllmap_insert (NULL, "i:libdemo.dll", NULL, relocated_demo_path, NULL); + * + * The above will remap \c DllImport statements for \c libdemo.dll and \c LIBDEMO.DLL to + * the contents of \c relocated_demo_path for all assemblies in the Mono process. + * + * NOTE: This can be called before the runtime is initialized, for example from + * \c mono_config_parse. + */ +void +mono_dllmap_insert (MonoImage *assembly, const char *dll, const char *func, const char *tdll, const char *tfunc) +{ + mono_dllmap_insert_internal (assembly, dll, func, tdll, tfunc); +} + +void +mono_global_dllmap_cleanup (void) +{ + free_dllmap (global_dll_map); + global_dll_map = NULL; +} + +static MonoDl* +cached_module_load (const char *name, int flags, char **err) +{ + MonoDl *res; + + if (err) + *err = NULL; + mono_global_loader_data_lock (); + if (!global_module_map) + global_module_map = g_hash_table_new (g_str_hash, g_str_equal); + res = (MonoDl *)g_hash_table_lookup (global_module_map, name); + if (res) { + mono_global_loader_data_unlock (); + return res; + } + res = mono_dl_open (name, flags, err); + if (res) + g_hash_table_insert (global_module_map, g_strdup (name), res); + mono_global_loader_data_unlock (); + return res; +} + +void +mono_loader_register_module (const char *name, MonoDl *module) +{ + if (!global_module_map) + global_module_map = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (global_module_map, g_strdup (name), module); +} + +static void +remove_cached_module (gpointer key, gpointer value, gpointer user_data) +{ + mono_dl_close((MonoDl*)value); +} + +void +mono_cached_module_cleanup (void) +{ + if (global_module_map != NULL) { + g_hash_table_foreach(global_module_map, remove_cached_module, NULL); + + g_hash_table_destroy(global_module_map); + global_module_map = NULL; + } +} + +static gboolean +is_absolute_path (const char *path) +{ +#ifdef HOST_DARWIN + if (!strncmp (path, "@executable_path/", 17) || !strncmp (path, "@loader_path/", 13) || + !strncmp (path, "@rpath/", 7)) + return TRUE; +#endif + return g_path_is_absolute (path); +} + +static gpointer +lookup_pinvoke_call_impl (MonoMethod *method, MonoLookupPInvokeStatus *status_out); + +static void +pinvoke_probe_convert_status_for_api (MonoLookupPInvokeStatus *status, const char **exc_class, const char **exc_arg) +{ + if (!exc_class) + return; + switch (status->err_code) { + case LOOKUP_PINVOKE_ERR_OK: + *exc_class = NULL; + *exc_arg = NULL; + break; + case LOOKUP_PINVOKE_ERR_NO_LIB: + *exc_class = "DllNotFoundException"; + *exc_arg = status->err_arg; + status->err_arg = NULL; + break; + case LOOKUP_PINVOKE_ERR_NO_SYM: + *exc_class = "EntryPointNotFoundException"; + *exc_arg = status->err_arg; + status->err_arg = NULL; + break; + default: + g_assert_not_reached (); + } +} + +static void +pinvoke_probe_convert_status_to_error (MonoLookupPInvokeStatus *status, MonoError *error) +{ + /* Note: this has to return a MONO_ERROR_GENERIC because mono_mb_emit_exception_for_error only knows how to decode generic errors. */ + switch (status->err_code) { + case LOOKUP_PINVOKE_ERR_OK: + return; + case LOOKUP_PINVOKE_ERR_NO_LIB: + mono_error_set_generic_error (error, "System", "DllNotFoundException", "%s", status->err_arg); + g_free (status->err_arg); + status->err_arg = NULL; + break; + case LOOKUP_PINVOKE_ERR_NO_SYM: + mono_error_set_generic_error (error, "System", "EntryPointNotFoundException", "%s", status->err_arg); + g_free (status->err_arg); + status->err_arg = NULL; + break; + default: + g_assert_not_reached (); + } +} + +/** + * mono_lookup_pinvoke_call: + */ +gpointer +mono_lookup_pinvoke_call (MonoMethod *method, const char **exc_class, const char **exc_arg) +{ + gpointer result; + MONO_ENTER_GC_UNSAFE; + MonoLookupPInvokeStatus status; + memset (&status, 0, sizeof (status)); + result = lookup_pinvoke_call_impl (method, &status); + pinvoke_probe_convert_status_for_api (&status, exc_class, exc_arg); + MONO_EXIT_GC_UNSAFE; + return result; +} + +static MonoDl* +pinvoke_probe_for_module (MonoImage *image, const char*new_scope, const char *import, char **found_name_out, char **error_msg_out); + +static MonoDl* +pinvoke_probe_for_module_relative_directories (MonoImage *image, const char *file_name, char **found_name_out); + +static gpointer +pinvoke_probe_for_symbol (MonoDl *module, MonoMethodPInvoke *piinfo, const char *import, char **error_msg_out); + +gpointer +mono_lookup_pinvoke_call_internal (MonoMethod *method, MonoError *error) +{ + gpointer result; + MonoLookupPInvokeStatus status; + memset (&status, 0, sizeof (status)); + result = lookup_pinvoke_call_impl (method, &status); + if (status.err_code) + pinvoke_probe_convert_status_to_error (&status, error); + return result; +} + +gpointer +lookup_pinvoke_call_impl (MonoMethod *method, MonoLookupPInvokeStatus *status_out) +{ + MonoImage *image = m_class_get_image (method->klass); + MonoMethodPInvoke *piinfo = (MonoMethodPInvoke *)method; + MonoTableInfo *tables = image->tables; + MonoTableInfo *im = &tables [MONO_TABLE_IMPLMAP]; + MonoTableInfo *mr = &tables [MONO_TABLE_MODULEREF]; + guint32 im_cols [MONO_IMPLMAP_SIZE]; + guint32 scope_token; + const char *import = NULL; + const char *orig_scope; + const char *new_scope; + char *error_msg; + char *found_name = NULL; + MonoDl *module = NULL; + gboolean cached = FALSE; + gpointer addr = NULL; + + g_assert (method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL); + + g_assert (status_out); + + if (piinfo->addr) + return piinfo->addr; + + if (image_is_dynamic (m_class_get_image (method->klass))) { + MonoReflectionMethodAux *method_aux = + (MonoReflectionMethodAux *)g_hash_table_lookup ( + ((MonoDynamicImage*)m_class_get_image (method->klass))->method_aux_hash, method); + if (!method_aux) + return NULL; + + import = method_aux->dllentry; + orig_scope = method_aux->dll; + } + else { + if (!piinfo->implmap_idx || piinfo->implmap_idx > im->rows) + return NULL; + + mono_metadata_decode_row (im, piinfo->implmap_idx - 1, im_cols, MONO_IMPLMAP_SIZE); + + if (!im_cols [MONO_IMPLMAP_SCOPE] || im_cols [MONO_IMPLMAP_SCOPE] > mr->rows) + return NULL; + + piinfo->piflags = im_cols [MONO_IMPLMAP_FLAGS]; + import = mono_metadata_string_heap (image, im_cols [MONO_IMPLMAP_NAME]); + scope_token = mono_metadata_decode_row_col (mr, im_cols [MONO_IMPLMAP_SCOPE] - 1, MONO_MODULEREF_NAME); + orig_scope = mono_metadata_string_heap (image, scope_token); + } + +#ifndef ENABLE_NETCORE + // FIXME: The dllmap remaps System.Native to mono-native + mono_dllmap_lookup (image, orig_scope, import, &new_scope, &import); +#else + /* AK: FIXME: dllmap, above doesn't strdup the results, so these leak + * since there's no free() */ + new_scope = g_strdup (orig_scope); + import = g_strdup (import); +#endif + + if (!module) { + mono_image_lock (image); + if (!image->pinvoke_scopes) { + image->pinvoke_scopes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + image->pinvoke_scope_filenames = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + } + module = (MonoDl *)g_hash_table_lookup (image->pinvoke_scopes, new_scope); + found_name = (char *)g_hash_table_lookup (image->pinvoke_scope_filenames, new_scope); + mono_image_unlock (image); + if (module) + cached = TRUE; + if (found_name) + found_name = g_strdup (found_name); + } + + if (!module) + module = pinvoke_probe_for_module (image, new_scope, import, &found_name, &error_msg); + + if (!module) { + mono_trace (G_LOG_LEVEL_WARNING, MONO_TRACE_DLLIMPORT, + "DllImport unable to load library '%s'.", + error_msg); + g_free (error_msg); + + status_out->err_code = LOOKUP_PINVOKE_ERR_NO_LIB; + status_out->err_arg = g_strdup (new_scope); + return NULL; + } + + if (!cached) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "DllImport loaded library '%s'.", found_name); + mono_image_lock (image); + if (!g_hash_table_lookup (image->pinvoke_scopes, new_scope)) { + g_hash_table_insert (image->pinvoke_scopes, g_strdup (new_scope), module); + g_hash_table_insert (image->pinvoke_scope_filenames, g_strdup (new_scope), g_strdup (found_name)); + } + mono_image_unlock (image); + } + + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "DllImport searching in: '%s' ('%s').", new_scope, found_name); + g_free (found_name); + + addr = pinvoke_probe_for_symbol (module, piinfo, import, &error_msg); + + if (!addr) { + g_free (error_msg); + status_out->err_code = LOOKUP_PINVOKE_ERR_NO_SYM; + status_out->err_arg = g_strdup (import); + return NULL; + } + piinfo->addr = addr; + return addr; +} + +/** + * pinvoke_probe_transform_path: + * + * Try transforming the library path given in \p new_scope in different ways + * depending on \p phase + * + * \returns \c TRUE if a transformation was applied and the transformed path + * components are written to the out arguments, or \c FALSE if a transformation + * did not apply. + */ +static gboolean +pinvoke_probe_transform_path (const char *new_scope, int phase, char **file_name_out, char **base_name_out, char **dir_name_out, gboolean *is_absolute_out) +{ + char *file_name = NULL, *base_name = NULL, *dir_name = NULL; + gboolean changed = FALSE; + gboolean is_absolute = is_absolute_path (new_scope); + switch (phase) { + case 0: + /* Try the original name */ + file_name = g_strdup (new_scope); + changed = TRUE; + break; + case 1: + /* Try trimming the .dll extension */ + if (strstr (new_scope, ".dll") == (new_scope + strlen (new_scope) - 4)) { + file_name = g_strdup (new_scope); + file_name [strlen (new_scope) - 4] = '\0'; + changed = TRUE; + } + break; + case 2: + if (is_absolute) { + dir_name = g_path_get_dirname (new_scope); + base_name = g_path_get_basename (new_scope); + if (strstr (base_name, "lib") != base_name) { + char *tmp = g_strdup_printf ("lib%s", base_name); + g_free (base_name); + base_name = tmp; + file_name = g_strdup_printf ("%s%s%s", dir_name, G_DIR_SEPARATOR_S, base_name); + changed = TRUE; + } + } else if (strstr (new_scope, "lib") != new_scope) { + file_name = g_strdup_printf ("lib%s", new_scope); + changed = TRUE; + } + break; + case 3: + if (!is_absolute && mono_dl_get_system_dir ()) { + dir_name = (char*)mono_dl_get_system_dir (); + file_name = g_path_get_basename (new_scope); + base_name = NULL; + changed = TRUE; + } + break; + default: +#ifndef TARGET_WIN32 + if (!g_ascii_strcasecmp ("user32.dll", new_scope) || + !g_ascii_strcasecmp ("kernel32.dll", new_scope) || + !g_ascii_strcasecmp ("user32", new_scope) || + !g_ascii_strcasecmp ("kernel", new_scope)) { + file_name = g_strdup ("libMonoSupportW.so"); + changed = TRUE; + } +#endif + break; + } + if (changed && is_absolute) { + if (!dir_name) + dir_name = g_path_get_dirname (file_name); + if (!base_name) + base_name = g_path_get_basename (file_name); + } + *file_name_out = file_name; + *base_name_out = base_name; + *dir_name_out = dir_name; + *is_absolute_out = is_absolute; + return changed; +} + +static MonoDl* +pinvoke_probe_for_module (MonoImage *image, const char *new_scope, const char *import, char **found_name_out, char **error_msg_out) +{ + char *full_name, *file_name; + char *error_msg = NULL; + char *found_name = NULL; + int i; + MonoDl *module = NULL; + + g_assert (found_name_out); + g_assert (error_msg_out); + + if (!module) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "DllImport attempting to load: '%s'.", new_scope); + + /* we allow a special name to dlopen from the running process namespace */ + if (strcmp (new_scope, "__Internal") == 0){ + if (internal_module == NULL) + internal_module = mono_dl_open (NULL, MONO_DL_LAZY, &error_msg); + module = internal_module; + } + } + + /* + * Try loading the module using a variety of names + */ + for (i = 0; i < 5; ++i) { + char *base_name = NULL, *dir_name = NULL; + gboolean is_absolute; + + gboolean changed = pinvoke_probe_transform_path (new_scope, i, &file_name, &base_name, &dir_name, &is_absolute); + if (!changed) + continue; + + if (!module && is_absolute) { + module = cached_module_load (file_name, MONO_DL_LAZY, &error_msg); + if (!module) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "DllImport error loading library '%s': '%s'.", + file_name, error_msg); + g_free (error_msg); + } else { + found_name = g_strdup (file_name); + } + } + + if (!module && !is_absolute) { + module = pinvoke_probe_for_module_relative_directories (image, file_name, &found_name); + } + + if (!module) { + void *iter = NULL; + char *file_or_base = is_absolute ? base_name : file_name; + while ((full_name = mono_dl_build_path (dir_name, file_or_base, &iter))) { + module = cached_module_load (full_name, MONO_DL_LAZY, &error_msg); + if (!module) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "DllImport error loading library '%s': '%s'.", + full_name, error_msg); + g_free (error_msg); + } else { + found_name = g_strdup (full_name); + } + g_free (full_name); + if (module) + break; + } + } + + if (!module) { + module = cached_module_load (file_name, MONO_DL_LAZY, &error_msg); + if (!module) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "DllImport error loading library '%s': '%s'.", + file_name, error_msg); + } else { + found_name = g_strdup (file_name); + } + } + + g_free (file_name); + if (is_absolute) { + g_free (base_name); + g_free (dir_name); + } + + if (module) + break; + } + + *found_name_out = found_name; + *error_msg_out = error_msg; + return module; +} + +#ifdef ENABLE_NETCORE +void +mono_set_pinvoke_search_directories (int dir_count, char **dirs) +{ + pinvoke_search_directories_count = dir_count; + pinvoke_search_directories = dirs; +} +#endif + +static MonoDl* +pinvoke_probe_for_module_in_directory (const char *mdirname, const char *file_name, char **found_name_out) +{ + void *iter = NULL; + char *full_name; + MonoDl* module = NULL; + + while ((full_name = mono_dl_build_path (mdirname, file_name, &iter)) && module == NULL) { + char *error_msg; + module = cached_module_load (full_name, MONO_DL_LAZY, &error_msg); + if (!module) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, "DllImport error loading library '%s': '%s'.", full_name, error_msg); + g_free (error_msg); + } else { + *found_name_out = g_strdup (full_name); + } + g_free (full_name); + } + g_free (full_name); + + return module; +} + +static MonoDl* +pinvoke_probe_for_module_relative_directories (MonoImage *image, const char *file_name, char **found_name_out) +{ + char *found_name = NULL; + MonoDl* module = NULL; + + g_assert (found_name_out); + +#if ENABLE_NETCORE + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "netcore DllImport handler: wanted '%s'", file_name); + + // Search in predefined directories first + for (int j = 0; j < pinvoke_search_directories_count && module == NULL; ++j) { + module = pinvoke_probe_for_module_in_directory (pinvoke_search_directories[j], file_name, &found_name); + } + + // Fallback to image directory + if (module == NULL) { + // TODO: Check DefaultDllImportSearchPathsAttribute, NativeLibrary callback + char *mdirname = g_path_get_dirname (image->name); + if (mdirname) + module = pinvoke_probe_for_module_in_directory (mdirname, file_name, &found_name); + g_free (mdirname); + } +#else + for (int j = 0; j < 3; ++j) { + char *mdirname = NULL; + switch (j) { + case 0: + mdirname = g_path_get_dirname (image->name); + break; + case 1: /* @executable_path@/../lib */ + { + char buf [4096]; + int binl; + binl = mono_dl_get_executable_path (buf, sizeof (buf)); + if (binl != -1) { + char *base, *newbase; + char *resolvedname; + buf [binl] = 0; + resolvedname = mono_path_resolve_symlinks (buf); + + base = g_path_get_dirname (resolvedname); + newbase = g_path_get_dirname(base); + + // On Android the executable for the application is going to be /system/bin/app_process{32,64} depending on + // the application's architecture. However, libraries for the different architectures live in different + // subdirectories of `/system`: `lib` for 32-bit apps and `lib64` for 64-bit ones. Thus appending `/lib` below + // will fail to load the DSO for a 64-bit app, even if it exists there, because it will have a different + // architecture. This is the cause of https://github.com/xamarin/xamarin-android/issues/2780 and the ifdef + // below is the fix. + mdirname = g_strdup_printf ( +#if defined(TARGET_ANDROID) && (defined(TARGET_ARM64) || defined(TARGET_AMD64)) + "%s/lib64", +#else + "%s/lib", +#endif + newbase); + g_free (resolvedname); + g_free (base); + g_free (newbase); + } + break; + } +#ifdef __MACH__ + case 2: /* @executable_path@/../Libraries */ + { + char buf [4096]; + int binl; + binl = mono_dl_get_executable_path (buf, sizeof (buf)); + if (binl != -1) { + char *base, *newbase; + char *resolvedname; + buf [binl] = 0; + resolvedname = mono_path_resolve_symlinks (buf); + + base = g_path_get_dirname (resolvedname); + newbase = g_path_get_dirname(base); + mdirname = g_strdup_printf ("%s/Libraries", newbase); + + g_free (resolvedname); + g_free (base); + g_free (newbase); + } + break; + } +#endif + } + + if (!mdirname) + continue; + + module = pinvoke_probe_for_module_in_directory (mdirname, file_name, &found_name); + g_free (mdirname); + if (module) + break; + } +#endif + + *found_name_out = found_name; + return module; +} + +static gpointer +pinvoke_probe_for_symbol (MonoDl *module, MonoMethodPInvoke *piinfo, const char *import, char **error_msg_out) +{ + char *error_msg = NULL; + gpointer addr = NULL; + + g_assert (error_msg_out); + +#ifdef HOST_WIN32 + if (import && import [0] == '#' && isdigit (import [1])) { + char *end; + long id; + + id = strtol (import + 1, &end, 10); + if (id > 0 && *end == '\0') + import++; + } +#endif + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "Searching for '%s'.", import); + + if (piinfo->piflags & PINVOKE_ATTRIBUTE_NO_MANGLE) { + error_msg = mono_dl_symbol (module, import, &addr); + } else { + /* + * Search using a variety of mangled names + */ + for (int mangle_stdcall = 0; mangle_stdcall <= 1 && addr == NULL; mangle_stdcall++) { +#if HOST_WIN32 && HOST_X86 + const int max_managle_param_count = (mangle_stdcall == 0) ? 0 : 256; +#else + const int max_managle_param_count = 0; +#endif + for (int mangle_charset = 0; mangle_charset <= 1 && addr == NULL; mangle_charset ++) { + for (int mangle_param_count = 0; mangle_param_count <= max_managle_param_count && addr == NULL; mangle_param_count += 4) { + + char *mangled_name = (char*)import; + switch (piinfo->piflags & PINVOKE_ATTRIBUTE_CHAR_SET_MASK) { + case PINVOKE_ATTRIBUTE_CHAR_SET_UNICODE: + /* Try the mangled name first */ + if (mangle_charset == 0) + mangled_name = g_strconcat (import, "W", (const char*)NULL); + break; + case PINVOKE_ATTRIBUTE_CHAR_SET_AUTO: +#ifdef HOST_WIN32 + if (mangle_charset == 0) + mangled_name = g_strconcat (import, "W", (const char*)NULL); +#else + /* Try the mangled name last */ + if (mangle_charset == 1) + mangled_name = g_strconcat (import, "A", (const char*)NULL); +#endif + break; + case PINVOKE_ATTRIBUTE_CHAR_SET_ANSI: + default: + /* Try the mangled name last */ + if (mangle_charset == 1) + mangled_name = g_strconcat (import, "A", (const char*)NULL); + break; + } + +#if HOST_WIN32 && HOST_X86 + /* Try the stdcall mangled name */ + /* + * gcc under windows creates mangled names without the underscore, but MS.NET + * doesn't support it, so we doesn't support it either. + */ + if (mangle_stdcall == 1) { + MonoMethod *method = &piinfo->method; + int param_count; + if (mangle_param_count == 0) + param_count = mono_method_signature_internal (method)->param_count * sizeof (gpointer); + else + /* Try brute force, since it would be very hard to compute the stack usage correctly */ + param_count = mangle_param_count; + + char *mangled_stdcall_name = g_strdup_printf ("_%s@%d", mangled_name, param_count); + + if (mangled_name != import) + g_free (mangled_name); + + mangled_name = mangled_stdcall_name; + } +#endif + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "Probing '%s'.", mangled_name); + + error_msg = mono_dl_symbol (module, mangled_name, &addr); + + if (addr) + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "Found as '%s'.", mangled_name); + else + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, + "Could not find '%s' due to '%s'.", mangled_name, error_msg); + + g_free (error_msg); + error_msg = NULL; + + if (mangled_name != import) + g_free (mangled_name); + } + } + } + } + + *error_msg_out = error_msg; + return addr; +} diff --git a/src/mono/msvc/libmonoruntime-common.targets b/src/mono/msvc/libmonoruntime-common.targets index b1cc248..4b59bea 100644 --- a/src/mono/msvc/libmonoruntime-common.targets +++ b/src/mono/msvc/libmonoruntime-common.targets @@ -106,6 +106,7 @@ + diff --git a/src/mono/msvc/libmonoruntime-common.targets.filters b/src/mono/msvc/libmonoruntime-common.targets.filters index 3457886..51903fe 100644 --- a/src/mono/msvc/libmonoruntime-common.targets.filters +++ b/src/mono/msvc/libmonoruntime-common.targets.filters @@ -544,6 +544,9 @@ Header Files$(MonoRuntimeFilterSubFolder)\common + + Source Files$(MonoRuntimeFilterSubFolder)\common + -- 2.7.4