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