浏览代码

C#+iOS: Fix P/Invoke symbols being stripped by the linker

We use `Mono.Cecil` to search for P/Invoke methods in assemblies in
order to collect symbols that we must prevent from being stripped.

We could pass the symbols as `-u` linker arguments (`-Wl,-u,symbol`)
for the native target (not for the project), but it was simpler to
generate referencing code and avoid changes to Godot's iOS exporter.
Ignacio Roldán Etchevery 4 年之前
父节点
当前提交
21a739e3b1

+ 109 - 6
modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs

@@ -4,7 +4,10 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Text;
+using Godot;
+using GodotTools.Core;
 using GodotTools.Internals;
+using Mono.Cecil;
 using Directory = GodotTools.Utils.Directory;
 using File = GodotTools.Utils.File;
 using OS = GodotTools.Utils.OS;
@@ -415,6 +418,30 @@ MONO_AOT_MODE_LAST = 1000,
             cppCode.AppendLine($"\tmono_jit_set_aot_mode({aotModeStr});");
 
             cppCode.AppendLine("} // gd_mono_setup_aot");
+
+            // Prevent symbols from being stripped
+
+            var symbols = CollectSymbols(assemblies);
+
+            foreach (string symbol in symbols)
+            {
+                cppCode.Append("extern void *");
+                cppCode.Append(symbol);
+                cppCode.AppendLine(";");
+            }
+
+            cppCode.AppendLine("__attribute__((used)) __attribute__((optnone)) static void __godot_symbol_referencer() {");
+            cppCode.AppendLine("\tvoid *aux;");
+
+            foreach (string symbol in symbols)
+            {
+                cppCode.Append("\taux = ");
+                cppCode.Append(symbol);
+                cppCode.AppendLine(";");
+            }
+
+            cppCode.AppendLine("} // __godot_symbol_referencer");
+
             cppCode.AppendLine("} // extern \"C\"");
             cppCode.AppendLine("#endif // !TARGET_OS_SIMULATOR");
 
@@ -456,13 +483,89 @@ MONO_AOT_MODE_LAST = 1000,
             exporter.AddIosFramework("libiconv.tbd");
             exporter.AddIosFramework("GSS.framework");
             exporter.AddIosFramework("CFNetwork.framework");
+        }
+
+        private static List<string> CollectSymbols(IDictionary<string, string> assemblies)
+        {
+            var symbols = new List<string>();
+
+            var resolver = new DefaultAssemblyResolver();
+            foreach (var searchDir in resolver.GetSearchDirectories())
+                resolver.RemoveSearchDirectory(searchDir);
+            foreach (var searchDir in assemblies
+                .Select(a => a.Value.GetBaseDir().NormalizePath()).Distinct())
+            {
+                resolver.AddSearchDirectory(searchDir);
+            }
+
+            AssemblyDefinition ReadAssembly(string fileName)
+                => AssemblyDefinition.ReadAssembly(fileName,
+                    new ReaderParameters {AssemblyResolver = resolver});
+
+            foreach (var assembly in assemblies)
+            {
+                using (var assemblyDef = ReadAssembly(assembly.Value))
+                    CollectSymbolsFromAssembly(assemblyDef, symbols);
+            }
+
+            return symbols;
+        }
+
+        private static void CollectSymbolsFromAssembly(AssemblyDefinition assembly, ICollection<string> symbols)
+        {
+            if (!assembly.MainModule.HasTypes)
+                return;
+
+            foreach (var type in assembly.MainModule.Types)
+            {
+                CollectSymbolsFromType(type, symbols);
+            }
+        }
+
+        private static void CollectSymbolsFromType(TypeDefinition type, ICollection<string> symbols)
+        {
+            if (type.HasNestedTypes)
+            {
+                foreach (var nestedType in type.NestedTypes)
+                    CollectSymbolsFromType(nestedType, symbols);
+            }
 
-            // Force load and export dynamic are needed for the linker to not strip required symbols.
-            // In theory we shouldn't be relying on this for P/Invoked functions (as is the case with
-            // functions in System.Native/libmono-native). Instead, we should use cecil to search for
-            // DllImports in assemblies and pass them to 'ld' as '-u/--undefined {pinvoke_symbol}'.
-            exporter.AddIosLinkerFlags("-rdynamic");
-            exporter.AddIosLinkerFlags($"-force_load \"$(SRCROOT)/{MonoFrameworkFile("libmono-native")}\"");
+            if (type.Module.HasModuleReferences)
+                CollectPInvokeSymbols(type, symbols);
+        }
+
+        private static void CollectPInvokeSymbols(TypeDefinition type, ICollection<string> symbols)
+        {
+            if (!type.HasMethods)
+                return;
+
+            foreach (var method in type.Methods)
+            {
+                if (!method.IsPInvokeImpl || !method.HasPInvokeInfo)
+                    continue;
+
+                var pInvokeInfo = method.PInvokeInfo;
+
+                if (pInvokeInfo == null)
+                    continue;
+
+                switch (pInvokeInfo.Module.Name)
+                {
+                    case "__Internal":
+                    case "libSystem.Net.Security.Native":
+                    case "System.Net.Security.Native":
+                    case "libSystem.Security.Cryptography.Native.Apple":
+                    case "System.Security.Cryptography.Native.Apple":
+                    case "libSystem.Native":
+                    case "System.Native":
+                    case "libSystem.Globalization.Native":
+                    case "System.Globalization.Native":
+                    {
+                        symbols.Add(pInvokeInfo.EntryPoint);
+                        break;
+                    }
+                }
+            }
         }
 
         /// Converts an assembly name to a valid symbol name in the same way the AOT compiler does

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj

@@ -20,6 +20,7 @@
     <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+    <PackageReference Include="Mono.Cecil" Version="0.11.3" />
     <Reference Include="GodotSharp">
       <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath>
       <Private>False</Private>