From cb6db0e68cf706521091a2f13de784a4fd5253d1 Mon Sep 17 00:00:00 2001 From: BadRyuner <54708336+BadRyuner@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:22:11 +0300 Subject: [PATCH 1/2] Add ldvirtftn --- .../Dispatch/ObjectModel/LdvirtftnHandler.cs | 56 +++++++++++++++++++ .../Emulation/CilVirtualMachineTest.cs | 37 ++++++++++++ test/Platforms/Mocks/TestClass.cs | 16 ++++++ 3 files changed, 109 insertions(+) create mode 100644 src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/LdvirtftnHandler.cs diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/LdvirtftnHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/LdvirtftnHandler.cs new file mode 100644 index 00000000..cc4e86b6 --- /dev/null +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/LdvirtftnHandler.cs @@ -0,0 +1,56 @@ +using System.Linq; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Cil; + +namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel; + +/// +/// Implements a CIL instruction handler for ldvirtftn instruction. +/// +[DispatcherTableEntry(CilCode.Ldvirtftn)] +public class LdvirtftnHandler : FallThroughOpCodeHandler +{ + /// + protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction) + { + var stack = context.CurrentFrame.EvaluationStack; + var factory = context.Machine.ValueFactory; + var methods = context.Machine.ValueFactory.ClrMockMemory.MethodEntryPoints; + var type = context.Machine.ContextModule.CorLibTypeFactory.IntPtr; + + var thisObject = stack.Pop().Contents; + if (!thisObject.IsFullyKnown) + throw new CilEmulatorException("Unable to resolve an unknown object."); + + var thisObjectType = thisObject.AsObjectHandle(context.Machine).GetObjectType().Resolve(); + if (thisObjectType == null) + throw new CilEmulatorException("Unable to resolve the type of object"); + + var virtualFunction = ((IMethodDescriptor)instruction.Operand!); + var virtualFunctionName = virtualFunction.Name; + var virtualFunctionSignature = virtualFunction.Signature; + + do + { + // try resolve function + var resolvedVirtualFunction = thisObjectType.Methods + .FirstOrDefault(method => method.Name == virtualFunctionName + && SignatureComparer.Default.Equals(method.Signature, virtualFunctionSignature)); + + // if resolved then push function pointer + if (resolvedVirtualFunction != null) + { + var functionPointer = methods.GetAddress(resolvedVirtualFunction); + stack.Push(factory.CreateNativeInteger(functionPointer), type); + return CilDispatchResult.Success(); + } + + // else switch to BaseType and try resolve again + thisObjectType = thisObjectType.BaseType?.Resolve(); + } // or exit and throw CilEmulationException + while (thisObjectType != null); + + throw new CilEmulatorException($"Unable to resolve a virtual function for type {thisObjectType!.FullName}"); + } +} diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs index 450986c8..2acaff90 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Text; using System.Threading; +using AsmResolver; using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; @@ -710,5 +711,41 @@ public void CallDelegate() Assert.Single(_mainThread.CallStack.Peek().EvaluationStack); Assert.Equal(5, _mainThread.CallStack.Peek().EvaluationStack.Peek().Contents.AsSpan().I32); } + + [Fact] + public void CallDelegateWithVirtualFunction() + { + var method = _fixture.MockModule + .LookupMember(typeof(TestClass).MetadataToken) + .Methods.First(m => m.Name == nameof(TestClass.TestVirtualDelegateCall)); + + _vm.Invoker = DefaultInvokers.CreateDefaultShims().WithFallback(DefaultInvokers.StepIn).WithFallback(DefaultInvokers.ReflectionInvoke); + _mainThread.CallStack.Push(method); + + var instructions = method.CilMethodBody!.Instructions; + + var invokeDelegateOffset = instructions + .First(instruction => instruction.Operand is IMethodDescriptor descriptor + && descriptor.Name == "Invoke") + .Offset; + + var expectedResults = new string[] { "Mr.String", "Mocks.TestClass" }; + + for (int i = 0; i < expectedResults.Length; i++) + { + _mainThread.StepWhile(CancellationToken.None, context => context.CurrentFrame.ProgramCounter != invokeDelegateOffset); + _mainThread.Step(); // invoke: string Func::Invoke() + // Expected stack: + // (0) root -> (1) TestVirtualDelegateCall -> (2) Func::Invoke -> (3) *::ToString + Assert.Equal(4, _mainThread.CallStack.Count); + + // exit from *::ToString + while (_mainThread.CallStack.Count != 2) + _mainThread.StepOut(); + + var returnedString = _mainThread.CallStack.Peek().EvaluationStack.Peek(); + Assert.Equal(expectedResults[i], _vm.ObjectMarshaller.ToObject(returnedString.Contents.AsSpan())); + } + } } } \ No newline at end of file diff --git a/test/Platforms/Mocks/TestClass.cs b/test/Platforms/Mocks/TestClass.cs index 150beaec..54736d7b 100644 --- a/test/Platforms/Mocks/TestClass.cs +++ b/test/Platforms/Mocks/TestClass.cs @@ -295,5 +295,21 @@ public static int TestDelegateCall() ReturnAnyIntDelegate del = ReturnAnyInt; return del(); } + + public static void TestVirtualDelegateCall() + { + var objects = new object[] { "Mr.String", new TestClass() }; + for(int i = 0; i < objects.Length; i++) + { + var @object = objects[i]; + var function = new Func(@object.ToString); + _ = function(); + } + } + + public override string ToString() + { + return "Mocks.TestClass"; + } } } \ No newline at end of file From 4214f1a63b7bc558faee499b21cbff3202cd4093 Mon Sep 17 00:00:00 2001 From: BadRyuner <54708336+BadRyuner@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:52:14 +0300 Subject: [PATCH 2/2] Redo method devirtualization for ldvirtftn --- .../Dispatch/ObjectModel/CallHandlerBase.cs | 2 +- .../Dispatch/ObjectModel/LdvirtftnHandler.cs | 54 ++++++++++--------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs index 1ee0f256..f9c7e8b6 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs @@ -257,7 +257,7 @@ private static CilDispatchResult Invoke(CilExecutionContext context, IMethodDesc } } - protected static MethodDefinition? FindMethodImplementationInType(TypeDefinition? type, MethodDefinition? baseMethod) + internal static MethodDefinition? FindMethodImplementationInType(TypeDefinition? type, MethodDefinition? baseMethod) { if (type is null || baseMethod is null || !baseMethod.IsVirtual) return baseMethod; diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/LdvirtftnHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/LdvirtftnHandler.cs index cc4e86b6..1a96f6c0 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/LdvirtftnHandler.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/LdvirtftnHandler.cs @@ -27,30 +27,34 @@ protected override CilDispatchResult DispatchInternal(CilExecutionContext contex if (thisObjectType == null) throw new CilEmulatorException("Unable to resolve the type of object"); - var virtualFunction = ((IMethodDescriptor)instruction.Operand!); - var virtualFunctionName = virtualFunction.Name; - var virtualFunctionSignature = virtualFunction.Signature; - - do - { - // try resolve function - var resolvedVirtualFunction = thisObjectType.Methods - .FirstOrDefault(method => method.Name == virtualFunctionName - && SignatureComparer.Default.Equals(method.Signature, virtualFunctionSignature)); - - // if resolved then push function pointer - if (resolvedVirtualFunction != null) - { - var functionPointer = methods.GetAddress(resolvedVirtualFunction); - stack.Push(factory.CreateNativeInteger(functionPointer), type); - return CilDispatchResult.Success(); - } - - // else switch to BaseType and try resolve again - thisObjectType = thisObjectType.BaseType?.Resolve(); - } // or exit and throw CilEmulationException - while (thisObjectType != null); - - throw new CilEmulatorException($"Unable to resolve a virtual function for type {thisObjectType!.FullName}"); + var virtualFunction = (IMethodDescriptor)instruction.Operand!; + + IMethodDescriptor? implementation = CallHandlerBase.FindMethodImplementationInType(thisObjectType, virtualFunction.Resolve()); + + if (implementation == null) + { + return CilDispatchResult.Exception( + context.Machine.Heap.AllocateObject( + context.Machine.ValueFactory.MissingMethodExceptionType, + true) + .AsObjectHandle(context.Machine) + ); + } + + // Instantiate any generics. + var genericContext = GenericContext.FromMethod(virtualFunction); + if (!genericContext.IsEmpty) + { + var instantiated = thisObjectType + .CreateMemberReference(implementation.Name!, implementation.Signature!); + + implementation = genericContext.Method != null + ? instantiated.MakeGenericInstanceMethod(genericContext.Method.TypeArguments.ToArray()) + : instantiated; + } + + var functionPointer = methods.GetAddress(implementation); + stack.Push(factory.CreateNativeInteger(functionPointer), type); + return CilDispatchResult.Success(); } }