1 //
2 // Copyright (c) 2010-2025 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Linq;
10 using Antmicro.Renode.Logging;
11 using System.Runtime.InteropServices;
12 using System.Reflection;
13 using System.Reflection.Emit;
14 using System.Collections.Generic;
15 using Dynamitey;
16 using Antmicro.Renode.Core;
17 
18 namespace Antmicro.Renode.Utilities.Binding
19 {
20     /// <summary>
21     /// The <c>NativeBinder</c> class lets one bind managed delegates from given class to functions
22     /// of a given native library and vice versa.
23     /// </summary>
24     public sealed class NativeBinder : IDisposable
25     {
NativeBinder()26         static NativeBinder()
27         {
28             var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
29                 new AssemblyName(nameof(NativeBinder)), AssemblyBuilderAccess.Run);
30             moduleBuilder = assemblyBuilder.DefineDynamicModule(nameof(NativeBinder));
31         }
32 
33         /// <summary>
34         /// Initializes a new instance of the <see cref="Antmicro.Renode.Utilities.Runtime.NativeBinder"/> class
35         /// and performs binding between the class and given library.
36         /// </summary>
37         /// <param name='classToBind'>
38         /// Class to bind.
39         /// </param>
40         /// <param name='libraryFile'>
41         /// Library file to bind.
42         /// </param>
43         /// <remarks>
44         /// Please note that:
45         /// <list type="bullet">
46         /// <item><description>
47         /// This works now only with ELF libraries.
48         /// </description></item>
49         /// <item><description>
50         /// You have to hold the reference to created native binder as long as the native functions
51         /// can call managed one.
52         /// </description></item>
53         /// <item><description>
54         /// You should dispose native binder after use to free memory taken by the native library.
55         /// </description></item>
56         /// </list>
57         /// </remarks>
NativeBinder(IEmulationElement classToBind, string libraryFile)58         public NativeBinder(IEmulationElement classToBind, string libraryFile)
59         {
60             delegateStore = new object[0];
61 #if !PLATFORM_WINDOWS && !NET
62             // According to https://github.com/dotnet/runtime/issues/26381#issuecomment-394765279,
63             // mono does not enforce the restrictions on pinned GCHandle objects.
64             // On .NET Core trying to pin unallowed object throws exception about non-primitive or non-blittable data.
65             handles = new GCHandle[0];
66 #endif
67             this.classToBind = classToBind;
68             libraryAddress = SharedLibraries.LoadLibrary(libraryFile);
69             libraryFileName = libraryFile;
70             var importFields = classToBind.GetType().GetAllFields().Where(x => x.IsDefined(typeof(ImportAttribute), false)).ToList();
71             EnsureWrappersType(importFields);
72             wrappersObj = CreateWrappersObject();
73             try
74             {
75                 ResolveCallsToNative(importFields);
76                 ResolveCallsToManaged();
77             }
78             catch
79             {
80                 Dispose();
81                 throw;
82             }
83         }
84 
Dispose()85         public void Dispose()
86         {
87             DisposeInner();
88             GC.SuppressFinalize(this);
89         }
90 
DisposeInner()91         private void DisposeInner()
92         {
93 #if !PLATFORM_WINDOWS && !NET
94             foreach(var handle in handles)
95             {
96                 handle.Free();
97             }
98 #endif
99             if(libraryAddress != IntPtr.Zero)
100             {
101                 SharedLibraries.UnloadLibrary(libraryAddress);
102                 libraryAddress = IntPtr.Zero;
103             }
104         }
105 
~NativeBinder()106         ~NativeBinder()
107         {
108             DisposeInner();
109         }
110 
EnsureWrappersType(List<FieldInfo> importFields)111         private void EnsureWrappersType(List<FieldInfo> importFields)
112         {
113             // This type lives in our NativeBinder dynamic assembly (see the static constructor).
114             // This means that if we find such a type, it has to have been created by us and we can
115             // just use it without verifying its layout.
116             // Its name is derived from the full name of the class to bind (including its namespace): for example,
117             // the wrappers type for Some.Namespace.Class will be NativeBinder.Some.Namespace.ClassWrappers.
118             var wrappersTypeName = $"NativeBinder.{classToBind.GetType().FullName}Wrappers";
119 
120             lock(moduleBuilder)
121             {
122                 wrappersType = moduleBuilder.GetType(wrappersTypeName);
123 
124                 if(wrappersType == null)
125                 {
126                     var typeBuilder = moduleBuilder.DefineType(wrappersTypeName);
127                     typeBuilder.DefineField(nameof(ExceptionKeeper), typeof(ExceptionKeeper), FieldAttributes.Public);
128                     typeBuilder.DefineField(nameof(classToBind), classToBind.GetType(), FieldAttributes.Public);
129 
130                     foreach(var field in importFields)
131                     {
132                         var attribute = (ImportAttribute)field.GetCustomAttributes(false).Single(x => x is ImportAttribute);
133                         if(attribute.UseExceptionWrapper)
134                         {
135                             typeBuilder.DefineField(field.Name, field.FieldType, FieldAttributes.Public);
136                         }
137                     }
138 
139                     wrappersType = typeBuilder.CreateType();
140                 }
141 
142                 exceptionKeeperField = wrappersType.GetField(nameof(ExceptionKeeper));
143                 instanceField = wrappersType.GetField(nameof(classToBind));
144             }
145         }
146 
CreateWrappersObject()147         private object CreateWrappersObject()
148         {
149             var wrappers = Activator.CreateInstance(wrappersType);
150             exceptionKeeperField.SetValue(wrappers, new ExceptionKeeper());
151             instanceField.SetValue(wrappers, classToBind);
152             return wrappers;
153         }
154 
WrapImport(FieldInfo importField, FieldInfo innerField)155         private Delegate WrapImport(FieldInfo importField, FieldInfo innerField)
156         {
157             var throwExceptions = typeof(ExceptionKeeper).GetMethod(nameof(ExceptionKeeper.ThrowExceptions));
158             var importType = importField.FieldType;
159             var invoke = importType.GetMethod("Invoke");
160             Type[] paramTypes = invoke.GetParameters().Select(p => p.ParameterType).ToArray();
161             Type[] paramTypesWithWrappersType = new Type[] { wrappersType }.Concat(paramTypes).ToArray();
162             // We need skipVisibility to handle methods that have a protected or private type as a parameter or return value.
163             // Interesting tidbit: this access check is only performed by .NET Framework, Mono and .NET Core allow it
164             // without skipVisibility.
165             DynamicMethod method = new DynamicMethod(importField.Name, invoke.ReturnType, paramTypesWithWrappersType, wrappersType, skipVisibility: true);
166             var il = method.GetILGenerator();
167 
168             il.Emit(OpCodes.Ldarg_0); // wrappersType instance
169             il.Emit(OpCodes.Ldfld, innerField);
170             for(int i = 0; i < paramTypes.Length; ++i)
171             {
172                 il.Emit(OpCodes.Ldarg, 1 + i);
173             }
174             il.EmitCall(OpCodes.Callvirt, invoke, null); // call inner
175 
176             il.Emit(OpCodes.Ldarg_0);
177             il.Emit(OpCodes.Ldfld, exceptionKeeperField);
178             il.EmitCall(OpCodes.Call, throwExceptions, null); // call ExceptionKeeper.ThrowExceptions
179 
180             il.Emit(OpCodes.Ret);
181 
182             return method.CreateDelegate(importType, wrappersObj);
183         }
184 
ResolveCallsToNative(List<FieldInfo> importFields)185         private void ResolveCallsToNative(List<FieldInfo> importFields)
186         {
187             classToBind.NoisyLog("Binding managed -> native calls.");
188 
189             foreach (var field in importFields)
190             {
191                 var attribute = (ImportAttribute)field.GetCustomAttributes(false).First(x => x is ImportAttribute);
192                 var cName = GetWrappedName(attribute.Name ?? GetCName(field.Name), attribute.UseExceptionWrapper);
193                 classToBind.NoisyLog(string.Format("(NativeBinder) Binding {1} as {0}.", field.Name, cName));
194                 Delegate result = null;
195                 try
196                 {
197                     var address = SharedLibraries.GetSymbolAddress(libraryAddress, cName);
198                     // Dynamically create a non-generic delegate type and make one from a function pointer
199                     var invoke = field.FieldType.GetMethod("Invoke");
200                     var delegateType = DelegateTypeFromParamsAndReturn(invoke.GetParameters().Select(p => p.ParameterType), invoke.ReturnType);
201                     var generatedDelegate = Marshal.GetDelegateForFunctionPointer(address, delegateType);
202                     // The method returned by GetDelegateForFunctionPointer is static on Mono, but not .NET
203                     var delegateTarget = generatedDelegate.Method.IsStatic ? null : generatedDelegate;
204                     // "Convert" the delegate from the dynamically-generated non-generic type
205                     // to the field type used in the bound class (which might be generic)
206                     result = Delegate.CreateDelegate(field.FieldType, delegateTarget, generatedDelegate.Method);
207                 }
208                 catch
209                 {
210                     if(!attribute.Optional)
211                     {
212                         throw;
213                     }
214                 }
215 
216                 if(attribute.UseExceptionWrapper && result != null)
217                 {
218                     var innerField = wrappersType.GetField(field.Name);
219                     innerField.SetValue(wrappersObj, result);
220                     field.SetValue(classToBind, WrapImport(field, innerField));
221                 }
222                 else
223                 {
224                     field.SetValue(classToBind, result);
225                 }
226             }
227         }
228 
WrapExport(Type delegateType, MethodInfo innerMethod)229         private Delegate WrapExport(Type delegateType, MethodInfo innerMethod)
230         {
231             var addException = typeof(ExceptionKeeper).GetMethod(nameof(ExceptionKeeper.AddException));
232             var nativeUnwind = classToBind.GetType().GetMethod(nameof(INativeUnwindable.NativeUnwind));
233 
234             Type[] paramTypes = innerMethod.GetParameters().Select(p => p.ParameterType).ToArray();
235             Type[] paramTypesWithWrappersType = new Type[] { wrappersType }.Concat(paramTypes).ToArray();
236             // We need skipVisibility to be able to access superclass private methods.
237             var attacheeWrapper = new DynamicMethod(innerMethod.Name + "Wrapper", innerMethod.ReturnType, paramTypesWithWrappersType, wrappersType, skipVisibility: true);
238             var il = attacheeWrapper.GetILGenerator();
239             LocalBuilder retval = null;
240             if(innerMethod.ReturnType != typeof(void))
241             {
242                 retval = il.DeclareLocal(innerMethod.ReturnType);
243             }
244             var exception = il.DeclareLocal(typeof(Exception));
245 
246             Label @try = il.BeginExceptionBlock();
247             il.Emit(OpCodes.Ldarg_0);
248             il.Emit(OpCodes.Ldfld, instanceField);
249             for(int i = 0; i < paramTypes.Length; ++i)
250             {
251                 il.Emit(OpCodes.Ldarg, 1 + i);
252             }
253 
254             il.EmitCall(OpCodes.Call, innerMethod, null);
255             if(retval != null)
256             {
257                 il.Emit(OpCodes.Stloc, retval);
258             }
259             il.Emit(OpCodes.Leave, @try);
260             il.BeginCatchBlock(typeof(Exception));
261             il.Emit(OpCodes.Stloc, exception); // We need this local because MSIL doesn't have an instruction
262             il.Emit(OpCodes.Ldarg_0);          // that swaps the top two values on the stack.
263             il.Emit(OpCodes.Ldfld, exceptionKeeperField);
264             il.Emit(OpCodes.Ldloc, exception);
265             il.EmitCall(OpCodes.Call, addException, null);
266             if(nativeUnwind != null)
267             {
268                 il.Emit(OpCodes.Ldarg_0);
269                 il.Emit(OpCodes.Ldfld, instanceField);
270                 il.EmitCall(OpCodes.Call, nativeUnwind, null);
271             }
272             else
273             {
274                 var typeName = classToBind.GetType().FullName;
275                 il.EmitWriteLine($"Export to type '{typeName}' which is not unwindable threw an exception.");
276 
277                 // Print exceptions.
278                 var printExceptions = typeof(ExceptionKeeper).GetMethod(nameof(ExceptionKeeper.PrintExceptions));
279                 il.Emit(OpCodes.Ldarg_0);
280                 il.Emit(OpCodes.Ldfld, exceptionKeeperField);
281                 il.EmitCall(OpCodes.Call, printExceptions, null); // call ExceptionKeeper.PrintExceptions
282 
283                 il.Emit(OpCodes.Ldc_I4_1);
284                 il.EmitCall(OpCodes.Call, typeof(Environment).GetMethod(nameof(Environment.Exit)), null);
285             }
286             il.EndExceptionBlock();
287             if(retval != null)
288             {
289                 il.Emit(OpCodes.Ldloc, retval);
290             }
291             il.Emit(OpCodes.Ret);
292 
293             return attacheeWrapper.CreateDelegate(delegateType, wrappersObj);
294         }
295 
ResolveCallsToManaged()296         private void ResolveCallsToManaged()
297         {
298             classToBind.NoisyLog("Binding native -> managed calls.");
299             var symbols = SharedLibraries.GetAllSymbols(libraryFileName);
300             var classMethods = classToBind.GetType().GetAllMethods().ToArray();
301             var exportedMethods = new List<MethodInfo>();
302             foreach(var originalCandidate in symbols.Where(x => x.Contains("renode_external_attach")))
303             {
304                 var candidate  = FilterCppName(originalCandidate);
305                 var parts = candidate.Split(new [] { "__" }, StringSplitOptions.RemoveEmptyEntries);
306                 var cName = parts[2];
307                 var expectedTypeName = parts[1];
308                 var csName = cName.StartsWith('$') ? GetCSharpName(cName.Substring(1)) : cName;
309                 classToBind.NoisyLog("(NativeBinder) Binding {0} as {2} of type {1}.", cName, expectedTypeName, csName);
310                 // let's find the desired method
311                 var desiredMethodInfo = classMethods.FirstOrDefault(x => x.Name == csName);
312                 if(desiredMethodInfo == null)
313                 {
314                     throw new InvalidOperationException(string.Format("Could not find method {0} in a class {1}.",
315                                                                       csName, classToBind.GetType().Name));
316                 }
317                 if(!desiredMethodInfo.IsDefined(typeof(ExportAttribute), true))
318                 {
319                     throw new InvalidOperationException(
320                         string.Format("Method {0} is exported as {1} but it is not marked with the Export attribute.",
321                                   desiredMethodInfo.Name, cName));
322                 }
323                 var parameterTypes = desiredMethodInfo.GetParameters().Select(p => p.ParameterType).ToList();
324                 var actualTypeName = ShortTypeNameFromParamsAndReturn(parameterTypes, desiredMethodInfo.ReturnType);
325                 if(expectedTypeName != actualTypeName)
326                 {
327                     throw new InvalidOperationException($"Method {cName} has type {actualTypeName} but the native library expects {expectedTypeName}");
328                 }
329                 exportedMethods.Add(desiredMethodInfo);
330                 // let's make the delegate instance
331                 var delegateType = DelegateTypeFromParamsAndReturn(parameterTypes, desiredMethodInfo.ReturnType);
332                 Delegate attachee;
333                 try
334                 {
335                     attachee = WrapExport(delegateType, desiredMethodInfo);
336                 }
337                 catch(ArgumentException e)
338                 {
339                     throw new InvalidOperationException($"Could not resolve call to managed: {e.Message}. Candidate is '{candidate}', desired method is '{desiredMethodInfo.ToString()}'");
340                 }
341 
342 #if !PLATFORM_WINDOWS && !NET
343                 // according to https://blogs.msdn.microsoft.com/cbrumme/2003/05/06/asynchronous-operations-pinning/,
344                 // pinning is wrong (and it does not work on windows too)...
345                 // but both on linux & osx it seems to be essential to avoid delegates from being relocated
346                 handles = handles.Union(new [] { GCHandle.Alloc(attachee, GCHandleType.Pinned) }).ToArray();
347 #endif
348                 delegateStore = delegateStore.Union(new [] { attachee }).ToArray();
349                 // let's make the attaching function delegate
350                 var attacherType = DelegateTypeFromParamsAndReturn(new [] { delegateType }, typeof(void), $"Attach{delegateType.Name}");
351                 var address = SharedLibraries.GetSymbolAddress(libraryAddress, originalCandidate);
352                 var attacher = Marshal.GetDelegateForFunctionPointer(address, attacherType);
353                 // and invoke it
354                 attacher.FastDynamicInvoke(attachee);
355             }
356             // check that all exported methods were really exported and issue a warning if not
357             var notExportedMethods = classMethods.Where(x => x.IsDefined(typeof(ExportAttribute), true)).Except(exportedMethods);
358             foreach(var method in notExportedMethods)
359             {
360                 classToBind.Log(LogLevel.Warning, "Method {0} is marked with Export attribute, but was not exported.", method.Name);
361             }
362         }
363 
ShortTypeNameFromParamsAndReturn(IEnumerable<Type> parameterTypes, Type returnType)364         private static string ShortTypeNameFromParamsAndReturn(IEnumerable<Type> parameterTypes, Type returnType)
365         {
366             var baseName = returnType == typeof(void) ? "Action" : "Func" + returnType.Name;
367             var paramNames = parameterTypes.Select(p => p.Name);
368             return string.Join("", paramNames.Prepend(baseName)).Replace("[]", "Array");
369         }
370 
GetCName(string name)371         private static string GetCName(string name)
372         {
373             var lastCapitalChar = 0;
374             var cName = name.GroupBy(x =>
375             {
376                 if (char.IsUpper(x))
377                 {
378                     lastCapitalChar++;
379                 }
380                 return lastCapitalChar;
381             }).Select(x => x.Aggregate(string.Empty, (y, z) => y + char.ToLower(z))).
382                 Aggregate((x, y) => x + "_" + y);
383 
384             return cName;
385         }
386 
GetWrappedName(string name, bool useExceptionWrapper)387         private static string GetWrappedName(string name, bool useExceptionWrapper)
388         {
389             if(useExceptionWrapper)
390             {
391                 return name + "_ex"; // Bind to exception wrapper instead of inner function
392             }
393 
394             return name;
395         }
396 
GetCSharpName(string name)397         private static string GetCSharpName(string name)
398         {
399             var words = name.Split('_');
400             return words.Select(x => FirstLetterUpper(x)).Aggregate((x, y) => x + y);
401         }
402 
FirstLetterUpper(string str)403         private static string FirstLetterUpper(string str)
404         {
405             return str.Substring(0, 1).ToUpper() + str.Substring(1);
406         }
407 
FilterCppName(string name)408         private static string FilterCppName(string name)
409         {
410             var result = Symbol.DemangleSymbol(name).Split('(')[0].ToString();
411             if(result.StartsWith("_"))
412             {
413                 return result.Skip(4).ToString();
414             }
415             return result;
416         }
417 
GetUnderlyingType(Type type)418         private static Type GetUnderlyingType(Type type)
419         {
420             return type.IsEnum ? type.GetEnumUnderlyingType() : type;
421         }
422 
423         // This method and the constants used in it are inspired by MakeNewCustomDelegate from .NET itself.
424         // See https://github.com/dotnet/runtime/blob/8ca896c3f5ef8eb1317439178bf041b5f270f351/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/DelegateHelpers.cs#L110
DelegateTypeFromParamsAndReturn(IEnumerable<Type> parameterTypes, Type returnType, string name = null)425         private static Type DelegateTypeFromParamsAndReturn(IEnumerable<Type> parameterTypes, Type returnType, string name = null)
426         {
427             // Convert enums to their underlying types to avoid creating needless additional delegate types
428             returnType = GetUnderlyingType(returnType);
429             parameterTypes = parameterTypes.Select(GetUnderlyingType);
430 
431             if(name == null)
432             {
433                 // The default naming mirrors the generic delegate types, but for functions, the return type comes first
434                 // For example, Func<Int32, UInt64> becomes FuncUInt64Int32.
435                 name = ShortTypeNameFromParamsAndReturn(parameterTypes, returnType);
436             }
437 
438             var delegateTypeName = $"NativeBinder.Delegates.{name}";
439 
440             lock(moduleBuilder)
441             {
442                 var delegateType = moduleBuilder.GetType(delegateTypeName);
443 
444                 if(delegateType == null)
445                 {
446                     var delegateTypeAttributes = TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed |
447                         TypeAttributes.AnsiClass | TypeAttributes.AutoClass;
448                     var delegateConstructorAttributes = MethodAttributes.RTSpecialName | MethodAttributes.HideBySig |
449                         MethodAttributes.Public;
450                     var delegateInvokeAttributes = MethodAttributes.Public | MethodAttributes.HideBySig |
451                         MethodAttributes.NewSlot | MethodAttributes.Virtual;
452                     var delegateMethodImplAttributes = MethodImplAttributes.Runtime | MethodImplAttributes.Managed;
453 
454                     var typeBuilder = moduleBuilder.DefineType(delegateTypeName, delegateTypeAttributes, typeof(MulticastDelegate));
455 
456                     var constructor = typeBuilder.DefineConstructor(delegateConstructorAttributes,
457                         CallingConventions.Standard, new [] { typeof(object), typeof(IntPtr) });
458                     constructor.SetImplementationFlags(delegateMethodImplAttributes);
459 
460                     var invokeMethod = typeBuilder.DefineMethod("Invoke", delegateInvokeAttributes, returnType, parameterTypes.ToArray());
461                     invokeMethod.SetImplementationFlags(delegateMethodImplAttributes);
462 
463                     // Add the [UnmanagedFunctionPointer] attribute with Cdecl specified
464                     var unmanagedFnAttrCtor = typeof(UnmanagedFunctionPointerAttribute).GetConstructor(new [] { typeof(CallingConvention) });
465                     var unmanagedFnAttrBuilder = new CustomAttributeBuilder(unmanagedFnAttrCtor, new object[] { CallingConvention.Cdecl });
466                     typeBuilder.SetCustomAttribute(unmanagedFnAttrBuilder);
467 
468                     delegateType = typeBuilder.CreateType();
469                 }
470 
471                 return delegateType;
472             }
473         }
474 
475         private static readonly ModuleBuilder moduleBuilder;
476 
477         private IntPtr libraryAddress;
478         private string libraryFileName;
479         private IEmulationElement classToBind;
480         private Type wrappersType;
481         private FieldInfo instanceField;
482         private FieldInfo exceptionKeeperField;
483         private object wrappersObj;
484 
485         // the point of delegate store is to hold references to delegates
486         // which would otherwise be garbage collected while native calls
487         // can still use them
488         private object[] delegateStore;
489 #if !PLATFORM_WINDOWS && !NET
490         private GCHandle[] handles;
491 #endif
492     }
493 }
494 
495