From 238ea6506c059c7d7e37712e1af9d85046c41e7e Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 17 May 2018 11:51:12 +0200 Subject: [PATCH] Better mkbundle interop Xamarin.Android side of the Mono's mkbundle update which makes it easier (and safer) to introduce changes in the template generated by mkbundle when creating the application bundle. Code generated by mkbundle calls into the Mono runtime and in order for this to work on Xamarin.Android, libmonodroid needs to properly initialize the bundle or the application won't work (`dlsym` won't be able to find the Mono runtime symbols). This has been done by patching the generated code using plain search-and-replace which is very fragile even to the slightest changes and it doesn't guarantee that any new additions won't break the Xamarin.Android bundle (see https://github.com/xamarin/xamarin-android/issues/1651) Mono's mkbundle has been updated (see https://github.com/mono/mono/commit/170e9442c976ed6cd4a499093e11a9e12e7368bb) to support third parties, like Xamarin.Android, which need to use special methods to initialize the bundle. The third party provides a small bit of source code with a dispatch structure (mkbundle has its own default version of the structure) which contains pointers to all the Mono runtime methods required by the initialization code. The provided code is inserted by mkbundle in place of its default version. If there are any incompatibilities between the two structures (such as missing pointers, invalid signature, additional pointers etc) then the bundle will not build, thus allowing the third party to notice the problem early and update its version of the structure. Likewise, if the build succeeds but the pointers aren't correctly initialized (i.e. they are `null`), a runtime check will discover the fact and fail the application gracefully. The bit of inserted code also contains platform-specific logging function in place of the calls to `fprintf` used by mkbundle previously. For Xamarin.Android it means that all the error messages will end up in logcat. Xamarin.Android's version of the structure and the error logging function are found in the `mkbundle-api.h` C header which is used *both* when building libmonodroid (to ensure that when initializing the generated bundle libmonodroid is ABI-compatible with it) and is copied to the framework directory to be used when generating the application bundle. Sample of XA-generated bundle code: https://gist.github.com/grendello/465ecec96b3bee801e6bbc5a28019833 --- .../Tasks/MakeBundleNativeCodeExternal.cs | 25 +++++++---------- .../Xamarin.Android.Build.Tasks.csproj | 4 +++ .../Xamarin.Android.Common.targets | 7 ++--- src/monodroid/jni/dylib-mono.c | 2 +- src/monodroid/jni/dylib-mono.h | 2 ++ src/monodroid/jni/mkbundle-api.h | 27 +++++++++++++++++++ src/monodroid/jni/monodroid-glue.c | 22 ++++++++++++++- 7 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 src/monodroid/jni/mkbundle-api.h diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs index 916fc7f4ac6..698aeaab84c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs @@ -39,6 +39,9 @@ public class MakeBundleNativeCodeExternal : Task public bool EmbedDebugSymbols { get; set; } public bool KeepTemp { get; set; } + [Required] + public string BundleApiPath { get; set; } + [Output] public ITaskItem [] OutputNativeLibraries { get; set; } @@ -113,6 +116,8 @@ bool DoExecute () clb.AppendSwitch ("--nomain"); clb.AppendSwitch ("--i18n none"); clb.AppendSwitch ("--bundled-header"); + clb.AppendSwitch ("--mono-api-struct-path"); + clb.AppendFileNameIfNotNull (BundleApiPath); clb.AppendSwitch ("--style"); clb.AppendSwitch ("linux"); clb.AppendSwitch ("-c"); @@ -157,22 +162,6 @@ bool DoExecute () return false; } - Log.LogDebugMessage ("[mkbundle] modifying mono_mkbundle_init"); - // make some changes in the mkbundle output so that it does not require libmonodroid.so - var mkbundleOutput = new StringBuilder (File.ReadAllText (Path.Combine (outpath, "temp.c"))); - - mkbundleOutput.Replace ("mono_jit_set_aot_mode", "mono_jit_set_aot_mode_ptr") - .Replace ("void mono_mkbundle_init ()", "void mono_mkbundle_init (void (register_bundled_assemblies_func)(const MonoBundledAssembly **), void (register_config_for_assembly_func)(const char *, const char *), void (mono_jit_set_aot_mode_func) (int mode))") - .Replace ("mono_register_config_for_assembly (\"", "register_config_for_assembly_func (\"") - .Replace ("install_dll_config_files (void)", "install_dll_config_files (void (register_config_for_assembly_func)(const char *, const char *))") - .Replace ("install_dll_config_files ()", "install_dll_config_files (register_config_for_assembly_func)") - .Replace ("mono_register_bundled_assemblies(", "register_bundled_assemblies_func(") - .Replace ("int nbundles;", "int nbundles;\n\n\tmono_jit_set_aot_mode_ptr = mono_jit_set_aot_mode_func;"); - - mkbundleOutput.Insert (0, "void (*mono_jit_set_aot_mode_ptr) (int mode);\n"); - - File.WriteAllText (Path.Combine (outpath, "temp.c"), mkbundleOutput.ToString ()); - // then compile temp.c into temp.o and ... clb = new CommandLineBuilder (); @@ -182,6 +171,10 @@ bool DoExecute () // defined even if we don't use them clb.AppendSwitch ($"-D__ANDROID_API__={level}"); + // This is necessary because of the injected code, which is reused between libmonodroid + // and the bundle + clb.AppendSwitch ("-DANDROID"); + clb.AppendSwitch ("-o"); clb.AppendFileNameIfNotNull (Path.Combine (outpath, "temp.o")); if (!string.IsNullOrWhiteSpace (IncludePath)) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index 89133632461..cfa2b5b5215 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -574,6 +574,10 @@ LayoutBinding.cs PreserveNewest + + mkbundle-api.h + PreserveNewest + diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 80084f0bde8..9d93cbdbf64 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -303,9 +303,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. <_AndroidSequencePointsMode Condition=" '$(_AndroidSequencePointsMode)' == ''">None <_InstantRunEnabled Condition=" '$(_InstantRunEnabled)' == '' ">False <_AndroidBuildPropertiesCache>$(IntermediateOutputPath)build.props - False - + False @@ -2688,12 +2687,14 @@ because xbuild doesn't support framework reference assemblies. + ToolPath="$(_MonoAndroidToolsDirectory)" + BundleApiPath="$(MSBuildThisFileDirectory)\mkbundle-api.h"> diff --git a/src/monodroid/jni/dylib-mono.c b/src/monodroid/jni/dylib-mono.c index 8e1675a7042..67dd46b566d 100644 --- a/src/monodroid/jni/dylib-mono.c +++ b/src/monodroid/jni/dylib-mono.c @@ -139,7 +139,7 @@ int monodroid_dylib_mono_init (struct DylibMono *mono_imports, const char *libmo LOAD_SYMBOL(mono_thread_create) LOAD_SYMBOL(mono_thread_current) LOAD_SYMBOL(mono_use_llvm) - + LOAD_SYMBOL(mono_aot_register_module) if (symbols_missing) { log_fatal (LOG_DEFAULT, "Failed to load some Mono symbols, aborting..."); diff --git a/src/monodroid/jni/dylib-mono.h b/src/monodroid/jni/dylib-mono.h index 25cb828adfd..9d95e4fb7a3 100644 --- a/src/monodroid/jni/dylib-mono.h +++ b/src/monodroid/jni/dylib-mono.h @@ -254,6 +254,7 @@ typedef MonoThread* (*monodroid_mono_thread_current_fptr) (void); typedef void (*monodroid_mono_gc_disable_fptr) (void); typedef void* (*monodroid_mono_install_assembly_refonly_preload_hook_fptr) (MonoAssemblyPreLoadFunc func, void *user_data); typedef int (*monodroid_mono_runtime_set_main_args_fptr) (int argc, char* argv[]); +typedef void (*mono_aot_register_module_fptr) (void* aot_info); /* NOTE: structure members MUST NOT CHANGE ORDER. */ struct DylibMono { @@ -342,6 +343,7 @@ struct DylibMono { monodroid_mono_class_get_property_from_name_fptr mono_class_get_property_from_name; monodroid_mono_domain_from_appdomain_fptr mono_domain_from_appdomain; monodroid_mono_thread_current_fptr mono_thread_current; + mono_aot_register_module_fptr mono_aot_register_module; }; MONO_API struct DylibMono* monodroid_dylib_mono_new (const char *libmono_path); diff --git a/src/monodroid/jni/mkbundle-api.h b/src/monodroid/jni/mkbundle-api.h new file mode 100644 index 00000000000..aa833a70b00 --- /dev/null +++ b/src/monodroid/jni/mkbundle-api.h @@ -0,0 +1,27 @@ +#ifndef __MKBUNDLE_API_H +#define __MKBUNDLE_API_H +typedef struct BundleMonoAPI +{ + void (*mono_register_bundled_assemblies) (const MonoBundledAssembly **assemblies); + void (*mono_register_config_for_assembly) (const char* assembly_name, const char* config_xml); + void (*mono_jit_set_aot_mode) (int mode); + void (*mono_aot_register_module) (void* aot_info); + void (*mono_config_parse_memory) (const char *buffer); + void (*mono_register_machine_config) (const char *config_xml); +} BundleMonoAPI; + +#if ANDROID +#include +#include + +static void +mkbundle_log_error (const char *format, ...) +{ + va_list ap; + + va_start (ap, format); + __android_log_vprint (ANDROID_LOG_ERROR, "mkbundle", format, ap); + va_end (ap); +} +#endif // ANDROID +#endif // __MKBUNDLE_API_H diff --git a/src/monodroid/jni/monodroid-glue.c b/src/monodroid/jni/monodroid-glue.c index 61c7d23a3a2..31ba3d6af67 100644 --- a/src/monodroid/jni/monodroid-glue.c +++ b/src/monodroid/jni/monodroid-glue.c @@ -71,6 +71,7 @@ #include "unzip.h" #include "ioapi.h" #include "monodroid-glue.h" +#include "mkbundle-api.h" #ifndef WINDOWS #include "xamarin_getifaddrs.h" @@ -693,6 +694,7 @@ get_libmonosgen_path () typedef void* (*mono_mkbundle_init_ptr) (void (*)(const MonoBundledAssembly **), void (*)(const char* assembly_name, const char* config_xml),void (*) (int mode)); mono_mkbundle_init_ptr mono_mkbundle_init; +void (*mono_mkbundle_initialize_mono_api) (const BundleMonoAPI *info); static void setup_bundled_app (const char *libappso) @@ -705,7 +707,11 @@ setup_bundled_app (const char *libappso) log_fatal (LOG_BUNDLE, "bundled app initialization error: %s", dlerror ()); exit (FATAL_EXIT_CANNOT_LOAD_BUNDLE); } - + + mono_mkbundle_initialize_mono_api = dlsym (libapp, "initialize_mono_api"); + if (!mono_mkbundle_initialize_mono_api) + log_error (LOG_BUNDLE, "Missing initialize_mono_api in the application"); + mono_mkbundle_init = dlsym (libapp, "mono_mkbundle_init"); if (!mono_mkbundle_init) log_error (LOG_BUNDLE, "Missing mono_mkbundle_init in the application"); @@ -2647,6 +2653,20 @@ mono_runtime_init (char *runtime_args) register_gc_hooks (); + if (mono_mkbundle_initialize_mono_api) { + BundleMonoAPI bundle_mono_api = { + .mono_register_bundled_assemblies = mono.mono_register_bundled_assemblies, + .mono_register_config_for_assembly = mono.mono_register_config_for_assembly, + .mono_jit_set_aot_mode = mono.mono_jit_set_aot_mode, + .mono_aot_register_module = mono.mono_aot_register_module, + .mono_config_parse_memory = mono.mono_config_parse_memory, + .mono_register_machine_config = mono.mono_register_machine_config, + }; + + /* The initialization function copies the struct */ + mono_mkbundle_initialize_mono_api (&bundle_mono_api); + } + if (mono_mkbundle_init) mono_mkbundle_init (mono.mono_register_bundled_assemblies, mono.mono_register_config_for_assembly, mono.mono_jit_set_aot_mode);