diff --git a/.gitmodules b/.gitmodules index c9883e88b80..dae435f0c82 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,7 @@ path = external/xamarin-android-tools url = https://github.com/xamarin/xamarin-android-tools branch = main +[submodule "external/libunwind"] + path = external/libunwind + url = https://github.com/libunwind/libunwind.git + branch = v1.7-stable diff --git a/Configuration.props b/Configuration.props index 1a467652e80..0890b5b7103 100644 --- a/Configuration.props +++ b/Configuration.props @@ -104,6 +104,8 @@ True $(MSBuildThisFileDirectory)external\opentk $(MSBuildThisFileDirectory)external\sqlite + $(MSBuildThisFileDirectory)external\libunwind + $(BootstrapOutputDirectory)\libunwind $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)src-ThirdParty\ armeabi-v7a;x86 @@ -144,6 +146,8 @@ $([System.IO.Path]::GetFullPath ('$(JavaInteropSourceDirectory)')) $([System.IO.Path]::GetFullPath ('$(MonoSourceDirectory)')) $([System.IO.Path]::GetFullPath ('$(SqliteSourceDirectory)')) + $([System.IO.Path]::GetFullPath ('$(LibUnwindSourceDirectory)')) + $([System.IO.Path]::GetFullPath ('$(LibUnwindGeneratedHeadersDirectory)')) $([System.IO.Path]::GetFullPath ('$(OpenTKSourceDirectory)')) net8.0 diff --git a/TheoriesToTest.txt b/TheoriesToTest.txt new file mode 100644 index 00000000000..7517af47f8a --- /dev/null +++ b/TheoriesToTest.txt @@ -0,0 +1,4 @@ +* See if it's a threading issue - add full locking to `get_function_pointer` instead of atomics that we use now +* See if it's an ALC issue - call `get_function_pointer` every time and check the returned pointer value +* Implement Jon's idea to always have the "old" dynamic registration stuff in JCW and only use it when some flag + is set and marshal methods are active. diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index 50918d1e4e3..adb1f590ba4 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -47,6 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.Diagnost EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.Cecil", "external\Java.Interop\src\Java.Interop.Tools.Cecil\Java.Interop.Tools.Cecil.csproj", "{D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libunwind", "src\libunwind-xamarin\libunwind-xamarin.csproj", "{F8E4961B-C427-47F9-92D6-0BEB5B76B3D7}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "monodroid", "src\monodroid\monodroid.csproj", "{53EE4C57-1C03-405A-8243-8DA539546C88}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483}" diff --git a/build-tools/cmake/xa_macros.cmake b/build-tools/cmake/xa_macros.cmake index 58e75192bfd..24812dfd82b 100644 --- a/build-tools/cmake/xa_macros.cmake +++ b/build-tools/cmake/xa_macros.cmake @@ -200,7 +200,8 @@ function(xa_common_prepare) -fstack-protector-strong -fstrict-return -fno-strict-aliasing - -ffunction-sections + -fno-function-sections + -fno-data-sections -funswitch-loops -finline-limit=300 -Wa,-noexecstack diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 8982eb6f911..41fbd68991d 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -41,6 +41,8 @@ projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmono-android.debug.so" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmono-android.release.so" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libxamarin-debug-app-helper.so" /> + <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmarshal-methods-tracing.a" /> + <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libxamarin-native-tracing.so" /> diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index dbd3982b5bf..562180003e0 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -170,12 +170,20 @@ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)ManifestOverlays\Timing.xml" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libc.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libm.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libdl.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\liblog.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libc.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libm.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libdl.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\liblog.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libc.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libm.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libdl.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\liblog.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libc.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libm.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libdl.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\liblog.so" /> <_MSBuildTargetsSrcFiles Include="$(MSBuildTargetsSrcDir)\Xamarin.Android.AvailableItems.targets" /> diff --git a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs index 2d9fd7efcfa..32f56aa57ff 100644 --- a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs +++ b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs @@ -38,6 +38,8 @@ static class KnownProperties public const string JavaInteropFullPath = "JavaInteropFullPath"; public const string JavaSdkDirectory = "JavaSdkDirectory"; public const string JdkIncludePath = "JdkIncludePath"; + public const string LibUnwindGeneratedHeadersFullPath = nameof (LibUnwindGeneratedHeadersFullPath); + public const string LibUnwindSourceFullPath = nameof (LibUnwindSourceFullPath); public const string LibZipSourceFullPath = "LibZipSourceFullPath"; public const string ManagedRuntime = "ManagedRuntime"; public const string MicrosoftAndroidSdkOutDir = "MicrosoftAndroidSdkOutDir"; diff --git a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in index 6e59af3ab6e..39b44422eb7 100644 --- a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in +++ b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in @@ -42,6 +42,8 @@ namespace Xamarin.Android.Prepare properties.Add (KnownProperties.JavaInteropFullPath, StripQuotes (@"@JavaInteropFullPath@")); properties.Add (KnownProperties.JavaSdkDirectory, StripQuotes (@"@JavaSdkDirectory@")); properties.Add (KnownProperties.JdkIncludePath, StripQuotes (@"@JdkIncludePath@")); + properties.Add (KnownProperties.LibUnwindGeneratedHeadersFullPath, StripQuotes (@"@LibUnwindGeneratedHeadersFullPath@")); + properties.Add (KnownProperties.LibUnwindSourceFullPath, StripQuotes (@"@LibUnwindSourceFullPath")); properties.Add (KnownProperties.LibZipSourceFullPath, StripQuotes (@"@LibZipSourceFullPath@")); properties.Add (KnownProperties.ManagedRuntime, StripQuotes (@"@ManagedRuntime@")); properties.Add (KnownProperties.MicrosoftAndroidSdkOutDir, StripQuotes (@"@MicrosoftAndroidSdkOutDir@")); diff --git a/build-tools/xaprepare/xaprepare/xaprepare.targets b/build-tools/xaprepare/xaprepare/xaprepare.targets index 4c8e3be8906..dc8465d148b 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.targets +++ b/build-tools/xaprepare/xaprepare/xaprepare.targets @@ -75,6 +75,7 @@ + diff --git a/external/libunwind b/external/libunwind new file mode 160000 index 00000000000..688caaf6ef9 --- /dev/null +++ b/external/libunwind @@ -0,0 +1 @@ +Subproject commit 688caaf6ef9853cc26ad8bd1706804d48a0df0bc diff --git a/java.interop-tracing.diff b/java.interop-tracing.diff new file mode 100644 index 00000000000..bb00e359fa4 --- /dev/null +++ b/java.interop-tracing.diff @@ -0,0 +1,213 @@ +diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs +index 76f2bfc0..7a57f6b2 100644 +--- a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs ++++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs +@@ -133,6 +133,7 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + JavaCallableWrapperGenerator (TypeDefinition type, string? outerType, Action log, IMetadataResolver resolver, JavaCallableMethodClassifier? methodClassifier = null) + { ++ Console.WriteLine ($"JCWG: processing {type}"); + this.methodClassifier = methodClassifier; + this.type = type; + this.log = log; +@@ -224,6 +225,8 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + curCtors = new List (); + AddConstructors (ctorTypes [i], outerType, baseCtors, curCtors, false); + } ++ ++ Console.WriteLine ($"JCWG: done processing {type}"); + } + + static void ExtractJavaNames (string jniName, out string package, out string type) +@@ -285,8 +288,10 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + void AddConstructor (MethodDefinition ctor, TypeDefinition type, string? outerType, List? baseCtors, List curCtors, bool onlyRegisteredOrExportedCtors, bool skipParameterCheck) + { ++ Console.WriteLine ($" JCWG: AddConstructor ({ctor}, {type}, ..., onlyRegisteredOrExportedCtors: {onlyRegisteredOrExportedCtors}, skipParameterCheck: {skipParameterCheck});"); + string managedParameters = GetManagedParameters (ctor, outerType); + if (!skipParameterCheck && (managedParameters == null || ctors.Any (c => c.ManagedParameters == managedParameters))) { ++ Console.WriteLine (" JCWG: skip #1"); + return; + } + +@@ -297,28 +302,38 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + } + ctors.Add (new Signature (ctor, eattr, cache)); + curCtors.Add (ctor); ++ Console.WriteLine (" JCWG: add #1"); + return; + } + + RegisterAttribute rattr = GetMethodRegistrationAttributes (ctor).FirstOrDefault (); + if (rattr != null) { +- if (ctors.Any (c => c.JniSignature == rattr.Signature)) ++ if (ctors.Any (c => c.JniSignature == rattr.Signature)) { ++ Console.WriteLine (" JCWG: skip #2"); + return; ++ } + ctors.Add (new Signature (ctor, rattr, managedParameters, outerType, cache)); + curCtors.Add (ctor); ++ Console.WriteLine (" JCWG: add #2"); + return; + } + +- if (onlyRegisteredOrExportedCtors) ++ if (onlyRegisteredOrExportedCtors) { ++ Console.WriteLine (" JCWG: skip #1"); + return; ++ } + + string? jniSignature = GetJniSignature (ctor, cache); + +- if (jniSignature == null) ++ if (jniSignature == null) { ++ Console.WriteLine (" JCWG: skip #3"); + return; ++ } + +- if (ctors.Any (c => c.JniSignature == jniSignature)) ++ if (ctors.Any (c => c.JniSignature == jniSignature)) { ++ Console.WriteLine (" JCWG: skip #4"); + return; ++ } + + if (baseCtors == null) { + throw new InvalidOperationException ("`baseCtors` should not be null!"); +@@ -327,11 +342,13 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + if (baseCtors.Any (m => m.Parameters.AreParametersCompatibleWith (ctor.Parameters, cache))) { + ctors.Add (new Signature (".ctor", jniSignature, "", managedParameters, outerType, null)); + curCtors.Add (ctor); ++ Console.WriteLine (" JCWG: add #3"); + return; + } + if (baseCtors.Any (m => !m.HasParameters)) { + ctors.Add (new Signature (".ctor", jniSignature, "", managedParameters, outerType, "")); + curCtors.Add (ctor); ++ Console.WriteLine (" JCWG: add #4"); + return; + } + } +@@ -549,17 +566,17 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + GenerateHeader (writer); + + bool needCtor = false; +- if (HasDynamicallyRegisteredMethods) { ++// if (HasDynamicallyRegisteredMethods) { + needCtor = true; + writer.WriteLine ("/** @hide */"); + writer.WriteLine ("\tpublic static final String __md_methods;"); +- } ++// } + + if (children != null) { + for (int i = 0; i < children.Count; i++) { +- if (!children[i].HasDynamicallyRegisteredMethods) { +- continue; +- } ++ // if (!children[i].HasDynamicallyRegisteredMethods) { ++ // continue; ++ // } + needCtor = true; + writer.Write ("\tstatic final String __md_"); + writer.Write (i + 1); +@@ -570,9 +587,9 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + if (needCtor) { + writer.WriteLine ("\tstatic {"); + +- if (HasDynamicallyRegisteredMethods) { ++// if (HasDynamicallyRegisteredMethods) { + GenerateRegisterType (writer, this, "__md_methods"); +- } ++// } + + if (children != null) { + for (int i = 0; i < children.Count; ++i) { +@@ -687,9 +704,12 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + void GenerateBody (TextWriter sw) + { ++ Console.WriteLine ($" JCWG: GenerateBody for {name} (ctors size: {ctors.Count})"); + foreach (Signature ctor in ctors) { +- if (string.IsNullOrEmpty (ctor.Params) && JavaNativeTypeManager.IsApplication (type, cache)) ++ if (string.IsNullOrEmpty (ctor.Params) && JavaNativeTypeManager.IsApplication (type, cache)) { ++ Console.WriteLine ($" JCWG: skipping constructor generation; params empty? {string.IsNullOrEmpty (ctor.Params)}; is application? {JavaNativeTypeManager.IsApplication (type, cache)}"); + continue; ++ } + GenerateConstructor (ctor, sw); + } + +@@ -745,9 +765,9 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + void GenerateRegisterType (TextWriter sw, JavaCallableWrapperGenerator self, string field) + { +- if (!self.HasDynamicallyRegisteredMethods) { +- return; +- } ++ // if (!self.HasDynamicallyRegisteredMethods) { ++ // return; ++ // } + + sw.Write ("\t\t"); + sw.Write (field); +@@ -818,9 +838,13 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + return jniName.Replace ('/', '.').Replace ('$', '.'); + } + +- bool CannotRegisterInStaticConstructor (TypeDefinition type) ++ bool CannotRegisterInStaticConstructor (TypeDefinition type, bool dolog = false) + { +- return JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache); ++ Console.WriteLine ($" JCWG: CannotRegisterInStaticConstructor ({type})"); ++ bool ret = JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache); ++ Console.WriteLine ($" JCWG: CannotRegisterInStaticConstructor for type {type}; ret == {ret}; IsApplication: {JavaNativeTypeManager.IsApplication (type, cache)}; IsInstrumentation: {JavaNativeTypeManager.IsInstrumentation (type, cache)}"); ++ ++ return ret; + } + + class Signature { +@@ -941,6 +965,8 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + void GenerateConstructor (Signature ctor, TextWriter sw) + { ++ Console.WriteLine ($" JCWG: GenerateConstructor for {name}"); ++ + // TODO: we only generate constructors so that Android types w/ no + // default constructor can be subclasses by our generated code. + // +diff --git a/tools/java-source-utils/.classpath b/tools/java-source-utils/.classpath +index 30230c1c..cbded110 100644 +--- a/tools/java-source-utils/.classpath ++++ b/tools/java-source-utils/.classpath +@@ -20,6 +20,12 @@ + + + ++ ++ ++ ++ ++ ++ + + + +diff --git a/tools/java-source-utils/.project b/tools/java-source-utils/.project +index 8337a1c5..613733c8 100644 +--- a/tools/java-source-utils/.project ++++ b/tools/java-source-utils/.project +@@ -20,4 +20,15 @@ + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + ++ ++ ++ 1682588570543 ++ ++ 30 ++ ++ org.eclipse.core.resources.regexFilterMatcher ++ node_modules|.metadata|archetype-resources|META-INF/maven|__CREATED_BY_JAVA_LANGUAGE_SERVER__ ++ ++ ++ + diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index d7876273983..60b7b52eae0 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -491,8 +491,11 @@ public void RegisterNativeMembers ( { try { if (methods.IsEmpty) { - if (jniAddNativeMethodRegistrationAttributePresent) + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNatives: Type {type.FullName} registers no methods"); + if (jniAddNativeMethodRegistrationAttributePresent) { + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNatives: Type {type.FullName} has [AddNativeMethodRegistration] attribute"); base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); + } return; } else if (FastRegisterNativeMembers (nativeClass, type, methods)) { return; @@ -539,6 +542,7 @@ public void RegisterNativeMembers ( throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}")); callback = CreateDynamicCallback (minfo); needToRegisterNatives = true; + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNativesMethod: [ ] {type.FullName}: __export__ found for method '{name.ToString ()}'"); } else { Type callbackDeclaringType = type; if (!callbackDeclaringTypeString.IsEmpty) { @@ -556,6 +560,7 @@ public void RegisterNativeMembers ( if (callback != null) { needToRegisterNatives = true; natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNativesMethod: [ ] {type.FullName}.{name.ToString ()}"); } } @@ -563,20 +568,12 @@ public void RegisterNativeMembers ( } if (needToRegisterNatives) { + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNatives: Type {type.FullName} calling JniEnvironment.Types.RegisterNatives"); JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); } } catch (Exception e) { JniEnvironment.Runtime.RaisePendingException (e); } - - bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, string typeName, string callbackName) - { - if (String.Compare (typeName, callbackTypeName, StringComparison.Ordinal) != 0) { - return false; - } - - return String.Compare (callbackName, callbackString, StringComparison.Ordinal) == 0; - } } static int CountMethods (ReadOnlySpan methodsSpan) diff --git a/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs b/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs index 5eb3b04bd87..795ed57d217 100644 --- a/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs +++ b/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs @@ -8,11 +8,18 @@ public sealed class InputStreamAdapter : Java.IO.InputStream { public Stream BaseStream {get; private set;} + InputStreamAdapter () { + Console.WriteLine ("InputStreamAdapter () invoked"); + Console.WriteLine (new System.Diagnostics.StackTrace(true)); + } + public InputStreamAdapter (System.IO.Stream stream) : base ( JNIEnv.StartCreateInstance ("mono/android/runtime/InputStreamAdapter", "()V"), JniHandleOwnership.TransferLocalRef) { + Console.WriteLine ("InputStreamAdapter (System.IO.Stream) invoked"); + Console.WriteLine (new System.Diagnostics.StackTrace(true)); JNIEnv.FinishCreateInstance (Handle, "()V"); this.BaseStream = stream; @@ -54,4 +61,3 @@ public static IntPtr ToLocalJniHandle (Stream? value) } } } - diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 2f65464716b..22ba4dc13f3 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -76,6 +76,9 @@ static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, In [UnmanagedCallersOnly] internal static unsafe void Initialize (JnienvInitializeArgs* args) { +#if NETCOREAPP + RuntimeNativeMethods.monodroid_log_traces (TraceKind.All, "In JNIEnvInit.Initialize"); +#endif IntPtr total_timing_sequence = IntPtr.Zero; IntPtr partial_timing_sequence = IntPtr.Zero; diff --git a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs index a0c936c59ff..da635032800 100644 --- a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs +++ b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs @@ -6,8 +6,23 @@ namespace Android.Runtime { + // Values must be identical to those in src/monodroid/jni/monodroid-glue-internal.hh + [Flags] + enum TraceKind : uint + { + Java = 0x01, + Managed = 0x02, + Native = 0x04, + Signals = 0x08, + + All = Java | Managed | Native | Signals, + } + internal static class RuntimeNativeMethods { + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void monodroid_log_traces (TraceKind kind, string first_line); + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index fb4e87216a4..924a1d02ef3 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -224,8 +224,10 @@ static Exception CreateJavaLocationException () if (!JNIEnvInit.IsRunningOnDesktop) { // Miss message is logged in the native runtime - if (JNIEnvInit.LogAssemblyCategory) + if (JNIEnvInit.LogAssemblyCategory) { JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Assembly, CreateJavaLocationException ().ToString ()); + } return null; } diff --git a/src/Mono.Android/Java.Lang/Thread.cs b/src/Mono.Android/Java.Lang/Thread.cs index 8c6155f41d3..a17aeebc16f 100644 --- a/src/Mono.Android/Java.Lang/Thread.cs +++ b/src/Mono.Android/Java.Lang/Thread.cs @@ -41,7 +41,15 @@ public void Run () Dispose (); } - static Dictionary instances = new Dictionary (); + static Dictionary instances;// = new Dictionary (); + + static RunnableImplementor () + { + Console.WriteLine ("RunnableImplementor.cctor (): begin"); + Console.WriteLine (new System.Diagnostics.StackTrace(true)); + instances = new (); + Console.WriteLine ("RunnableImplementor.cctor (): end"); + } public static RunnableImplementor Remove (Action handler) { @@ -65,4 +73,3 @@ public Thread (Action runHandler, string threadName) : this (new RunnableImpleme public Thread (ThreadGroup group, Action runHandler, string threadName, long stackSize) : this (group, new RunnableImplementor (runHandler), threadName, stackSize) {} } } - diff --git a/src/Mono.Android/java/mono/android/TypeManager.java b/src/Mono.Android/java/mono/android/TypeManager.java index bf64780bf9e..ab3c1beeef9 100644 --- a/src/Mono.Android/java/mono/android/TypeManager.java +++ b/src/Mono.Android/java/mono/android/TypeManager.java @@ -1,20 +1,26 @@ package mono.android; +//#NOTE: make sure the `#FEATURE=*:START` and `#FEATURE=*:END` lines for different values of FEATURE don't overlap or interlace. +//#NOTE: Each FEATURE block should be self-contained, with no other FEATURE blocks nested. +//#NOTE: This is because the code that skips over those FEATURE blocks is VERY primitive (see the `CreateTypeManagerJava` task in XABT) public class TypeManager { public static void Activate (String typeName, String sig, Object instance, Object[] parameterList) { +//#FEATURE=CALL_TRACING:START - do not remove or modify this line, it is required during application build + android.util.Log.i ("monodroid-trace", String.format ("java: activating type: '%s' [%s]", typeName, sig)); +//#FEATURE=CALL_TRACING:END - do not remove or modify this line, it is required during application build n_activate (typeName, sig, instance, parameterList); } private static native void n_activate (String typeName, String sig, Object instance, Object[] parameterList); -//#MARSHAL_METHODS:START - do not remove or modify this line, it is required during application build static { - String methods = + String methods = +//#FEATURE=MARSHAL_METHODS:START - do not remove or modify this line, it is required during application build "n_activate:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V:GetActivateHandler\n" + +//#FEATURE=MARSHAL_METHODS:END - do not remove or modify this line, it is required during application build ""; mono.android.Runtime.register ("Java.Interop.TypeManager+JavaTypeManager, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", TypeManager.class, methods); } -//#MARSHAL_METHODS:END - do not remove or modify this line, it is required during application build } diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index ae35d401aee..1d689da575f 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -224,7 +224,8 @@ _ResolveAssemblies MSBuild target. InputLibraries="@(_ResolvedNativeLibraries)" ExcludedLibraries="@(_MonoExcludedLibraries)" Components="@(_MonoComponent->Distinct())" - IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)"> + IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)" + IncludeNativeTracing="$(_UseNativeStackTraces)"> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets index 8504ad98ec8..8de29b960d5 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets @@ -45,6 +45,18 @@ false + + + + <_AndroidMarshalMethodsTracingMode Condition=" '$(_AndroidMarshalMethodsTracingMode)' == '' ">none + <_AndroidUseNativeStackTraces Condition=" '$(_AndroidUseNativeStackTraces)' == '' And '$(_AndroidMarshalMethodsTracingMode)' == 'none' ">false + <_AndroidUseNativeStackTraces Condition=" '$(_AndroidUseNativeStackTraces)' == '' And '$(_AndroidMarshalMethodsTracingMode)' != 'none' ">true + <_UseNativeStackTraces Condition=" '$(_AndroidUseNativeStackTraces)' == 'true' Or '$(_AndroidMarshalMethodsTracingMode)' != 'none' ">true + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs index 37872f31927..87309b463d9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs @@ -15,6 +15,9 @@ public class CreateTypeManagerJava : AndroidTask [Required] public string ResourceName { get; set; } + public bool CallTracingEnabled { get; set; } + public bool MarshalMethodsEnabled { get; set; } + [Required] public string OutputFilePath { get; set; } @@ -31,12 +34,18 @@ public override bool RunTask () var result = new StringBuilder (); bool ignoring = false; foreach (string line in content.Split ('\n')) { + if (SkipNoteLine (line)) { + continue; + } + if (!ignoring) { - if (ignoring = line.StartsWith ("//#MARSHAL_METHODS:START", StringComparison.Ordinal)) { + ignoring = StartIgnoring (line, out bool skipLine); + if (ignoring || skipLine) { continue; } + result.AppendLine (line); - } else if (line.StartsWith ("//#MARSHAL_METHODS:END", StringComparison.Ordinal)) { + } else if (EndIgnoring (line)) { ignoring = false; } } @@ -62,6 +71,38 @@ public override bool RunTask () return !Log.HasLoggedErrors; } + bool SkipNoteLine (string l) => l.Trim ().StartsWith ("//#NOTE:"); + + bool StartIgnoring (string l, out bool skipLine) + { + string line = l.Trim (); + skipLine = true; + if (MarshalMethodsEnabled && line.StartsWith ("//#FEATURE=MARSHAL_METHODS:START", StringComparison.Ordinal)) { + return true; + } + + if (!CallTracingEnabled && line.StartsWith ("//#FEATURE=CALL_TRACING:START", StringComparison.Ordinal)) { + return true; + } + + skipLine = line.StartsWith ("//#FEATURE", StringComparison.Ordinal); + return false; + } + + bool EndIgnoring (string l) + { + string line = l.Trim (); + if (MarshalMethodsEnabled && line.StartsWith ("//#FEATURE=MARSHAL_METHODS:END", StringComparison.Ordinal)) { + return true; + } + + if (!CallTracingEnabled && line.StartsWith ("//#FEATURE=CALL_TRACING:END", StringComparison.Ordinal)) { + return true; + } + + return false; + } + string? ReadResource (string resourceName) { using (var from = ExecutingAssembly.GetManifestResourceStream (resourceName)) { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 453885d1758..50f4c54ddaf 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -229,7 +229,7 @@ void Run (XAAssemblyResolver res, bool useMarshalMethods) MarshalMethodsClassifier classifier = null; if (useMarshalMethods) { - classifier = new MarshalMethodsClassifier (cache, res, Log); + classifier = new MarshalMethodsClassifier (cache, res, Log, IntermediateOutputDirectory); } // Step 2 - Generate Java stub code @@ -374,9 +374,9 @@ void Run (XAAssemblyResolver res, bool useMarshalMethods) foreach (JavaType jt in javaTypes) { TypeDefinition type = jt.Type; if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) { - if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) { - continue; - } + // if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) { + // continue; + // } string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'); regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);", @@ -392,6 +392,7 @@ void Run (XAAssemblyResolver res, bool useMarshalMethods) if (useMarshalMethods) { classifier.AddSpecialCaseMethods (); + classifier.FlushAndCloseOutputs (); Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}"); @@ -485,7 +486,7 @@ bool CreateJavaSources (IEnumerable newJavaTypes, TypeDefinitionCache using (var writer = MemoryStreamPool.Shared.CreateStreamWriter ()) { try { var jcw_type = CecilImporter.CreateType (t, cache, reader_options); - + jcw_type.Generate (writer, writer_options); if (useMarshalMethods) { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 101f70f84c6..58b8fa7ed16 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -23,6 +23,7 @@ public class GeneratePackageManagerJava : AndroidTask public override string TaskPrefix => "GPM"; Guid buildId = Guid.NewGuid (); + MarshalMethodsTracingMode mmTracingMode; [Required] public ITaskItem[] ResolvedAssemblies { get; set; } @@ -66,6 +67,7 @@ public class GeneratePackageManagerJava : AndroidTask public bool InstantRunEnabled { get; set; } public bool EnableMarshalMethods { get; set; } + public string MarshalMethodsTracingMode { get; set; } public string RuntimeConfigBinFilePath { get; set; } public string BoundExceptionType { get; set; } @@ -91,6 +93,8 @@ bool _Debug { public override bool RunTask () { + mmTracingMode = MonoAndroidHelper.ParseMarshalMethodsTracingMode (MarshalMethodsTracingMode); + BuildId = buildId.ToString (); Log.LogDebugMessage (" [Output] BuildId: {0}", BuildId); @@ -389,7 +393,8 @@ void AddEnvironment () Log, assemblyCount, uniqueAssemblyNames, - marshalMethodsState?.MarshalMethods + marshalMethodsState?.MarshalMethods, + mmTracingMode ); } else { marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (Log, assemblyCount, uniqueAssemblyNames); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index 5f4b09ececa..1d8b17327ec 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -44,8 +44,16 @@ sealed class InputFiles [Required] public string AndroidBinUtilsDirectory { get; set; } + public string MarshalMethodsTracingMode { get; set; } + + MarshalMethodsTracingMode mmTracingMode; + bool mmTracingEnabled; + public override System.Threading.Tasks.Task RunTaskAsync () { + mmTracingMode = MonoAndroidHelper.ParseMarshalMethodsTracingMode (MarshalMethodsTracingMode); + mmTracingEnabled = mmTracingMode != Tasks.MarshalMethodsTracingMode.None; + return this.WhenAll (GetLinkerConfigs (), RunLinker); } @@ -122,6 +130,7 @@ IEnumerable GetLinkerConfigs () abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, runtimeNativeLibStubsDir); } + // "--eh-frame-hdr " + const string commonLinkerArgs = "--shared " + "--allow-shlib-undefined " + @@ -134,7 +143,7 @@ IEnumerable GetLinkerConfigs () "--warn-shared-textrel " + "--fatal-warnings"; - string stripSymbolsArg = DebugBuild ? String.Empty : " -s"; + string stripSymbolsArg = DebugBuild || mmTracingEnabled ? String.Empty : " -s"; string ld = Path.Combine (AndroidBinUtilsDirectory, MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, "ld")); var targetLinkerArgs = new List (); @@ -208,6 +217,11 @@ InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem "-lc", }; + if (mmTracingEnabled) { + extraLibraries.Add (Path.Combine (runtimeLibsDir, "libmarshal-methods-tracing.a")); + extraLibraries.Add ("-lxamarin-native-tracing"); + } + return new InputFiles { OutputSharedLibrary = runtimeSharedLibrary, ObjectFiles = GetItemsForABI (abi, objectFiles), diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs index 4970875d659..30a57d1398b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs @@ -30,6 +30,7 @@ public class ProcessNativeLibraries : AndroidTask public string [] ExcludedLibraries { get; set; } public bool IncludeDebugSymbols { get; set; } + public bool IncludeNativeTracing { get; set; } [Output] public ITaskItem [] OutputLibraries { get; set; } @@ -82,6 +83,9 @@ public override bool RunTask () if (!wantedComponents.Contains (fileName)) { continue; } + } else if (!IncludeNativeTracing && String.Compare ("libxamarin-native-tracing", fileName, StringComparison.Ordinal) == 0) { + Log.LogDebugMessage ($"Excluding '{library.ItemSpec}' because native stack traces support is disabled"); + continue; } else if (ExcludedLibraries != null && ExcludedLibraries.Contains (fileName, StringComparer.OrdinalIgnoreCase)) { Log.LogDebugMessage ($"Excluding '{library.ItemSpec}'"); continue; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index cee11386b4a..e0109172d26 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -7,6 +7,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.Android.Build.Tasks; + using Xamarin.Android.Tasks.LLVMIR; namespace Xamarin.Android.Tasks @@ -298,7 +299,6 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob if (entry == null) { throw new InvalidOperationException ($"Internal error: DSO cache entry has unexpected type {instance.Obj.GetType ()}"); } - entry.hash = MonoAndroidHelper.GetXxHash (entry.HashedName, is64Bit); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs index dd6d0924fff..375025bfeb9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs @@ -210,6 +210,8 @@ public JniRemappingAssemblyGenerator (TaskLoggingHelper log, List> (); + Console.WriteLine ($"Type replacement input count: {typeReplacementsInput.Count}"); + foreach (JniRemappingTypeReplacement mtr in typeReplacementsInput) { var entry = new JniRemappingTypeReplacementEntry { name = MakeJniRemappingString (mtr.From), diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 96063bf2126..01c7412811a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -299,6 +299,12 @@ void GenerateNonBlittableConversion (TypeReference sourceType, TypeReference tar return; } + if (IsCharConversion (sourceType, targetType)) { + // No need to generate any code. Java's `char` is unsigned 16-bit type, the same as + // .NET's + return; + } + ThrowUnsupportedType (sourceType); } @@ -332,6 +338,19 @@ bool IsBooleanConversion (TypeReference sourceType, TypeReference targetType) return false; } + bool IsCharConversion (TypeReference sourceType, TypeReference targetType) + { + if (String.Compare ("System.Char", sourceType.FullName, StringComparison.Ordinal) == 0) { + if (String.Compare ("System.UInt16", targetType.FullName, StringComparison.Ordinal) != 0) { + throw new InvalidOperationException ($"Unexpected conversion from '{sourceType.FullName}' to '{targetType.FullName}'"); + } + + return true; + } + + return false; + } + void ThrowUnsupportedType (TypeReference type) { throw new InvalidOperationException ($"Unsupported non-blittable type '{type.FullName}'"); @@ -436,6 +455,12 @@ TypeReference MapToBlittableTypeIfNecessary (TypeReference type, out bool typeMa return ReturnValid (typeof(byte)); } + if (String.Compare ("System.Char", type.FullName, StringComparison.Ordinal) == 0) { + // Maps to Java JNI's jchar which is an unsigned 16-bit type + typeMapped = true; + return ReturnValid (typeof(ushort)); + } + throw new NotSupportedException ($"Cannot map unsupported blittable type '{type.FullName}'"); TypeReference ReturnValid (Type typeToLookUp) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 0792f992daa..0611a6a454b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Java.Interop.Tools.Cecil; @@ -233,13 +234,14 @@ public bool Matches (MethodDefinition method) HashSet typesWithDynamicallyRegisteredMethods; ulong rejectedMethodCount = 0; ulong wrappedMethodCount = 0; + StreamWriter ignoredMethodsLog; public IDictionary> MarshalMethods => marshalMethods; public ICollection Assemblies => assemblies; public ulong RejectedMethodCount => rejectedMethodCount; public ulong WrappedMethodCount => wrappedMethodCount; - public MarshalMethodsClassifier (TypeDefinitionCache tdCache, XAAssemblyResolver res, TaskLoggingHelper log) + public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyResolver res, TaskLoggingHelper log, string intermediateOutputDirectory) { this.log = log ?? throw new ArgumentNullException (nameof (log)); this.tdCache = tdCache ?? throw new ArgumentNullException (nameof (tdCache)); @@ -247,6 +249,17 @@ public MarshalMethodsClassifier (TypeDefinitionCache tdCache, XAAssemblyResolver marshalMethods = new Dictionary> (StringComparer.Ordinal); assemblies = new HashSet (); typesWithDynamicallyRegisteredMethods = new HashSet (); + + var fs = File.Open (Path.Combine (intermediateOutputDirectory, "marshal-methods-ignored.txt"), FileMode.Create); + ignoredMethodsLog = new StreamWriter (fs, Files.UTF8withoutBOM); + } + + public void FlushAndCloseOutputs () + { + ignoredMethodsLog.WriteLine (); + ignoredMethodsLog.WriteLine ($"Marshal methods count: {MarshalMethods.Count}; Rejected methods count: {RejectedMethodCount}"); + ignoredMethodsLog.Flush (); + ignoredMethodsLog.Close (); } public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute) @@ -422,6 +435,15 @@ bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registere return true; } + void LogIgnored (TypeDefinition type, MethodDefinition method, string message, bool logWarning = true) + { + if (logWarning) { + log.LogWarning (message); + } + + ignoredMethodsLog.WriteLine ($"{type.FullName}\t{method.FullName}\t{message}"); + } + bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, string jniName, string jniSignature) { const string HandlerNameStart = "Get"; @@ -446,23 +468,23 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD MethodDefinition connectorMethod = FindMethod (connectorDeclaringType, connectorName); if (connectorMethod == null) { - log.LogWarning ($"\tConnector method '{connectorName}' not found in type '{connectorDeclaringType.FullName}'"); + LogIgnored (topType, registeredMethod, $"\tConnector method '{connectorName}' not found in type '{connectorDeclaringType.FullName}'"); return false; } if (String.Compare ("System.Delegate", connectorMethod.ReturnType.FullName, StringComparison.Ordinal) != 0) { - log.LogWarning ($"\tConnector '{connectorName}' in type '{connectorDeclaringType.FullName}' has invalid return type, expected 'System.Delegate', found '{connectorMethod.ReturnType.FullName}'"); + LogIgnored (topType, registeredMethod, $"\tConnector '{connectorName}' in type '{connectorDeclaringType.FullName}' has invalid return type, expected 'System.Delegate', found '{connectorMethod.ReturnType.FullName}'"); return false; } var ncbs = new NativeCallbackSignature (registeredMethod, log, tdCache); MethodDefinition nativeCallbackMethod = FindMethod (connectorDeclaringType, nativeCallbackName, ncbs); if (nativeCallbackMethod == null) { - log.LogWarning ($"\tUnable to find native callback method '{nativeCallbackName}' in type '{connectorDeclaringType.FullName}', matching the '{registeredMethod.FullName}' signature (jniName: '{jniName}')"); + LogIgnored (topType, registeredMethod, $"\tUnable to find native callback method '{nativeCallbackName}' in type '{connectorDeclaringType.FullName}', matching the '{registeredMethod.FullName}' signature (jniName: '{jniName}')"); return false; } - if (!EnsureIsValidUnmanagedCallersOnlyTarget (nativeCallbackMethod, out bool needsBlittableWorkaround)) { + if (!EnsureIsValidUnmanagedCallersOnlyTarget (topType, registeredMethod, nativeCallbackMethod, out bool needsBlittableWorkaround)) { return false; } @@ -471,7 +493,7 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD FieldDefinition delegateField = FindField (nativeCallbackMethod.DeclaringType, delegateFieldName); if (delegateField != null) { if (String.Compare ("System.Delegate", delegateField.FieldType.FullName, StringComparison.Ordinal) != 0) { - log.LogWarning ($"\tdelegate field '{delegateFieldName}' in type '{nativeCallbackMethod.DeclaringType.FullName}' has invalid type, expected 'System.Delegate', found '{delegateField.FieldType.FullName}'"); + LogIgnored (topType, registeredMethod, $"\tdelegate field '{delegateFieldName}' in type '{nativeCallbackMethod.DeclaringType.FullName}' has invalid type, expected 'System.Delegate', found '{delegateField.FieldType.FullName}'"); return false; } } @@ -530,23 +552,23 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD return true; } - bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool needsBlittableWorkaround) + bool EnsureIsValidUnmanagedCallersOnlyTarget (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition nativeCallbackMethod, out bool needsBlittableWorkaround) { needsBlittableWorkaround = false; // Requirements: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0#remarks - if (!method.IsStatic) { + if (!nativeCallbackMethod.IsStatic) { return LogReasonWhyAndReturnFailure ($"is not static"); } - if (method.HasGenericParameters) { + if (nativeCallbackMethod.HasGenericParameters) { return LogReasonWhyAndReturnFailure ($"has generic parameters"); } TypeReference type; bool needsWrapper = false; - if (String.Compare ("System.Void", method.ReturnType.FullName, StringComparison.Ordinal) != 0) { - type = GetRealType (method.ReturnType); + if (String.Compare ("System.Void", nativeCallbackMethod.ReturnType.FullName, StringComparison.Ordinal) != 0) { + type = GetRealType (nativeCallbackMethod.ReturnType); if (!IsAcceptable (type)) { needsBlittableWorkaround = true; WarnWhy ($"has a non-blittable return type '{type.FullName}'"); @@ -554,15 +576,15 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool } } - if (method.DeclaringType.HasGenericParameters) { + if (nativeCallbackMethod.DeclaringType.HasGenericParameters) { return LogReasonWhyAndReturnFailure ($"is declared in a type with generic parameters"); } - if (!method.HasParameters) { + if (!nativeCallbackMethod.HasParameters) { return UpdateWrappedCountAndReturn (true); } - foreach (ParameterDefinition pdef in method.Parameters) { + foreach (ParameterDefinition pdef in nativeCallbackMethod.Parameters) { type = GetRealType (pdef.ParameterType); if (!IsAcceptable (type)) { @@ -606,14 +628,14 @@ TypeReference GetRealType (TypeReference type) bool LogReasonWhyAndReturnFailure (string why) { - log.LogWarning ($"Method '{method.FullName}' {why}. It cannot be used with the `[UnmanagedCallersOnly]` attribute"); + LogIgnored (topType, registeredMethod, $"Method '{nativeCallbackMethod.FullName}' {why}. It cannot be used with the `[UnmanagedCallersOnly]` attribute"); return false; } void WarnWhy (string why) { // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers - log.LogDebugMessage ($"Method '{method.FullName}' {why}. A workaround is required, this may make the application slower"); + log.LogDebugMessage ($"Method '{nativeCallbackMethod.FullName}' {why}. A workaround is required, this may make the application slower"); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs new file mode 100644 index 00000000000..7f4afc832c9 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -0,0 +1,603 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +using Xamarin.Android.Tools; +using Xamarin.Android.Tasks.LLVMIR; + +namespace Xamarin.Android.Tasks; + +using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; +using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; + +partial class MarshalMethodsNativeAssemblyGenerator +{ + sealed class LlvmLifetimePointerSizeArgumentPlaceholder : LlvmIrInstructionPointerSizeArgumentPlaceholder + { + public override object? GetValue (LlvmIrModuleTarget target) + { + // the llvm.lifetime functions need a 64-bit integer, target.NativePointerSize is 32-bit + return (ulong)(uint)base.GetValue (target); + } + } + + enum TracingRenderArgumentFunction + { + None, + GetClassName, + GetObjectClassname, + GetCString, + GetBooleanString, + } + + sealed class TracingState + { + public List? trace_enter_leave_args = null; + public LlvmIrLocalVariable? asprintfAllocatedStringVar = null; + } + + readonly MarshalMethodsTracingMode tracingMode; + + LlvmIrFunction? asprintf; + LlvmIrFunction? free; + + LlvmIrFunction? llvm_lifetime_end; + LlvmIrFunction? llvm_lifetime_start; + + LlvmIrFunction? mm_trace_func_enter; + LlvmIrFunction? mm_trace_func_leave; + LlvmIrFunction? mm_trace_get_boolean_string; + LlvmIrFunction? mm_trace_get_c_string; + LlvmIrFunction? mm_trace_get_class_name; + LlvmIrFunction? mm_trace_get_object_class_name; + LlvmIrFunction? mm_trace_init; + + LlvmIrFunctionAttributeSet? freeCallAttributes; + LlvmIrFunctionAttributeSet? asprintfCallAttributes; + + void InitTracing (LlvmIrModule module) + { + var externalFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); + + InitLlvmFunctions (module); + InitLibcFunctions (module, externalFunctionsAttributeSet); + InitTracingFunctions (module, externalFunctionsAttributeSet); + } + + void WriteTracingAtFunctionTop (LlvmIrModule module, MarshalMethodInfo method, LlvmIrFunctionBody body, LlvmIrFunction function, MarshalMethodsWriteState writeState) + { + if (tracingMode == MarshalMethodsTracingMode.None) { + return; + } + + module.RegisterString (method.NativeSymbolName); + + body.AddComment (" Tracing code start"); + + var tracingState = new TracingState { + asprintfAllocatedStringVar = function.CreateLocalVariable (typeof(string), "render_buf"), + }; + body.Alloca (tracingState.asprintfAllocatedStringVar); + + LlvmIrFunctionAttributeSet lifetimeAttrSet = MakeLlvmLifetimeAttributeSet (module); + LlvmIrInstructions.Call call = body.Call (llvm_lifetime_start, arguments: new List { new LlvmLifetimePointerSizeArgumentPlaceholder (), tracingState.asprintfAllocatedStringVar }); + call.AttributeSet = lifetimeAttrSet; + + body.Store (tracingState.asprintfAllocatedStringVar, module.TbaaAnyPointer); + + AddAsprintfCall (module, function, method, tracingState); + + CecilMethodDefinition nativeCallback = method.Method.NativeCallback; + var assemblyCacheIndexPlaceholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); + tracingState.trace_enter_leave_args = new List { + function.Signature.Parameters[0], // JNIEnv *env + (int)tracingMode, + assemblyCacheIndexPlaceholder, + method.ClassCacheIndex, + nativeCallback.MetadataToken.ToUInt32 (), + method.NativeSymbolName, + tracingState.asprintfAllocatedStringVar, + }; + body.Call (mm_trace_func_enter, arguments: tracingState.trace_enter_leave_args); + AddFreeCall (function, tracingState.asprintfAllocatedStringVar); + + body.AddComment (" Tracing code end"); + } + + void AddAsprintfCall (LlvmIrModule module, LlvmIrFunction function, MarshalMethodInfo method, TracingState tracingState) + { + Mono.Collections.Generic.Collection? managedMethodParameters = method.Method.RegisteredMethod?.Parameters ?? method.Method.ImplementedMethod?.Parameters; + + int expectedRegisteredParamCount; + int managedParametersOffset; + if (managedMethodParameters == null) { + // There was no registered nor implemented methods, so we're looking at a native callback - it should have the same number of parameters as our wrapper + managedMethodParameters = method.Method.NativeCallback.Parameters; + expectedRegisteredParamCount = method.Parameters.Count; + managedParametersOffset = 0; + } else { + // Managed methods get two less parameters (no `env` and `klass`) + expectedRegisteredParamCount = method.Parameters.Count - 2; + managedParametersOffset = 2; + } + + if (managedMethodParameters.Count != expectedRegisteredParamCount) { + throw new InvalidOperationException ($"Internal error: unexpected number of registered method '{method.Method.NativeCallback.FullName}' parameters. Should be {expectedRegisteredParamCount}, but is {managedMethodParameters.Count}"); + } + + if (method.Parameters.Count != function.Signature.Parameters.Count) { + throw new InvalidOperationException ($"Internal error: number of native method parameter ({method.Parameters.Count}) doesn't match the number of marshal method parameters ({function.Signature.Parameters.Count})"); + } + + var varargs = new List (); + var variablesToFree = new List (); + var formatSb = new StringBuilder ('('); + bool first = true; + + for (int i = 0; i < method.Parameters.Count; i++) { + LlvmIrFunctionParameter parameter = method.Parameters[i]; + + if (!first) { + formatSb.Append (", "); + } else { + first = false; + } + + // Native method will have two more parameters than its managed counterpart - one for JNIEnv* and another for jclass. They always are the first two + // parameters, so we start looking at the managed parameters only once the first two are out of the way + CecilParameterDefinition? managedParameter = i >= managedParametersOffset ? managedMethodParameters[i - managedParametersOffset] : null; + Type actualType = VerifyAndGetActualParameterType (parameter, managedParameter); + PrepareAsprintfArgument (formatSb, function, parameter, actualType, varargs, variablesToFree); + } + + formatSb.Append (')'); + + string format = formatSb.ToString (); + var asprintfArgs = new List { + tracingState.asprintfAllocatedStringVar, + format, + }; + asprintfArgs.AddRange (varargs); + + DoAddAsprintfCall (asprintfArgs, module, function, format, tracingState); + + foreach (LlvmIrVariable vtf in variablesToFree) { + AddFreeCall (function, vtf); + } + + Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, CecilParameterDefinition? managedParameter) + { + if (managedParameter == null) { + return nativeParameter.Type; + } + + if (nativeParameter.Type == typeof(byte) && String.Compare ("System.Boolean", managedParameter.Name, StringComparison.Ordinal) == 0) { + // `bool`, as a non-blittable type, is mapped to `byte` by the marshal method rewriter + return typeof(bool); + } + + return nativeParameter.Type; + } + } + + void PrepareAsprintfArgument (StringBuilder format, LlvmIrFunction function, LlvmIrVariable parameter, Type actualType, List varargs, List variablesToFree) + { + LlvmIrVariable? result = null; + if (actualType == typeof(_jclass)) { + format.Append ("%s @%p"); + result = AddTransformFunctionCall (function, TracingRenderArgumentFunction.GetClassName, parameter); + varargs.Add (result); + variablesToFree.Add (result); + varargs.Add (parameter); + return; + } + + if (actualType == typeof(_jobject)) { + format.Append ("%s @%p"); + result = AddTransformFunctionCall (function, TracingRenderArgumentFunction.GetObjectClassname, parameter); + varargs.Add (result); + variablesToFree.Add (result); + varargs.Add (parameter); + return; + } + + if (actualType == typeof(_jstring)) { + format.Append ("\"%s\""); + result = AddTransformFunctionCall (function, TracingRenderArgumentFunction.GetCString, parameter); + varargs.Add (result); + variablesToFree.Add (result); + return; + } + + if (actualType == typeof(bool)) { + format.Append ("%s"); + + // No need to free(3) it, returns pointer to a constant + result = AddTransformFunctionCall (function, TracingRenderArgumentFunction.GetBooleanString, parameter); + varargs.Add (result); + return; + } + + if (actualType == typeof(byte) || actualType == typeof(ushort)) { + format.Append ("%u"); + AddUpcast (); + return; + } + + if (actualType == typeof(sbyte) || actualType == typeof(short)) { + format.Append ("%d"); + AddUpcast (); + return; + } + + if (actualType == typeof(char)) { + format.Append ("'\\%x'"); + AddUpcast (); + return; + } + + if (actualType == typeof(float)) { + format.Append ("%g"); + AddUpcast (); + return; + } + + if (actualType == typeof(string)) { + format.Append ("\"%s\""); + } else if (actualType == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (actualType) || actualType == typeof(_JNIEnv)) { + format.Append ("%p"); + } else if (actualType == typeof(int)) { + format.Append ("%d"); + } else if (actualType == typeof(uint)) { + format.Append ("%u"); + } else if (actualType == typeof(long)) { + format.Append ("%ld"); + } else if (actualType == typeof(ulong)) { + format.Append ("%lu"); + } else if (actualType == typeof(double)) { + format.Append ("%g"); + } else { + throw new InvalidOperationException ($"Unsupported type '{actualType}'"); + } + + varargs.Add (parameter); + + void AddUpcast () + { + LlvmIrVariable ret = function.CreateLocalVariable (typeof(T)); + function.Body.Ext (parameter, typeof(T), ret); + varargs.Add (ret); + } + } + + LlvmIrVariable? AddTransformFunctionCall (LlvmIrFunction function, TracingRenderArgumentFunction renderFunction, LlvmIrVariable paramVar) + { + if (renderFunction == TracingRenderArgumentFunction.None) { + return null; + } + + var transformerArgs = new List (); + LlvmIrFunction transformerFunc; + + switch (renderFunction) { + case TracingRenderArgumentFunction.GetClassName: + transformerFunc = mm_trace_get_class_name; + AddJNIEnvArgument (); + break; + + case TracingRenderArgumentFunction.GetObjectClassname: + transformerFunc = mm_trace_get_object_class_name; + AddJNIEnvArgument (); + break; + + case TracingRenderArgumentFunction.GetCString: + transformerFunc = mm_trace_get_c_string; + AddJNIEnvArgument (); + break; + + case TracingRenderArgumentFunction.GetBooleanString: + transformerFunc = mm_trace_get_boolean_string; + break; + + default: + throw new InvalidOperationException ($"Internal error: unsupported transformer function {renderFunction}"); + }; + transformerArgs.Add (paramVar); + + if (!transformerFunc.ReturnsValue) { + return null; + } + LlvmIrLocalVariable? result = function.CreateLocalVariable (transformerFunc.Signature.ReturnType); + function.Body.Call (transformerFunc, result, transformerArgs); + + return result; + + void AddJNIEnvArgument () + { + transformerArgs.Add (function.Signature.Parameters[0]); + } + } + + void DoAddAsprintfCall (List args, LlvmIrModule module, LlvmIrFunction function, string format, TracingState tracingState) + { + module.RegisterString (format); + + LlvmIrFunctionBody body = function.Body; + LlvmIrLocalVariable asprintf_ret = function.CreateLocalVariable (typeof(int), "asprintf_ret"); + LlvmIrInstructions.Call call = body.Call (asprintf, asprintf_ret, args); + call.AttributeSet = asprintfCallAttributes; + call.Comment = $" Format: {format}"; + + // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) + LlvmIrLocalVariable asprintf_failed = function.CreateLocalVariable (typeof(bool), "asprintf_failed"); + body.Icmp (LlvmIrIcmpCond.SignedLessThan, asprintf_ret, (int)0, asprintf_failed); + + var asprintfIfThenLabel = new LlvmIrFunctionLabelItem (); + var asprintfIfDoneLabel = new LlvmIrFunctionLabelItem (); + + body.Br (asprintf_failed, asprintfIfThenLabel, asprintfIfDoneLabel); + + // Condition is true if asprintf **failed** + body.Add (asprintfIfThenLabel); + body.Store (tracingState.asprintfAllocatedStringVar); + body.Br (asprintfIfDoneLabel); + + body.Add (asprintfIfDoneLabel); + } + + void AddFreeCall (LlvmIrFunction function, object? toFree) + { + LlvmIrInstructions.Call call = function.Body.Call (free, arguments: new List { toFree }); + call.AttributeSet = freeCallAttributes; + } + + void InitLibcFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) + { + var asprintf_params = new List { + new (typeof(string), "strp") { + NoUndef = true, + }, + new (typeof(string), "fmt") { + NoUndef = true, + }, + new (typeof(void)) { + IsVarArgs = true, + }, + }; + + var asprintf_sig = new LlvmIrFunctionSignature ( + name: "asprintf", + returnType: typeof(int), + parameters: asprintf_params + ); + + asprintf = module.DeclareExternalFunction (new LlvmIrFunction (asprintf_sig, attrSet)); + + var free_params = new List { + new (typeof(IntPtr), "ptr") { + AllocPtr = true, + NoCapture = true, + NoUndef = true, + }, + }; + + var free_sig = new LlvmIrFunctionSignature ( + name: "free", + returnType: typeof(void), + parameters: free_params + ); + + free = module.DeclareExternalFunction (new LlvmIrFunction (free_sig, MakeFreeFunctionAttributeSet (module))); + freeCallAttributes = MakeFreeCallAttributeSet (module); + asprintfCallAttributes = MakeAsprintfCallAttributeSet (module); + } + + void InitLlvmFunctions (LlvmIrModule module) + { + var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); + var llvm_lifetime_params = new List { + new (typeof(ulong), "size"), + new (typeof(IntPtr), "pointer"), + }; + + var lifetime_sig = new LlvmIrFunctionSignature ( + name: "llvm.lifetime.start", + returnType: typeof(void), + parameters: llvm_lifetime_params + ); + + llvm_lifetime_start = module.DeclareExternalFunction ( + new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default + } + ); + + llvm_lifetime_end = module.DeclareExternalFunction ( + new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default + } + ); + } + + void InitTracingFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) + { + // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + var mm_trace_init_params = new List { + new (typeof(IntPtr), "env"), + }; + + var mm_trace_init_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_init", + returnType: typeof(void), + parameters: mm_trace_init_params + ); + + mm_trace_init = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_init_sig, attrSet)); + + var mm_trace_func_enter_or_leave_params = new List { + new (typeof(IntPtr), "env"), // JNIEnv *env + new (typeof(int), "tracing_mode"), + new (typeof(uint), "mono_image_index"), + new (typeof(uint), "class_index"), + new (typeof(uint), "method_token"), + new (typeof(string), "native_method_name"), + new (typeof(string), "method_extra_info"), + }; + + var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_func_enter", + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params + ); + + mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, attrSet)); + mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, attrSet)); + + var mm_trace_get_class_name_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jclass), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_class_name_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_class_name", + returnType: typeof(string), + parameters: mm_trace_get_class_name_params + ); + + mm_trace_get_class_name = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_class_name_sig, attrSet)); + + var mm_trace_get_object_class_name_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jobject), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_object_class_name_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_object_class_name", + returnType: typeof(string), + parameters: mm_trace_get_object_class_name_params + ); + + mm_trace_get_object_class_name = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_object_class_name_sig, attrSet)); + + var mm_trace_get_c_string_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jstring), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_c_string_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_c_string", + returnType: typeof(string), + parameters: mm_trace_get_c_string_params + ); + + mm_trace_get_c_string = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_c_string_sig, attrSet)); + + var mm_trace_get_boolean_string_params = new List { + new (typeof(bool), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_boolean_string_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_boolean_string", + returnType: typeof(string), + parameters: mm_trace_get_boolean_string_params + ); + mm_trace_get_boolean_string_sig.ReturnAttributes.NonNull = true; + + mm_trace_get_boolean_string = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_boolean_string_sig, attrSet)); + } + + LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) + { + return new LlvmIrFunctionAttributeSet { + new ArgmemonlyFunctionAttribute (), + new MustprogressFunctionAttribute (), + new NocallbackFunctionAttribute (), + new NofreeFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + }; + } + + LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + ret.Add (AndroidTargetArch.Arm64, new FramePointerFunctionAttribute ("non-leaf")); + ret.Add (AndroidTargetArch.Arm, new FramePointerFunctionAttribute ("all")); + ret.Add (AndroidTargetArch.X86, new FramePointerFunctionAttribute ("none")); + ret.Add (AndroidTargetArch.X86_64, new FramePointerFunctionAttribute ("none")); + + return ret; + } + + LlvmIrFunctionAttributeSet MakeFreeCallAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + }; + ret.DoNotAddTargetSpecificAttributes = true; + + return module.AddAttributeSet (ret); + } + + LlvmIrFunctionAttributeSet MakeAsprintfCallAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + }; + ret.DoNotAddTargetSpecificAttributes = true; + + return module.AddAttributeSet (ret); + } + + LlvmIrFunctionAttributeSet MakeFreeFunctionAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + new AllockindFunctionAttribute ("free"), + // TODO: LLVM 16+ feature, enable when we switch to this version + // new MemoryFunctionAttribute { + // Default = MemoryAttributeAccessKind.Write, + // Argmem = MemoryAttributeAccessKind.None, + // InaccessibleMem = MemoryAttributeAccessKind.None, + // }, + new AllocFamilyFunctionAttribute ("malloc"), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return module.AddAttributeSet (ret); + } + + LlvmIrFunctionAttributeSet MakeLlvmLifetimeAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + }; + + ret.DoNotAddTargetSpecificAttributes = true; + return module.AddAttributeSet (ret); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index ee6fc0e7f28..2d14576acee 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -254,7 +254,7 @@ public MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, int numberO /// /// Constructor to be used ONLY when marshal methods are ENABLED /// - public MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods) + public MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, MarshalMethodsTracingMode tracingMode) : base (log) { this.numberOfAssembliesInApk = numberOfAssembliesInApk; @@ -262,7 +262,8 @@ public MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, int numberO this.marshalMethods = marshalMethods; generateEmptyCode = false; - defaultCallMarker = LlvmIrCallMarker.Tail; + this.tracingMode = tracingMode; + defaultCallMarker = tracingMode != MarshalMethodsTracingMode.None ? LlvmIrCallMarker.None : LlvmIrCallMarker.Tail; } void Init () @@ -591,6 +592,10 @@ protected override void Construct (LlvmIrModule module) MapStructures (module); Init (); + if (tracingMode != MarshalMethodsTracingMode.None) { + InitTracing (module); + } + AddAssemblyImageCache (module, out AssemblyCacheState acs); // class cache @@ -680,6 +685,8 @@ void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmI void WriteBody (LlvmIrFunctionBody body) { + WriteTracingAtFunctionTop (module, method, body, func, writeState); + LlvmIrLocalVariable cb1 = func.CreateLocalVariable (typeof(IntPtr), "cb1"); body.Load (backingField, cb1, tbaa: module.TbaaAnyPointer); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs new file mode 100644 index 00000000000..1aa9e82c5d0 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Android.Tasks +{ + // Enumeration member numeric values MUST match those in src/monodroid/jni/marshal-methods-tracing.hh + public enum MarshalMethodsTracingMode + { + None = 0x00, + Basic = 0x01, + Full = 0x02, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs index 3e8fe9a2d8c..9e2a2af2150 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs @@ -138,6 +138,48 @@ public static string ArchToAbi (AndroidTargetArch arch) return abi; } + // TODO: use a dictionary + public static string ArchToClangRuntimeAbi (AndroidTargetArch arch) + { + switch (arch) { + case AndroidTargetArch.Arm64: + return "aarch64"; + + case AndroidTargetArch.Arm: + return "arm"; + + case AndroidTargetArch.X86: + return "i386"; + + case AndroidTargetArch.X86_64: + return "x86_64"; + + default: + throw new InvalidOperationException ($"Internal error: unsupported architecture '{arch}'"); + } + } + + // TODO: use a dictionary + public static string ArchToClangLibraryAbi (AndroidTargetArch arch) + { + switch (arch) { + case AndroidTargetArch.Arm64: + return "aarch64"; + + case AndroidTargetArch.Arm: + return "arm"; + + case AndroidTargetArch.X86: + return "i686"; + + case AndroidTargetArch.X86_64: + return "x86_64"; + + default: + throw new InvalidOperationException ($"Internal error: unsupported architecture '{arch}'"); + } + } + public static string? CultureInvariantToString (object? obj) { if (obj == null) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 9c2794a367a..d122f69a6f0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -522,7 +522,29 @@ public static string GetRelativePathForAndroidAsset (string assetsDirectory, ITa } #endif // MSBUILD + public static MarshalMethodsTracingMode ParseMarshalMethodsTracingMode (string input) + { + if (String.IsNullOrEmpty (input) || String.Compare ("none", input, StringComparison.InvariantCultureIgnoreCase) == 0) { + return MarshalMethodsTracingMode.None; + } + + if (String.Compare ("basic", input, StringComparison.InvariantCultureIgnoreCase) == 0) { + return MarshalMethodsTracingMode.Basic; + } + + if (String.Compare ("full", input, StringComparison.InvariantCultureIgnoreCase) == 0) { + return MarshalMethodsTracingMode.Full; + } + throw new InvalidOperationException ($"Unsupported marshal methods tracing mode '{input}'"); + } + + public static string? CultureInvariantToString (object? obj) + { + if (obj == null) { + return null; + } + } /// /// Converts $(SupportedOSPlatformVersion) to an API level, as it can be a version (21.0), or an int (21). diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets index 91a88bedf80..c5b0be5e0e8 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets @@ -276,29 +276,29 @@ BeforeTargets="CopyFilesToOutputDirectory" Inputs="$(MSBuildAllProjects);@(IntermediateAssembly);@(InputAssemblies)" Outputs="$(IntermediateOutputPath)ILRepacker.stamp" > - - <_InputAssembliesThatExist Include="@(InputAssemblies)" Condition="Exists('%(Identity)')" /> - <_NetstandardPath Include="@(ReferencePath->'%(RootDir)%(Directory)')" Condition="'%(FileName)%(Extension)' == 'netstandard.dll'" /> - - - <_NetstandardDir>@(_NetstandardPath) - <_ILRepackArgs>/out:"$(MSBuildThisFileDirectory)$(IntermediateOutputPath)$(AssemblyName).dll" /internalize - <_ILRepackArgs>$(_ILRepackArgs) /keyfile:"$(XamarinAndroidSourcePath)product.snk" - <_ILRepackArgs>$(_ILRepackArgs) "$(MSBuildThisFileDirectory)$(IntermediateOutputPath)$(AssemblyName).dll" - <_ILRepackArgs>$(_ILRepackArgs) @(_InputAssembliesThatExist->'"%(Identity)"', ' ') - <_ILRepackArgs>$(_ILRepackArgs) /lib:"$(_NetstandardDir.TrimEnd('\'))" - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 0fda73a444e..ebe4019ccab 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -329,6 +329,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. false true True + + False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods) @@ -1383,6 +1385,8 @@ because xbuild doesn't support framework reference assemblies. /> @@ -1746,6 +1750,7 @@ because xbuild doesn't support framework reference assemblies. RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)" UseAssemblyStore="$(AndroidUseAssemblyStore)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" + MarshalMethodsTracingMode="$(_AndroidMarshalMethodsTracingMode)" > @@ -2040,6 +2045,7 @@ because xbuild doesn't support framework reference assemblies. ApplicationSharedLibraries="@(_ApplicationSharedLibrary)" DebugBuild="$(AndroidIncludeDebugSymbols)" AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)" + MarshalMethodsTracingMode="$(_AndroidMarshalMethodsTracingMode)" /> diff --git a/src/libunwind-xamarin/CMakeLists.txt b/src/libunwind-xamarin/CMakeLists.txt new file mode 100644 index 00000000000..7eb15e9edc3 --- /dev/null +++ b/src/libunwind-xamarin/CMakeLists.txt @@ -0,0 +1,382 @@ +cmake_minimum_required(VERSION 3.18.1) + +# +# MUST be included before project()! +# +include("../../build-tools/cmake/xa_common.cmake") + +if(NOT DEFINED LIBUNWIND_LIBRARY_NAME) + message(FATAL_ERROR "Please set the LIBUNWIND_LIBRARY_NAME variable on command line (-DLIBUNWIND_LIBRARY_NAME=base_library_name)") +endif() + +if(NOT DEFINED LIBUNWIND_SOURCE_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_SOURCE_DIR variable on command line (-DLIBUNWIND_SOURCE_DIR=source_dir_path)") +endif() + +if(NOT DEFINED LIBUNWIND_OUTPUT_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_OUTPUT_DIR variable on command line (-DLIBUNWIND_OUTPUT_DIR=output_dir_path)") +endif() + +if(NOT DEFINED LIBUNWIND_HEADERS_OUTPUT_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_HEADERS_OUTPUT_DIR variable on command line (-DLIBUNWIND_HEADERS_OUTPUT_DIR=output_dir_path)") +endif() + +# +# Read libunwind version +# +set(CONFIGURE_AC "${LIBUNWIND_SOURCE_DIR}/configure.ac") +file(STRINGS "${CONFIGURE_AC}" UNWIND_MAJOR_VERSION_AC REGEX "^[ \t]*define[ \t]*\\([ \t]*pkg_major,[ \t]*(.*)\\)") +file(STRINGS "${CONFIGURE_AC}" UNWIND_MINOR_VERSION_AC REGEX "^[ \t]*define[ \t]*\\([ \t]*pkg_minor,[ \t]*(.*)\\)") +file(STRINGS "${CONFIGURE_AC}" UNWIND_EXTRA_VERSION_AC REGEX "^[ \t]*define[ \t]*\\([ \t]*pkg_extra,[ \t]*(.*)\\)") +string(REGEX REPLACE "^[ \t]*define[ \t]*\\([ \t]*pkg_major,[ \t]*(.*)\\)" "\\1" UNWIND_MAJOR_VERSION "${UNWIND_MAJOR_VERSION_AC}") +string(REGEX REPLACE "^[ \t]*define[ \t]*\\([ \t]*pkg_minor,[ \t]*(.*)\\)" "\\1" UNWIND_MINOR_VERSION "${UNWIND_MINOR_VERSION_AC}") +string(REGEX REPLACE "^[ \t]*define[ \t]*\\([ \t]*pkg_extra,[ \t]*(.*)\\)" "\\1" UNWIND_EXTRA_VERSION "${UNWIND_EXTRA_VERSION_AC}") + +set(PKG_MAJOR "${UNWIND_MAJOR_VERSION}") +set(PKG_MINOR "${UNWIND_MINOR_VERSION}") +set(PKG_EXTRA "${UNWIND_EXTRA_VERSION}") +set(PACKAGE_STRING "libunwind-xamarin") +set(PACKAGE_BUGREPORT "") + +message(STATUS "Major: ${PKG_MAJOR}; Minor: ${PKG_MINOR}; Extra: ${PKG_EXTRA}") + +project( + libunwind-xamarin +# VERSION "${PKG_MAJOR}.${PKG_MINOR}.${PKG_EXTRA}" + LANGUAGES C ASM +) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +include(CheckCSourceCompiles) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include("../../build-tools/cmake/xa_macros.cmake") + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) + +if(CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a) + set(TARGET_AARCH64 TRUE) + set(arch aarch64) +elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a) + set(TARGET_ARM TRUE) + set(arch arm) +elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL x86_64) + set(TARGET_AMD64 TRUE) + set(arch x86_64) +elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL x86) + set(TARGET_X86 TRUE) + set(arch x86) +else() + message(FATAL_ERROR "Unsupported Android ABI ${CMAKE_ANDROID_ARCH_ABI}") +endif() + +set(DSO_SYMBOL_VISIBILITY "hidden") +xa_common_prepare() + +if(CMAKE_BUILD_TYPE STREQUAL Debug) + list(APPEND LOCAL_COMPILER_ARGS -g -fno-omit-frame-pointer) +else() + list(APPEND LOCAL_COMPILER_ARGS -s -fomit-frame-pointer) + add_compile_definitions(NDEBUG) +endif() +list(APPEND LOCAL_COMPILER_ARGS + ${XA_DEFAULT_SYMBOL_VISIBILITY} + -fno-asynchronous-unwind-tables + -fno-unwind-tables +) + +xa_check_c_flags(XA_C_FLAGS "${LOCAL_COMPILER_ARGS}") +xa_check_c_linker_flags(XA_C_LINKER_FLAGS "${LOCAL_COMPILER_ARGS}") + +add_compile_options(${XA_C_FLAGS}) +add_link_options(${XA_C_LINKER_FLAGS}) + +add_compile_definitions(HAVE_CONFIG_H) +add_compile_definitions(_GNU_SOURCE) +add_compile_definitions(UNW_LOCAL_ONLY) # we don't need remote unwinding + +# Detect include files +set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) + +set(HAVE_ASM_VSYSCALL_H True) +#check_include_files(asm/vsyscall.h HAVE_ASM_VSYSCALL_H) +check_include_files(byteswap.h HAVE_BYTESWAP_H) +check_include_files(elf.h HAVE_ELF_H) +check_include_files(endian.h HAVE_ENDIAN_H) +check_include_files(link.h HAVE_LINK_H) +check_include_files(sys/endian.h HAVE_SYS_ENDIAN_H) +check_include_files(sys/link.h HAVE_SYS_LINK_H) +check_include_files(sys/param.h HAVE_SYS_PARAM_H) +check_include_files(sys/syscall.h HAVE_SYS_SYSCALL_H) + +# Detect functions +check_symbol_exists(mincore "sys/mman.h" HAVE_MINCORE) +check_symbol_exists(pipe2 "fcntl.h;unistd.h" HAVE_PIPE2) + +# TODO: consider enabling zlib + +configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/config.h) +configure_file(${LIBUNWIND_SOURCE_DIR}/include/libunwind-common.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind-common.h) +configure_file(${LIBUNWIND_SOURCE_DIR}/include/libunwind.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind.h) +configure_file(${LIBUNWIND_SOURCE_DIR}/include/tdep/libunwind_i.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/tdep/libunwind_i.h) + +# The files are nearly identical, the only difference is name of the header included when remote-only libunwind is used +# We don't use it, but we still copy files to separate directories in order to avoid locking issues during parallel builds +set(HEADERS_DIR ${LIBUNWIND_HEADERS_OUTPUT_DIR}/${CMAKE_ANDROID_ARCH_ABI}) + +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind-common.h DESTINATION ${HEADERS_DIR}) +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind.h DESTINATION ${HEADERS_DIR}) +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/include/tdep/libunwind_i.h DESTINATION ${HEADERS_DIR}/tdep/) + +include_directories(${LIBUNWIND_SOURCE_DIR}/include/tdep) +include_directories(${LIBUNWIND_SOURCE_DIR}/include) +include_directories(${LIBUNWIND_SOURCE_DIR}/src) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include/tdep) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + +if(TARGET_ARM) + # Ensure that the remote and local unwind code can reside in the same binary without name clashing + add_definitions("-Darm_search_unwind_table=UNW_OBJ(arm_search_unwind_table)") + # We compile code with -std=c99 and the asm keyword is not recognized as it is a gnu extension + #TODO: possibly not needed? add_definitions(-Dasm=__asm__) + # The arm sources include ex_tables.h from include/tdep-arm without going through a redirection + # in include/tdep like it works for similar files on other architectures. So we need to add + # the include/tdep-arm to include directories + include_directories(${LIBUNWIND_SOURCE_DIR}/include/tdep-arm) +elseif(TARGET_AARCH64) + # We compile code with -std=c99 and the asm keyword is not recognized as it is a gnu extension + #TODO: possibly not needed? add_definitions(-Dasm=__asm__) +endif() + +set(SOURCES_DIR ${LIBUNWIND_SOURCE_DIR}/src) + +set(LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/dwarf/Gexpr.c + ${SOURCES_DIR}/dwarf/Gfde.c + ${SOURCES_DIR}/dwarf/Gfind_proc_info-lsb.c + ${SOURCES_DIR}/dwarf/Gfind_unwind_table.c + ${SOURCES_DIR}/dwarf/Gparser.c + ${SOURCES_DIR}/dwarf/Gpe.c + ${SOURCES_DIR}/dwarf/Lexpr.c + ${SOURCES_DIR}/dwarf/Lfde.c + ${SOURCES_DIR}/dwarf/Lfind_proc_info-lsb.c + ${SOURCES_DIR}/dwarf/Lfind_unwind_table.c + ${SOURCES_DIR}/dwarf/Lparser.c + ${SOURCES_DIR}/dwarf/Lpe.c + ${SOURCES_DIR}/dwarf/global.c + ${SOURCES_DIR}/elfxx.c + ${SOURCES_DIR}/mi/Gdestroy_addr_space.c + ${SOURCES_DIR}/mi/Gdyn-extract.c + ${SOURCES_DIR}/mi/Gfind_dynamic_proc_info.c + ${SOURCES_DIR}/mi/Gget_fpreg.c + ${SOURCES_DIR}/mi/Gget_proc_info_by_ip.c + ${SOURCES_DIR}/mi/Gget_proc_info_in_range.c + ${SOURCES_DIR}/mi/Gget_proc_name.c + ${SOURCES_DIR}/mi/Gget_reg.c + ${SOURCES_DIR}/mi/Gput_dynamic_unwind_info.c + ${SOURCES_DIR}/mi/Gset_cache_size.c + ${SOURCES_DIR}/mi/Gset_caching_policy.c + ${SOURCES_DIR}/mi/Gset_fpreg.c + ${SOURCES_DIR}/mi/Gset_reg.c + ${SOURCES_DIR}/mi/Ldestroy_addr_space.c + ${SOURCES_DIR}/mi/Ldyn-extract.c + ${SOURCES_DIR}/mi/Lfind_dynamic_proc_info.c + ${SOURCES_DIR}/mi/Lget_accessors.c + ${SOURCES_DIR}/mi/Lget_fpreg.c + ${SOURCES_DIR}/mi/Lget_proc_info_by_ip.c + ${SOURCES_DIR}/mi/Lget_proc_info_in_range.c + ${SOURCES_DIR}/mi/Lget_proc_name.c + ${SOURCES_DIR}/mi/Lget_reg.c + ${SOURCES_DIR}/mi/Lput_dynamic_unwind_info.c + ${SOURCES_DIR}/mi/Lset_cache_size.c + ${SOURCES_DIR}/mi/Lset_caching_policy.c + ${SOURCES_DIR}/mi/Lset_fpreg.c + ${SOURCES_DIR}/mi/Lset_reg.c + ${SOURCES_DIR}/mi/backtrace.c + ${SOURCES_DIR}/mi/dyn-cancel.c + ${SOURCES_DIR}/mi/dyn-info-list.c + ${SOURCES_DIR}/mi/dyn-register.c + ${SOURCES_DIR}/mi/flush_cache.c + ${SOURCES_DIR}/mi/init.c + ${SOURCES_DIR}/mi/mempool.c + ${SOURCES_DIR}/mi/strerror.c + ${SOURCES_DIR}/os-linux.c +) + +if(TARGET_AMD64 OR TARGET_AARCH64) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/elf64.c + ) +endif() + +if(TARGET_X86 OR TARGET_ARM) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/elf32.c + ) +endif() + +if(TARGET_X86) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/x86/Gapply_reg_state.c + ${SOURCES_DIR}/x86/Gcreate_addr_space.c + ${SOURCES_DIR}/x86/Gget_proc_info.c + ${SOURCES_DIR}/x86/Gget_save_loc.c + ${SOURCES_DIR}/x86/Gglobal.c + ${SOURCES_DIR}/x86/Ginit.c + ${SOURCES_DIR}/x86/Ginit_local.c + ${SOURCES_DIR}/x86/Ginit_remote.c + ${SOURCES_DIR}/x86/Gos-linux.c + ${SOURCES_DIR}/x86/Greg_states_iterate.c + ${SOURCES_DIR}/x86/Gregs.c + ${SOURCES_DIR}/x86/Gresume.c + ${SOURCES_DIR}/x86/Gstep.c + ${SOURCES_DIR}/x86/Lapply_reg_state.c + ${SOURCES_DIR}/x86/Lcreate_addr_space.c + ${SOURCES_DIR}/x86/Lget_proc_info.c + ${SOURCES_DIR}/x86/Lget_save_loc.c + ${SOURCES_DIR}/x86/Lglobal.c + ${SOURCES_DIR}/x86/Linit.c + ${SOURCES_DIR}/x86/Linit_local.c + ${SOURCES_DIR}/x86/Linit_remote.c + ${SOURCES_DIR}/x86/Los-linux.c + ${SOURCES_DIR}/x86/Lreg_states_iterate.c + ${SOURCES_DIR}/x86/Lregs.c + ${SOURCES_DIR}/x86/Lresume.c + ${SOURCES_DIR}/x86/Lstep.c + ${SOURCES_DIR}/x86/getcontext-linux.S + ${SOURCES_DIR}/x86/is_fpreg.c + ${SOURCES_DIR}/x86/regname.c + ) +endif(TARGET_X86) + +if(TARGET_AMD64) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/x86_64/Gapply_reg_state.c + ${SOURCES_DIR}/x86_64/Gcreate_addr_space.c + ${SOURCES_DIR}/x86_64/Gget_proc_info.c + ${SOURCES_DIR}/x86_64/Gget_save_loc.c + ${SOURCES_DIR}/x86_64/Gglobal.c + ${SOURCES_DIR}/x86_64/Ginit.c + ${SOURCES_DIR}/x86_64/Ginit_local.c + ${SOURCES_DIR}/x86_64/Ginit_remote.c + ${SOURCES_DIR}/x86_64/Gos-linux.c + ${SOURCES_DIR}/x86_64/Greg_states_iterate.c + ${SOURCES_DIR}/x86_64/Gregs.c + ${SOURCES_DIR}/x86_64/Gresume.c + ${SOURCES_DIR}/x86_64/Gstash_frame.c + ${SOURCES_DIR}/x86_64/Gstep.c + ${SOURCES_DIR}/x86_64/Gtrace.c + ${SOURCES_DIR}/x86_64/Lapply_reg_state.c + ${SOURCES_DIR}/x86_64/Lcreate_addr_space.c + ${SOURCES_DIR}/x86_64/Lget_proc_info.c + ${SOURCES_DIR}/x86_64/Lget_save_loc.c + ${SOURCES_DIR}/x86_64/Lglobal.c + ${SOURCES_DIR}/x86_64/Linit.c + ${SOURCES_DIR}/x86_64/Linit_local.c + ${SOURCES_DIR}/x86_64/Linit_remote.c + ${SOURCES_DIR}/x86_64/Los-linux.c + ${SOURCES_DIR}/x86_64/Lreg_states_iterate.c + ${SOURCES_DIR}/x86_64/Lregs.c + ${SOURCES_DIR}/x86_64/Lresume.c + ${SOURCES_DIR}/x86_64/Lstash_frame.c + ${SOURCES_DIR}/x86_64/Lstep.c + ${SOURCES_DIR}/x86_64/Ltrace.c + ${SOURCES_DIR}/x86_64/getcontext.S + ${SOURCES_DIR}/x86_64/is_fpreg.c + ${SOURCES_DIR}/x86_64/regname.c + ${SOURCES_DIR}/x86_64/setcontext.S + ) +endif() + +if(TARGET_ARM) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/arm/Gapply_reg_state.c + ${SOURCES_DIR}/arm/Gcreate_addr_space.c + ${SOURCES_DIR}/arm/Gex_tables.c + ${SOURCES_DIR}/arm/Gget_proc_info.c + ${SOURCES_DIR}/arm/Gget_save_loc.c + ${SOURCES_DIR}/arm/Gglobal.c + ${SOURCES_DIR}/arm/Ginit.c + ${SOURCES_DIR}/arm/Ginit_local.c + ${SOURCES_DIR}/arm/Ginit_remote.c + ${SOURCES_DIR}/arm/Gos-linux.c + ${SOURCES_DIR}/arm/Greg_states_iterate.c + ${SOURCES_DIR}/arm/Gregs.c + ${SOURCES_DIR}/arm/Gresume.c + ${SOURCES_DIR}/arm/Gstash_frame.c + ${SOURCES_DIR}/arm/Gstep.c + ${SOURCES_DIR}/arm/Gtrace.c + ${SOURCES_DIR}/arm/Lapply_reg_state.c + ${SOURCES_DIR}/arm/Lcreate_addr_space.c + ${SOURCES_DIR}/arm/Lex_tables.c + ${SOURCES_DIR}/arm/Lget_proc_info.c + ${SOURCES_DIR}/arm/Lget_save_loc.c + ${SOURCES_DIR}/arm/Lglobal.c + ${SOURCES_DIR}/arm/Linit.c + ${SOURCES_DIR}/arm/Linit_local.c + ${SOURCES_DIR}/arm/Linit_remote.c + ${SOURCES_DIR}/arm/Los-linux.c + ${SOURCES_DIR}/arm/Lreg_states_iterate.c + ${SOURCES_DIR}/arm/Lregs.c + ${SOURCES_DIR}/arm/Lresume.c + ${SOURCES_DIR}/arm/Lstash_frame.c + ${SOURCES_DIR}/arm/Lstep.c + ${SOURCES_DIR}/arm/Ltrace.c + ${SOURCES_DIR}/arm/getcontext.S + ${SOURCES_DIR}/arm/is_fpreg.c + ${SOURCES_DIR}/arm/regname.c + ) +endif() + +if(TARGET_AARCH64) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/aarch64/Gapply_reg_state.c + ${SOURCES_DIR}/aarch64/Gcreate_addr_space.c + ${SOURCES_DIR}/aarch64/Gget_proc_info.c + ${SOURCES_DIR}/aarch64/Gget_save_loc.c + ${SOURCES_DIR}/aarch64/Gglobal.c + ${SOURCES_DIR}/aarch64/Ginit.c + ${SOURCES_DIR}/aarch64/Ginit_local.c + ${SOURCES_DIR}/aarch64/Ginit_remote.c + ${SOURCES_DIR}/aarch64/Gis_signal_frame.c + ${SOURCES_DIR}/aarch64/Greg_states_iterate.c + ${SOURCES_DIR}/aarch64/Gregs.c + ${SOURCES_DIR}/aarch64/Gresume.c + ${SOURCES_DIR}/aarch64/Gstash_frame.c + ${SOURCES_DIR}/aarch64/Gstep.c + ${SOURCES_DIR}/aarch64/Gtrace.c + ${SOURCES_DIR}/aarch64/Lapply_reg_state.c + ${SOURCES_DIR}/aarch64/Lcreate_addr_space.c + ${SOURCES_DIR}/aarch64/Lget_proc_info.c + ${SOURCES_DIR}/aarch64/Lget_save_loc.c + ${SOURCES_DIR}/aarch64/Lglobal.c + ${SOURCES_DIR}/aarch64/Linit.c + ${SOURCES_DIR}/aarch64/Linit_local.c + ${SOURCES_DIR}/aarch64/Linit_remote.c + ${SOURCES_DIR}/aarch64/Lis_signal_frame.c + ${SOURCES_DIR}/aarch64/Lreg_states_iterate.c + ${SOURCES_DIR}/aarch64/Lregs.c + ${SOURCES_DIR}/aarch64/Lresume.c + ${SOURCES_DIR}/aarch64/Lstash_frame.c + ${SOURCES_DIR}/aarch64/Lstep.c + ${SOURCES_DIR}/aarch64/Ltrace.c + ${SOURCES_DIR}/aarch64/getcontext.S + ${SOURCES_DIR}/aarch64/is_fpreg.c + ${SOURCES_DIR}/aarch64/regname.c + ) +endif() + +add_library(${LIBUNWIND_LIBRARY_NAME} + STATIC + ${LIBUNWIND_XAMARIN_SOURCES} +) + +target_link_options( + ${LIBUNWIND_LIBRARY_NAME} + PRIVATE ${XA_DEFAULT_SYMBOL_VISIBILITY} +) diff --git a/src/libunwind-xamarin/config.h.in b/src/libunwind-xamarin/config.h.in new file mode 100644 index 00000000000..52656bb9740 --- /dev/null +++ b/src/libunwind-xamarin/config.h.in @@ -0,0 +1,21 @@ +#if !defined (__LIBUNWIND_CONFIG_H) +#define __LIBUNWIND_CONFIG_H + +#cmakedefine01 HAVE_ELF_H +#cmakedefine01 HAVE_ENDIAN_H +#cmakedefine01 HAVE_ASM_VSYSCALL_H +#cmakedefine01 HAVE_BYTESWAP_H +#cmakedefine01 HAVE_ELF_H +#cmakedefine01 HAVE_ENDIAN_H +#cmakedefine01 HAVE_LINK_H +#cmakedefine01 HAVE_SYS_ENDIAN_H +#cmakedefine01 HAVE_SYS_LINK_H +#cmakedefine01 HAVE_SYS_PARAM_H +#cmakedefine01 HAVE_SYS_SYSCALL_H +#cmakedefine01 HAVE_MINCORE +#cmakedefine01 HAVE_PIPE2 + +#define PACKAGE_STRING "@PACKAGE_STRING@" +#define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" + +#endif // ndef __LIBUNWIND_CONFIG_H diff --git a/src/libunwind-xamarin/libunwind-xamarin.csproj b/src/libunwind-xamarin/libunwind-xamarin.csproj new file mode 100644 index 00000000000..b7aba4a3cfb --- /dev/null +++ b/src/libunwind-xamarin/libunwind-xamarin.csproj @@ -0,0 +1,15 @@ + + + Exe + netstandard2.0 + False + + + + + + $(MicrosoftAndroidSdkOutDir)lib + + + + diff --git a/src/libunwind-xamarin/libunwind-xamarin.targets b/src/libunwind-xamarin/libunwind-xamarin.targets new file mode 100644 index 00000000000..e157862f527 --- /dev/null +++ b/src/libunwind-xamarin/libunwind-xamarin.targets @@ -0,0 +1,54 @@ + + + + + + + <_LibUnwindBaseLibName>unwind_xamarin + <_LibUnwindLibName>lib$(_LibUnwindBaseLibName).a + <_LibUnwindHeadersOutputDir>$(LibUnwindGeneratedHeadersFullPath) + + + + + + + + <_ConfigureLibUnwindCommands + Include="@(AndroidSupportedTargetJitAbi)"> + $(CmakePath) + $(_CmakeAndroidFlags) -DANDROID_NATIVE_API_LEVEL=%(AndroidSupportedTargetJitAbi.ApiLevel) -DANDROID_PLATFORM=android-%(AndroidSupportedTargetJitAbi.ApiLevel) -DANDROID_ABI=%(AndroidSupportedTargetJitAbi.Identity) -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$(OutputPath)%(AndroidSupportedTargetJitAbi.AndroidRID)" -DCMAKE_LIBRARY_OUTPUT_DIRECTORY="$(OutputPath)%(AndroidSupportedTargetJitAbi.AndroidRID)" -DCMAKE_BUILD_TYPE=$(Configuration) -DLIBUNWIND_SOURCE_DIR="$(LibUnwindSourceFullPath)" -DLIBUNWIND_LIBRARY_NAME="$(_LibUnwindBaseLibName)" -DLIBUNWIND_OUTPUT_DIR="@(AndroidSupportedTargetJitAbi->'$(OutputPath)/%(AndroidRID)')" -DLIBUNWIND_HEADERS_OUTPUT_DIR="$(_LibUnwindHeadersOutputDir)" $(MSBuildThisFileDirectory) + $(IntermediateOutputPath)%(AndroidSupportedTargetJitAbi.Identity)-$(Configuration) + + + + + + + + + + + <_BuildLibUnwindCommands + Include="@(AndroidSupportedTargetJitAbi)"> + $(NinjaPath) + -v + $(IntermediateOutputPath)%(AndroidSupportedTargetJitAbi.Identity)-$(Configuration) + + + + + + + + + + + diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 0947a0e9bd7..1c337c13b0b 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -102,6 +102,16 @@ if(NOT DEFINED XA_LIB_TOP_DIR) message(FATAL_ERROR "Please set the XA_LIB_TOP_DIR variable on command line (-DXA_LIB_TOP_DIR=path)") endif() +if(NOT DEFINED LIBUNWIND_SOURCE_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_SOURCE_DIR on command line (-DLIBUNWIND_SOURCE_DIR=path)") +endif() + +if(ANDROID AND ENABLE_NET) + if(NOT DEFINED LIBUNWIND_HEADERS_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_HEADERS_DIR on command line (-DLIBUNWIND_HEADERS_DIR=path)") + endif() +endif() + if(NOT ANDROID) if (NOT DEFINED JDK_INCLUDE) message(FATAL_ERROR "Please set the JDK_INCLUDE variable on command line (-DJDK_INCLUDE)") @@ -493,6 +503,8 @@ endif() set(XAMARIN_INTERNAL_API_LIB xa-internal-api${CHECKED_BUILD_INFIX}) set(XAMARIN_DEBUG_APP_HELPER_LIB xamarin-debug-app-helper${CHECKED_BUILD_INFIX}) set(XAMARIN_APP_STUB_LIB xamarin-app) +set(XAMARIN_NATIVE_TRACING_LIB xamarin-native-tracing) +set(XAMARIN_MARSHAL_METHODS_TRACING_LIB marshal-methods-tracing) string(TOLOWER ${CMAKE_BUILD_TYPE} XAMARIN_MONO_ANDROID_SUFFIX) set(XAMARIN_MONO_ANDROID_LIB "mono-android${CHECKED_BUILD_INFIX}.${XAMARIN_MONO_ANDROID_SUFFIX}") @@ -559,7 +571,25 @@ if(UNIX) endif() if(ANDROID) + set(NATIVE_TRACING_SOURCES + ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/src/cxa_demangle.cpp + ${SOURCES_DIR}/cxx-abi/string.cc + ${SOURCES_DIR}/cxx-abi/terminate.cc + ${SOURCES_DIR}/helpers.cc + ${SOURCES_DIR}/native-tracing.cc + ${SOURCES_DIR}/new_delete.cc + ) + + set(MARSHAL_METHODS_TRACING_SOURCES + ${SOURCES_DIR}/cxx-abi/string.cc + ${SOURCES_DIR}/cxx-abi/terminate.cc + ${SOURCES_DIR}/helpers.cc + ${SOURCES_DIR}/marshal-methods-tracing.cc + ${SOURCES_DIR}/new_delete.cc + ) + list(APPEND XAMARIN_MONODROID_SOURCES + ${SOURCES_DIR}/monodroid-tracing.cc ${SOURCES_DIR}/monovm-properties.cc ${SOURCES_DIR}/pinvoke-override-api.cc ${JAVA_INTEROP_SRC_PATH}/java-interop-util.cc @@ -695,6 +725,77 @@ add_library( ) if(ANDROID) + if(ENABLE_NET AND NOT DEBUG_BUILD) + set(CPP_ABI_PATH ${CMAKE_SYSROOT}/usr/lib/${SYSROOT_ABI_LIB_DIR}/libc++abi.a) + + add_library( + ${XAMARIN_NATIVE_TRACING_LIB} + SHARED ${NATIVE_TRACING_SOURCES} + ) + + target_include_directories( + ${XAMARIN_NATIVE_TRACING_LIB} BEFORE + PRIVATE + ${LIBUNWIND_SOURCE_DIR}/include + ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} + ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/include + ) + + target_compile_options( + ${XAMARIN_NATIVE_TRACING_LIB} + PRIVATE + # Avoid the 'warning: dynamic exception specifications are deprecated' warning from libc++ headers + -Wno-deprecated-dynamic-exception-spec + ${XA_DEFAULT_SYMBOL_VISIBILITY} + # Prevent genration of the .eh_frame section (we don't use exceptions and don't need it) + -fno-asynchronous-unwind-tables + ) + + target_link_options( + ${XAMARIN_NATIVE_TRACING_LIB} + PRIVATE ${XA_DEFAULT_SYMBOL_VISIBILITY} + ) + + target_link_libraries( + ${XAMARIN_NATIVE_TRACING_LIB} + PRIVATE + -llog + ${CPP_ABI_PATH} + ${XA_LIBRARY_OUTPUT_DIRECTORY}/libunwind_xamarin.a + ) + + target_compile_definitions( + ${XAMARIN_NATIVE_TRACING_LIB} + PRIVATE + XAMARIN_TRACING + ) + + add_library( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} + STATIC ${MARSHAL_METHODS_TRACING_SOURCES} + ) + + target_include_directories( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} BEFORE + PRIVATE + ${LIBUNWIND_SOURCE_DIR}/include + ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} + ) + + target_compile_definitions( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} + PRIVATE + XAMARIN_TRACING + ) + + target_compile_options( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} + PRIVATE + # Prevent genration of the .eh_frame section (we don't use exceptions and don't need it) + -fno-asynchronous-unwind-tables + ) + endif() + # Ugly, but this is the only way to change LZ4 symbols visibility without modifying lz4.h set(LZ4_VISIBILITY_OPTS "-DLZ4LIB_VISIBILITY=__attribute__ ((visibility (\"hidden\")))") endif() @@ -704,6 +805,13 @@ target_compile_options( PRIVATE ${XA_DEFAULT_SYMBOL_VISIBILITY} "${LZ4_VISIBILITY_OPTS}" ) +target_include_directories( + ${XAMARIN_MONO_ANDROID_LIB} BEFORE + PRIVATE + ${LIBUNWIND_SOURCE_DIR}/include + ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} +) + if(APPLE) set_target_properties( ${XAMARIN_MONO_ANDROID_LIB} diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index bb9a977e8ce..a27aa8a8be9 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -22,13 +23,75 @@ const TypeMap type_map = { managed_to_java }; #else -const uint32_t map_module_count = 0; +const uint32_t map_module_count = 2; const uint32_t java_type_count = 0; -const char* const java_type_names[] = {}; +const char* const java_type_names[] = { + "java/lang/String", + "java/lang/Exception", +}; + +static TypeMapModuleEntry module1[] = { + { + .type_token_id = 1111, + .java_map_index = 0 + } +}; -TypeMapModule map_modules[] = {}; -const TypeMapJava map_java[] = {}; -const xamarin::android::hash_t map_java_hashes[] = {}; +static uint8_t module1_java_map[] = { 1, 2 }; + +static TypeMapModuleEntry module2[] = { + { + .type_token_id = 2222, + .java_map_index = 0 + } +}; + +static uint8_t module2_java_map[] = { 3, 4 }; + +TypeMapModule map_modules[] = { + { + .module_uuid = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, + .entry_count = 1, + .duplicate_count = 0, + .map = module1, + .duplicate_map = nullptr, + .assembly_name = "Mono.Android", + .image = nullptr, + .java_name_width = 111, + .java_map = module1_java_map, + }, + + { + .module_uuid = {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }, + .entry_count = 1, + .duplicate_count = 0, + .map = module2, + .duplicate_map = nullptr, + .assembly_name = "System", + .image = nullptr, + .java_name_width = 222, + .java_map = module2_java_map, + }, +}; + +const TypeMapJava map_java[] = { + { + .module_index = 0, + .type_token_id = 1, + .java_name_index = 1, + }, + + { + .module_index = 1, + .type_token_id = 2, + .java_name_index = 2, + }, +}; + +const xamarin::android::hash_t map_java_hashes[] = { + 0x1, + 0x2, +}; #endif CompressedAssemblies compressed_assemblies = { @@ -69,7 +132,9 @@ const ApplicationConfig application_config = { }; const char* const mono_aot_mode_name = "normal"; -const char* const app_environment_variables[] = {}; +const char* const app_environment_variables[] = { + "name", "value", +}; const char* const app_system_properties[] = {}; static constexpr size_t AssemblyNameWidth = 128; diff --git a/src/monodroid/jni/cpp-util.hh b/src/monodroid/jni/cpp-util.hh index 4e698660b68..abafa681b5d 100644 --- a/src/monodroid/jni/cpp-util.hh +++ b/src/monodroid/jni/cpp-util.hh @@ -38,9 +38,19 @@ namespace xamarin::android template struct CDeleter final { + using UnderlyingType = std::remove_cv_t; + void operator() (T* p) { - std::free (p); + UnderlyingType *ptr; + + if constexpr (std::is_const_v) { + ptr = const_cast*> (p); + } else { + ptr = p; + } + + std::free (reinterpret_cast(ptr)); } }; diff --git a/src/monodroid/jni/generate-pinvoke-tables.cc b/src/monodroid/jni/generate-pinvoke-tables.cc index 17a051c3b4f..b32642adef4 100644 --- a/src/monodroid/jni/generate-pinvoke-tables.cc +++ b/src/monodroid/jni/generate-pinvoke-tables.cc @@ -61,6 +61,7 @@ const std::vector internal_pinvoke_names = { "_monodroid_gref_log_delete", "_monodroid_gref_log_new", "monodroid_log", + "monodroid_log_traces", "_monodroid_lookup_replacement_type", "_monodroid_lookup_replacement_method_info", "_monodroid_lref_log_delete", @@ -122,6 +123,8 @@ const std::vector dotnet_pinvoke_names = { "CompressionNative_InflateEnd", "CompressionNative_InflateInit2_", "CompressionNative_InflateReset", + "_kBrotliContextLookupTable", + "_kBrotliPrefixCodeRanges", // libSystem.Native.so "SystemNative_Abort", @@ -249,6 +252,7 @@ const std::vector dotnet_pinvoke_names = { "SystemNative_GetSystemTimeAsTicks", "SystemNative_GetTcpGlobalStatistics", "SystemNative_GetTimestamp", + "SystemNative_GetTimeZoneData", "SystemNative_GetUdpGlobalStatistics", "SystemNative_GetUnixRelease", "SystemNative_GetUnixVersion", @@ -517,6 +521,7 @@ const std::vector dotnet_pinvoke_names = { "CryptoNative_HmacOneShot", "CryptoNative_HmacReset", "CryptoNative_HmacUpdate", + "Java_net_dot_android_crypto_DotnetProxyTrustManager_verifyRemoteCertificate", }; template diff --git a/src/monodroid/jni/helpers.hh b/src/monodroid/jni/helpers.hh index fa89be2f66c..45d5413a1b6 100644 --- a/src/monodroid/jni/helpers.hh +++ b/src/monodroid/jni/helpers.hh @@ -12,7 +12,7 @@ namespace xamarin::android #define ADD_WITH_OVERFLOW_CHECK(__ret_type__, __a__, __b__) xamarin::android::Helpers::add_with_overflow_check<__ret_type__>(__FILE__, __LINE__, (__a__), (__b__)) #define MULTIPLY_WITH_OVERFLOW_CHECK(__ret_type__, __a__, __b__) xamarin::android::Helpers::multiply_with_overflow_check<__ret_type__>(__FILE__, __LINE__, (__a__), (__b__)) - class Helpers + class [[gnu::visibility("hidden")]] Helpers { public: template diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc new file mode 100644 index 00000000000..49a319b7bf7 --- /dev/null +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "marshal-methods-tracing.hh" +#include "marshal-methods-utilities.hh" +#include "cpp-util.hh" +#include "helpers.hh" +#include "native-tracing.hh" + +using namespace xamarin::android; +using namespace xamarin::android::internal; + +constexpr int PRIORITY = ANDROID_LOG_INFO; +constexpr char LEAD[] = "MM: "; +constexpr char BOOL_TRUE[] = "true"; +constexpr char BOOL_FALSE[] = "false"; +constexpr char MISSING_ENV[] = ""; +constexpr char NULL_PARAM[] = ""; +constexpr char INTERNAL_ERROR[] = ""; +constexpr char UNKNOWN[] = ""; + +static jclass java_lang_Class; +static jmethodID java_lang_Class_getName; + +void _mm_trace_init (JNIEnv *env) noexcept +{ + if (env == nullptr || java_lang_Class != nullptr) { + return; + } + + java_lang_Class = to_gref (env, env->FindClass ("java/lang/Class")); + java_lang_Class_getName = env->GetMethodID (java_lang_Class, "getName", "()Ljava/lang/String"); + + if (env->ExceptionOccurred ()) { + env->ExceptionDescribe (); + env->ExceptionClear (); + xamarin::android::Helpers::abort_application (); + } + + bool all_found = assert_valid_jni_pointer (java_lang_Class, "class", "java.lang.Class"); + all_found &= assert_valid_jni_pointer (java_lang_Class_getName, "method", "java.lang.Class.getName ()"); + + if (!all_found) { + xamarin::android::Helpers::abort_application (); + } +} + +static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, + const char* which, const char* native_method_name, bool need_trace, const char* method_extra) noexcept +{ + uint64_t method_id = MarshalMethodsUtilities::get_method_id (mono_image_index, method_token); + const char *managed_method_name = MarshalMethodsUtilities::get_method_name (method_id); + const char *class_name = MarshalMethodsUtilities::get_class_name (class_index); + + if (need_trace && tracing_mode == TracingModeFull) { + std::string trace { LEAD }; + trace.append (which); + trace.append (": "); + trace.append (native_method_name); + if (method_extra != nullptr) { + trace.append (" "); + trace.append (method_extra); + } + trace.append (" {"); + trace.append (managed_method_name); + trace.append ("} in class "); + trace.append (class_name); + + trace.append ("\n Native stack trace:\n"); + c_unique_ptr native_trace { xa_get_native_backtrace () }; + trace.append (native_trace.get ()); + trace.append ("\n"); + + trace.append ("\n Java stack trace:\n"); + c_unique_ptr java_trace { xa_get_java_backtrace (env) }; + trace.append (java_trace.get ()); + trace.append ("\n"); + + trace.append ("\n Installed signal handlers:\n"); + c_unique_ptr signal_handlers { xa_get_interesting_signal_handlers () }; + trace.append (signal_handlers.get ()); + + __android_log_write (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, trace.c_str ()); + } else { + __android_log_print ( + PRIORITY, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + "%s%s: %s%s%s {%s} in class %s", + LEAD, + which, + native_method_name, + method_extra == nullptr ? "" : " ", + method_extra == nullptr ? "" : method_extra, + managed_method_name, + class_name + ); + } +} + +void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_params) noexcept +{ + constexpr char ENTER[] = "ENTER"; + _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, ENTER, native_method_name, true /* need_trace */, method_params); +} + +void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_return_value) noexcept +{ + constexpr char LEAVE[] = "LEAVE"; + _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */, method_return_value); +} + +const char* _mm_trace_get_boolean_string (uint8_t v) noexcept +{ + return v ? BOOL_TRUE : BOOL_FALSE; +} + +char* _mm_trace_get_c_string (JNIEnv *env, jstring v) noexcept +{ + if (env == nullptr) { + return strdup (MISSING_ENV); + } + + if (v == nullptr) { + return strdup (NULL_PARAM); + } + + const char *s = env->GetStringUTFChars (v, nullptr); + char *ret = strdup (s); + env->ReleaseStringUTFChars (v, s); + + return ret; +} + +[[gnu::always_inline]] +static char* get_class_name (JNIEnv *env, jclass klass) noexcept +{ + if (java_lang_Class == nullptr || java_lang_Class_getName == nullptr) { + return strdup (INTERNAL_ERROR); + } + + auto className = static_cast(env->CallObjectMethod (klass, java_lang_Class_getName)); + if (className == nullptr) { + return strdup (UNKNOWN); + } + + return _mm_trace_get_c_string (env, className); +} + +char* _mm_trace_get_class_name (JNIEnv *env, jclass v) noexcept +{ + if (env == nullptr) { + return strdup (MISSING_ENV); + } + + if (v == nullptr) { + return strdup (NULL_PARAM); + } + + return get_class_name (env, v); +} + +char* _mm_trace_get_object_class_name (JNIEnv *env, jobject v) noexcept +{ + if (env == nullptr) { + return strdup (MISSING_ENV); + } + + if (v == nullptr) { + return strdup (NULL_PARAM); + } + + return get_class_name (env, env->GetObjectClass (v)); +} diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh new file mode 100644 index 00000000000..aa725eae783 --- /dev/null +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -0,0 +1,42 @@ +#include +#if !defined (__MARSHAL_METHODS_TRACING_HH) +#define __MARSHAL_METHODS_TRACING_HH + +#include + +#include "monodroid-glue-internal.hh" + +// These values MUST match those in the MarshalMethodsTracingMode managed enum (src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs) + +inline constexpr int32_t TracingModeNone = 0x00; +inline constexpr int32_t TracingModeBasic = 0x01; +inline constexpr int32_t TracingModeFull = 0x02; + +extern "C" { + [[gnu::visibility("hidden")]] + void _mm_trace_init (JNIEnv *env) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_params) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_params) noexcept; + + // Returns pointer to a dynamically allocated string, must be freed + [[gnu::visibility("hidden")]] + char* _mm_trace_get_class_name (JNIEnv *env, jclass v) noexcept; + + // Returns pointer to a dynamically allocated string, must be freed + [[gnu::visibility("hidden")]] + char* _mm_trace_get_object_class_name (JNIEnv *env, jobject v) noexcept; + + // Returns pointer to a dynamically allocated string, must be freed + [[gnu::visibility("hidden")]] + char* _mm_trace_get_c_string (JNIEnv *env, jstring v) noexcept; + + // Returns pointer to a constant string, must not be freed + [[gnu::visibility("hidden")]] + const char* _mm_trace_get_boolean_string (uint8_t v) noexcept; +} + +#endif // ndef __MARSHAL_METHODS_TRACING_HH diff --git a/src/monodroid/jni/marshal-methods-utilities.hh b/src/monodroid/jni/marshal-methods-utilities.hh new file mode 100644 index 00000000000..e141178c75b --- /dev/null +++ b/src/monodroid/jni/marshal-methods-utilities.hh @@ -0,0 +1,45 @@ +#if !defined (__MARSHAL_METHODS_UTILITIES_HH) +#define __MARSHAL_METHODS_UTILITIES_HH + +#include + +#include "xamarin-app.hh" + +#if defined (ANDROID) && defined (RELEASE) +namespace xamarin::android::internal +{ + class MarshalMethodsUtilities + { + static constexpr char Unknown[] = "Unknown"; + + public: + static const char* get_method_name (uint64_t id) noexcept + { + size_t i = 0; + while (mm_method_names[i].id != 0) { + if (mm_method_names[i].id == id) { + return mm_method_names[i].name; + } + i++; + } + + return Unknown; + } + + static const char* get_class_name (uint32_t class_index) noexcept + { + if (class_index >= marshal_methods_number_of_classes) { + return Unknown; + } + + return mm_class_names[class_index]; + } + + static uint64_t get_method_id (uint32_t mono_image_index, uint32_t method_token) noexcept + { + return (static_cast(mono_image_index) << 32) | method_token; + } + }; +} +#endif // def ANDROID && def RELEASE +#endif // ndef __MARSHAL_METHODS_UTILITIES_HH diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index abd3401671c..1ddb597dbae 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -2,6 +2,7 @@ #ifndef __MONODROID_GLUE_INTERNAL_H #define __MONODROID_GLUE_INTERNAL_H +#include #include #include @@ -64,6 +65,15 @@ namespace xamarin::android::internal } }; + // Values must be identical to those in src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs + enum class TraceKind : uint32_t + { + Java = 0x01, + Managed = 0x02, + Native = 0x04, + Signals = 0x08, + }; + class MonodroidRuntime { using pinvoke_api_map = tsl::robin_map< @@ -146,6 +156,8 @@ namespace xamarin::android::internal static constexpr std::string_view mono_component_diagnostics_tracing_name { "libmono-component-diagnostics_tracing.so" }; static constexpr hash_t mono_component_diagnostics_tracing_hash = xxhash::hash (mono_component_diagnostics_tracing_name); + static constexpr char xamarin_native_tracing_name[] = "libxamarin-native-tracing.so"; + public: static constexpr int XA_LOG_COUNTERS = MONO_COUNTER_JIT | MONO_COUNTER_METADATA | MONO_COUNTER_GC | MONO_COUNTER_GENERICS | MONO_COUNTER_INTERP; @@ -209,6 +221,22 @@ namespace xamarin::android::internal static PinvokeEntry* find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept; static void* handle_other_pinvoke_request (const char *library_name, hash_t library_name_hash, const char *entrypoint_name, hash_t entrypoint_name_hash) noexcept; static void* monodroid_pinvoke_override (const char *library_name, const char *entrypoint_name); + + template + static void load_symbol (void *handle, const char *name, TFunc*& fnptr) noexcept + { + char *err = nullptr; + void *symptr = monodroid_dlsym (handle, name, &err, nullptr); + + if (symptr == nullptr) { + log_warn (LOG_DEFAULT, "Failed to load symbol '%s' library with handle %p. %s", name, handle, err == nullptr ? "Unknown error" : err); + fnptr = nullptr; + return; + } + + fnptr = reinterpret_cast(symptr); + } + static void* monodroid_dlopen_ignore_component_or_load (hash_t hash, const char *name, int flags, char **err) noexcept; static void* monodroid_dlopen (const char *name, int flags, char **err) noexcept; static void* monodroid_dlopen (const char *name, int flags, char **err, void *user_data) noexcept; @@ -228,6 +256,7 @@ namespace xamarin::android::internal void set_debug_options (); void parse_gdb_options (); void mono_runtime_init (JNIEnv *env, dynamic_local_string& runtime_args); + void init_android_runtime (JNIEnv *env, jclass runtimeClass, jobject loader); void set_environment_variable_for_directory (const char *name, jstring_wrapper &value, bool createDirectory, mode_t mode); @@ -270,7 +299,6 @@ namespace xamarin::android::internal #if defined (RELEASE) static const char* get_method_name (uint32_t mono_image_index, uint32_t method_token) noexcept; - static const char* get_class_name (uint32_t class_index) noexcept; template static void get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept; diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 60eeeacbab4..cb5bb7c2096 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -682,7 +682,6 @@ MonodroidRuntime::mono_runtime_init ([[maybe_unused]] JNIEnv *env, [[maybe_unuse mono_install_assembly_preload_hook (open_from_update_dir, nullptr); #endif -#if defined (RELEASE) if (application_config.marshal_methods_enabled) { xamarin_app_init (env, get_function_pointer_at_startup); } @@ -1766,6 +1765,14 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag env->ReleaseStringChars (methods, methods_ptr); env->ReleaseStringChars (managedType, managedType_ptr); + const char *tmp = env->GetStringUTFChars (managedType, nullptr); + log_warn (LOG_ASSEMBLY, "Blazor: registering type %s", tmp); + env->ReleaseStringUTFChars (managedType, tmp); + + tmp = env->GetStringUTFChars (methods, nullptr); + log_warn (LOG_ASSEMBLY, "Blazor: methods: %s", tmp); + env->ReleaseStringUTFChars (methods, tmp); + if (FastTiming::enabled ()) [[unlikely]] { internal_timing->end_event (total_time_index, true /* uses_more_info */); diff --git a/src/monodroid/jni/monodroid-tracing.cc b/src/monodroid/jni/monodroid-tracing.cc new file mode 100644 index 00000000000..53731ff28fa --- /dev/null +++ b/src/monodroid/jni/monodroid-tracing.cc @@ -0,0 +1,89 @@ +#include +#include + +#include "java-interop-logger.h" +#include "mono/utils/details/mono-dl-fallback-types.h" +#include "monodroid-glue-internal.hh" +#include "native-tracing.hh" +#include "cppcompat.hh" +#include + +using namespace xamarin::android::internal; + +static decltype(xa_get_native_backtrace)* _xa_get_native_backtrace; +static decltype(xa_get_managed_backtrace)* _xa_get_managed_backtrace; +static decltype(xa_get_java_backtrace)* _xa_get_java_backtrace; +static decltype(xa_get_interesting_signal_handlers)* _xa_get_interesting_signal_handlers; +static bool tracing_init_done; + +static std::mutex tracing_init_lock {}; + +void +MonodroidRuntime::log_traces (JNIEnv *env, TraceKind kind, const char *first_line) noexcept +{ + if (!tracing_init_done) { + std::lock_guard lock (tracing_init_lock); + + char *err = nullptr; + void *handle = monodroid_dlopen (xamarin_native_tracing_name, MONO_DL_EAGER, &err, nullptr); + if (handle == nullptr) { + log_warn (LOG_DEFAULT, "Failed to load native tracing library '%s'. %s", xamarin_native_tracing_name, err == nullptr ? "Unknown error" : err); + } else { + load_symbol (handle, "xa_get_native_backtrace", _xa_get_native_backtrace); + load_symbol (handle, "xa_get_managed_backtrace", _xa_get_managed_backtrace); + load_symbol (handle, "xa_get_java_backtrace", _xa_get_java_backtrace); + load_symbol (handle, "xa_get_interesting_signal_handlers", _xa_get_interesting_signal_handlers); + } + + tracing_init_done = true; + } + + std::string trace; + if (first_line != nullptr) { + trace.append (first_line); + trace.append ("\n"); + } + + bool need_newline = false; + auto add_trace = [&] (c_unique_ptr const& data, const char *banner) -> void { + if (need_newline) { + trace.append ("\n "); + } else { + trace.append (" "); + } + + trace.append (banner); + if (!data) { + trace.append (": unavailable"); + } else { + trace.append (":\n"); + trace.append (data.get ()); + trace.append ("\n"); + } + need_newline = true; + }; + + if ((kind & TraceKind::Native) == TraceKind::Native) { + c_unique_ptr native { _xa_get_native_backtrace != nullptr ? _xa_get_native_backtrace () : nullptr }; + add_trace (native, "Native stacktrace"); + } + + if ((kind & TraceKind::Java) == TraceKind::Java && env != nullptr) { + c_unique_ptr java { _xa_get_java_backtrace != nullptr ?_xa_get_java_backtrace (env) : nullptr }; + add_trace (java, "Java stacktrace"); + } + + if ((kind & TraceKind::Managed) == TraceKind::Managed) { + c_unique_ptr managed { _xa_get_managed_backtrace != nullptr ? _xa_get_managed_backtrace () : nullptr }; + add_trace (managed, "Managed stacktrace"); + } + + if ((kind & TraceKind::Signals) == TraceKind::Signals) { + c_unique_ptr signals { _xa_get_interesting_signal_handlers != nullptr ? _xa_get_interesting_signal_handlers () : nullptr }; + add_trace (signals, "Signal handlers"); + } + + // Use this call because it is slightly faster (doesn't need to parse the format) and it doesn't truncate longer + // strings (like the stack traces we've just produced), unlike __android_log_vprint used by our `log_*` functions + __android_log_write (ANDROID_LOG_INFO, SharedConstants::LOG_CATEGORY_NAME_MONODROID, trace.c_str ()); +} diff --git a/src/monodroid/jni/native-tracing.cc b/src/monodroid/jni/native-tracing.cc new file mode 100644 index 00000000000..1111c1adeca --- /dev/null +++ b/src/monodroid/jni/native-tracing.cc @@ -0,0 +1,336 @@ +#include +#include +#include + +#include +#include + +#include + +#include "native-tracing.hh" +#include "shared-constants.hh" +#include "cppcompat.hh" + +constexpr int PRIORITY = ANDROID_LOG_INFO; + +static void append_frame_number (std::string &trace, size_t count) noexcept; +static unw_word_t adjust_address (unw_word_t addr) noexcept; +static void init_jni (JNIEnv *env) noexcept; + +// java.lang.Thread +static jclass java_lang_Thread; +static jmethodID java_lang_Thread_currentThread; +static jmethodID java_lang_Thread_getStackTrace; + +// java.lang.StackTraceElement +static jclass java_lang_StackTraceElement; +static jmethodID java_lang_StackTraceElement_toString; + +static std::mutex java_init_lock; + +const char* xa_get_managed_backtrace () noexcept +{ + std::string trace { "TODO: implement" }; + + return strdup (trace.c_str ()); +} + +const char* xa_get_native_backtrace () noexcept +{ + constexpr int FRAME_OFFSET_WIDTH = sizeof(uintptr_t) * 2; + + unw_cursor_t cursor; + unw_context_t uc; + unw_word_t ip; + unw_word_t offp; + std::array name_buf; + std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible + // additions (sign, padding, punctuation etc) + const char *symbol_name; + Dl_info info; + + unw_getcontext (&uc); + unw_init_local (&cursor, &uc); + + size_t frame_counter = 0; + + std::string trace; + while (unw_step (&cursor) > 0) { + if (!trace.empty ()) { + trace.append ("\n"); + } + + unw_get_reg (&cursor, UNW_REG_IP, &ip); + ip = adjust_address (ip); + + auto ptr = reinterpret_cast(ip); + const char *fname = nullptr; + const void *symptr = nullptr; + unw_word_t frame_offset = 0; + bool info_valid = false; + + if (dladdr (ptr, &info) != 0) { + if (info.dli_fname != nullptr) { + fname = info.dli_fname; + } + symptr = info.dli_sname; + frame_offset = ip - reinterpret_cast(info.dli_fbase); + info_valid = true; + } else { + frame_offset = ip; + } + + append_frame_number (trace, frame_counter++); + + std::snprintf (num_buf.data (), num_buf.size (), "%0*zx (", FRAME_OFFSET_WIDTH, frame_offset); + trace.append (num_buf.data ()); + std::snprintf (num_buf.data (), num_buf.size (), "%p) ", ptr); + trace.append (num_buf.data ()); + + // TODO: consider searching /proc/self/maps for the beginning of the corresponding region to calculate the + // correct offset (like done in bionic stack trace) + trace.append (fname != nullptr ? fname : "[anonymous]"); + + bool symbol_name_allocated = false; + offp = 0; + if (unw_get_proc_name (&cursor, name_buf.data (), name_buf.size (), &offp) == 0) { + symbol_name = name_buf.data (); + } else if (info_valid && info.dli_sname != nullptr) { + symbol_name = info.dli_sname; + } else { + symbol_name = nullptr; + } + offp = adjust_address (offp); + + if (symbol_name != nullptr) { + char *demangled_symbol_name; + int demangle_status; + + // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler + demangled_symbol_name = abi::__cxa_demangle (symbol_name, nullptr, nullptr, &demangle_status); + symbol_name_allocated = demangle_status == 0 && demangled_symbol_name != nullptr; + if (symbol_name_allocated) { + symbol_name = demangled_symbol_name; + } + } + + if (symbol_name != nullptr) { + trace.append (" "); + trace.append (symbol_name); + if (offp != 0) { + trace.append (" + "); + std::snprintf (num_buf.data (), num_buf.size (), "%zu", offp); + trace.append (num_buf.data ()); + } + } + + if (symptr != nullptr) { + trace.append (" (symaddr: "); + std::snprintf (num_buf.data (), num_buf.size (), "%p", symptr); + trace.append (num_buf.data ()); + trace.append (")"); + } + + if (symbol_name_allocated && symbol_name != nullptr) { + std::free (reinterpret_cast(const_cast(symbol_name))); + } + } + + return strdup (trace.c_str ()); +} + +const char* xa_get_java_backtrace (JNIEnv *env) noexcept +{ + init_jni (env); + + // TODO: error handling + jobject current_thread = env->CallStaticObjectMethod (java_lang_Thread, java_lang_Thread_currentThread); + auto stack_trace_array = static_cast(env->CallNonvirtualObjectMethod (current_thread, java_lang_Thread, java_lang_Thread_getStackTrace)); + jsize nframes = env->GetArrayLength (stack_trace_array); + std::string trace; + + for (jsize i = 0; i < nframes; i++) { + jobject frame = env->GetObjectArrayElement (stack_trace_array, i); + auto frame_desc_java = static_cast(env->CallObjectMethod (frame, java_lang_StackTraceElement_toString)); + const char *frame_desc = env->GetStringUTFChars (frame_desc_java, nullptr); + + if (!trace.empty ()) { + trace.append ("\n"); + } + + append_frame_number (trace, i); + trace.append (frame_desc); + env->ReleaseStringUTFChars (frame_desc_java, frame_desc); + } + + return strdup (trace.c_str ()); +} + +[[gnu::always_inline]] +unw_word_t adjust_address (unw_word_t addr) noexcept +{ + // This is what bionic does, let's do the same so that our backtrace addresses match bionic output + // Code copied verbatim from + // https://android.googlesource.com/platform/bionic/+/refs/tags/android-13.0.0_r37/libc/bionic/execinfo.cpp#50 + if (addr != 0) { +#if defined (__arm__) + // If the address is suspiciously low, do nothing to avoid a segfault trying + // to access this memory. + if (addr >= 4096) { + // Check bits [15:11] of the first halfword assuming the instruction + // is 32 bits long. If the bits are any of these values, then our + // assumption was correct: + // b11101 + // b11110 + // b11111 + // Otherwise, this is a 16 bit instruction. + uint16_t value = (*reinterpret_cast(addr - 2)) >> 11; + if (value == 0x1f || value == 0x1e || value == 0x1d) { + return addr - 4; + } + + return addr - 2; + } +#elif defined (__aarch64__) + // All instructions are 4 bytes long, skip back one instruction. + return addr - 4; +#elif defined (__i386__) || defined (__x86_64__) + // It's difficult to decode exactly where the previous instruction is, + // so subtract 1 to estimate where the instruction lives. + return addr - 1; +#endif + } + + return addr; +} + +const char* xa_get_interesting_signal_handlers () noexcept +{ + constexpr char SA_SIGNAL[] = "signal"; + constexpr char SA_SIGACTION[] = "sigaction"; + constexpr char SIG_IGNORED[] = "[ignored]"; + + std::array num_buf; + Dl_info info; + struct sigaction cur_sa; + std::string trace; + + for (int i = 0; i < _NSIG; i++) { + if (sigaction (i, nullptr, &cur_sa) != 0) { + continue; // ignore + } + + void *handler; + const char *installed_with; + if (cur_sa.sa_flags & SA_SIGINFO) { + handler = reinterpret_cast(cur_sa.sa_sigaction); + installed_with = SA_SIGACTION; + } else { + handler = reinterpret_cast(cur_sa.sa_handler); + installed_with = SA_SIGNAL; + } + + if (handler == SIG_DFL) { + continue; + } + + if (!trace.empty ()) { + trace.append ("\n"); + } + + const char *symbol_name = nullptr; + const char *file_name = nullptr; + if (handler == SIG_IGN) { + symbol_name = SIG_IGNORED; + } else { + if (dladdr (handler, &info) != 0) { + symbol_name = info.dli_sname; + file_name = info.dli_fname; + } + } + + trace.append (" "); + trace.append (strsignal (i)); + trace.append (" ("); + std::snprintf (num_buf.data (), num_buf.size (), "%d", i); + trace.append (num_buf.data ()); + trace.append ("), with "); + trace.append (installed_with); + trace.append (": "); + + if (file_name != nullptr) { + trace.append (file_name); + trace.append (" "); + } + + if (symbol_name == nullptr) { + std::snprintf (num_buf.data (), num_buf.size (), "%p", handler); + trace.append (num_buf.data ()); + } else { + trace.append (symbol_name); + } + } + + return strdup (trace.c_str ()); +} + +[[gnu::always_inline]] +void append_frame_number (std::string &trace, size_t count) noexcept +{ + std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible + // additions (sign, padding, punctuation etc) + trace.append (" #"); + std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", count); + trace.append (num_buf.data ()); +} + +void init_jni (JNIEnv *env) noexcept +{ + // We might be called more than once, ignore all but the first call + if (java_lang_Thread != nullptr) { + return; + } + + std::lock_guard lock (java_init_lock); + + java_lang_Thread = to_gref (env, env->FindClass ("java/lang/Thread")); + java_lang_Thread_currentThread = env->GetStaticMethodID (java_lang_Thread, "currentThread", "()Ljava/lang/Thread;"); + java_lang_Thread_getStackTrace = env->GetMethodID (java_lang_Thread, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); + java_lang_StackTraceElement = to_gref (env, env->FindClass ("java/lang/StackTraceElement")); + java_lang_StackTraceElement_toString = env->GetMethodID (java_lang_StackTraceElement, "toString", "()Ljava/lang/String;"); + + // We check for the Java exception and possible null pointers only here, since all the calls JNI before the last one + // would do the exception check for us. + if (env->ExceptionOccurred ()) { + env->ExceptionDescribe (); + env->ExceptionClear (); + xamarin::android::Helpers::abort_application (); + } + + bool all_found = assert_valid_jni_pointer (java_lang_Thread, "class", "java.lang.Thread"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_currentThread, "method", "java.lang.Thread.currentThread ()"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_getStackTrace, "method", "java.lang.Thread.getStackTrace ()"); + all_found &= assert_valid_jni_pointer (java_lang_Thread, "class", "java.lang.StackTraceElement"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_currentThread, "method", "java.lang.StackTraceElement.toString ()"); + + if (!all_found) { + xamarin::android::Helpers::abort_application (); + } +} + +bool assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept +{ + if (o != nullptr) { + return true; + } + + __android_log_print ( + PRIORITY, + xamarin::android::internal::SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + "missing Java %s: %s", + missing_kind, + missing_name + ); + + return false; +} diff --git a/src/monodroid/jni/native-tracing.hh b/src/monodroid/jni/native-tracing.hh new file mode 100644 index 00000000000..6f08e7273b3 --- /dev/null +++ b/src/monodroid/jni/native-tracing.hh @@ -0,0 +1,46 @@ +#if !defined (__NATIVE_TRACING_HH) +#define __NATIVE_TRACING_HH + +#include +#include +#include + +#define UNW_LOCAL_ONLY +#include + +// Public API must not expose any types that are part of libc++ - we don't know what version of the +// library (if any) is used by the application we're embedded in. +// +// For the same reason, we cannot return memory allocated with the `new` operator - the implementation +// used by the application's C++ code might be incompatible. For this reason, any dynamically allocated +// memory we return to the caller is allocated with the libc's `malloc` +// +extern "C" { + [[gnu::visibility("default")]] + const char* xa_get_native_backtrace () noexcept; + + [[gnu::visibility("default")]] + const char* xa_get_java_backtrace (JNIEnv *env) noexcept; + + [[gnu::visibility("default")]] + const char* xa_get_managed_backtrace () noexcept; + + [[gnu::visibility("default")]] + const char* xa_get_interesting_signal_handlers () noexcept; +} + +template +[[gnu::always_inline]] +inline TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept +{ + if (lref == nullptr) { + return nullptr; + } + + auto ret = static_cast (env->NewGlobalRef (lref)); + env->DeleteLocalRef (lref); + return ret; +} + +bool assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept; +#endif // ndef __NATIVE_TRACING_HH diff --git a/src/monodroid/jni/new_delete.cc b/src/monodroid/jni/new_delete.cc index 074dfe35610..dcd3eb1d20d 100644 --- a/src/monodroid/jni/new_delete.cc +++ b/src/monodroid/jni/new_delete.cc @@ -22,7 +22,9 @@ operator new (size_t size) { void* p = do_alloc (size); if (p == nullptr) { +#if !defined (XAMARIN_TRACING) log_fatal (LOG_DEFAULT, "Out of memory in the `new` operator"); +#endif xamarin::android::Helpers::abort_application (); } diff --git a/src/monodroid/jni/pinvoke-override-api.cc b/src/monodroid/jni/pinvoke-override-api.cc index 94232f60045..09710484807 100644 --- a/src/monodroid/jni/pinvoke-override-api.cc +++ b/src/monodroid/jni/pinvoke-override-api.cc @@ -363,6 +363,15 @@ _monodroid_lookup_replacement_method_info (const char *jniSourceType, const char return JniRemapping::lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); } +static void +monodroid_log_traces (uint32_t kind, const char *first_line) +{ + JNIEnv *env = osBridge.ensure_jnienv (); + auto tk = static_cast(kind); + + monodroidRuntime.log_traces (env, tk, first_line); +} + #include "pinvoke-tables.include" MonodroidRuntime::pinvoke_library_map MonodroidRuntime::other_pinvoke_map (MonodroidRuntime::LIBRARY_MAP_INITIAL_BUCKET_COUNT); diff --git a/src/monodroid/jni/shared-constants.hh b/src/monodroid/jni/shared-constants.hh index 10e0c38ec00..7a92035a5fd 100644 --- a/src/monodroid/jni/shared-constants.hh +++ b/src/monodroid/jni/shared-constants.hh @@ -69,6 +69,43 @@ namespace xamarin::android::internal // Documented in NDK's comments static constexpr size_t MAX_LOGCAT_MESSAGE_LENGTH = 1023; + + static constexpr char LOG_CATEGORY_NAME_NONE[] = "*none*"; + static constexpr char LOG_CATEGORY_NAME_MONODROID[] = "monodroid"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_ASSEMBLY[] ="monodroid-assembly"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_DEBUG[] = "monodroid-debug"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_GC[] = "monodroid-gc"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_GREF[] = "monodroid-gref"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_LREF[] = "monodroid-lref"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_TIMING[] = "monodroid-timing"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_BUNDLE[] = "monodroid-bundle"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_NETWORK[] = "monodroid-network"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_NETLINK[] = "monodroid-netlink"; + static constexpr char LOG_CATEGORY_NAME_ERROR[] = "*error*"; + +#if defined (__aarch64__) + static constexpr bool IsARM64 = true; +#else + static constexpr bool IsARM64 = false; +#endif + +#if defined (__arm__) + static constexpr bool IsARM32 = true; +#else + static constexpr bool IsARM32 = false; +#endif + +#if defined (__i386__) + static constexpr bool IsX86 = true; +#else + static constexpr bool IsX86 = false; +#endif + +#if defined (__x86_64__) + static constexpr bool IsX64 = true; +#else + static constexpr bool IsX64 = false; +#endif }; } #endif // __SHARED_CONSTANTS_HH diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index b4598519eb0..583bfed2141 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -5,36 +5,17 @@ #include "monodroid-glue-internal.hh" #include "mono-image-loader.hh" +#include "marshal-methods-utilities.hh" using namespace xamarin::android::internal; -static constexpr char Unknown[] = "Unknown"; - const char* MonodroidRuntime::get_method_name (uint32_t mono_image_index, uint32_t method_token) noexcept { - uint64_t id = (static_cast(mono_image_index) << 32) | method_token; + uint64_t id = MarshalMethodsUtilities::get_method_id (mono_image_index, method_token); log_debug (LOG_ASSEMBLY, "MM: looking for name of method with id 0x%llx, in mono image at index %u", id, mono_image_index); - size_t i = 0; - while (mm_method_names[i].id != 0) { - if (mm_method_names[i].id == id) { - return mm_method_names[i].name; - } - i++; - } - - return Unknown; -} - -const char* -MonodroidRuntime::get_class_name (uint32_t class_index) noexcept -{ - if (class_index >= marshal_methods_number_of_classes) { - return Unknown; - } - - return mm_class_names[class_index]; + return MarshalMethodsUtilities::get_method_name (id); } template @@ -45,7 +26,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas LOG_ASSEMBLY, "MM: Trying to look up pointer to method '%s' (token 0x%x) in class '%s' (index %u)", get_method_name (mono_image_index, method_token), method_token, - get_class_name (class_index), class_index + MarshalMethodsUtilities::get_class_name (class_index), class_index ); if (class_index >= marshal_methods_number_of_classes) [[unlikely]] { @@ -85,7 +66,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas LOG_DEFAULT, "Failed to obtain function pointer to method '%s' in class '%s'", get_method_name (mono_image_index, method_token), - get_class_name (class_index) + MarshalMethodsUtilities::get_class_name (class_index) ); log_fatal ( diff --git a/src/monodroid/monodroid.csproj b/src/monodroid/monodroid.csproj index c4dbc2d7b5c..100496fe8fc 100644 --- a/src/monodroid/monodroid.csproj +++ b/src/monodroid/monodroid.csproj @@ -6,7 +6,7 @@ Exe false - + @@ -14,8 +14,9 @@ - + +