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