Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ldftn and DelegateShim #140

Merged
merged 5 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public CilVirtualMachine(ModuleDefinition contextModule, bool is32Bit)
ValueFactory = new ValueFactory(contextModule, is32Bit);
ObjectMapMemory = new ObjectMapMemory(this, 0x1000_0000);
ObjectMarshaller = new ObjectMarshaller(this);
FunctionMarshaller = new FunctionMarshaller(this);

if (is32Bit)
{
Expand Down Expand Up @@ -179,6 +180,15 @@ public IObjectMarshaller ObjectMarshaller
get;
set;
}

/// <summary>
///
/// </summary>
public IFunctionMarshaller FunctionMarshaller
{
get;
set;
}

/// <summary>
/// Gets a collection of threads that are currently active in the machine.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using AsmResolver.DotNet;
using AsmResolver.PE.DotNet.Cil;

namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.Pointers;
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Implements a CIL instruction handler for <c>ldftn</c> instruction.
/// </summary>
[DispatcherTableEntry(CilCode.Ldftn)]
public class LdftnHandler: FallThroughOpCodeHandler
{
/// <inheritdoc/>
protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction)
{
var stack = context.CurrentFrame.EvaluationStack;
var factory = context.Machine.ValueFactory;
var marshaller = context.Machine.FunctionMarshaller;
var type = context.Machine.ContextModule.CorLibTypeFactory.IntPtr;

var functionPointer = marshaller.GetFunctionPointer((IMethodDescriptor)instruction.Operand!);
stack.Push(factory.CreateNativeInteger((long)functionPointer), type);

return CilDispatchResult.Success();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AsmResolver.DotNet;

namespace Echo.Platforms.AsmResolver.Emulation;

/// <inheritdoc/>
public class FunctionMarshaller : IFunctionMarshaller
{
private readonly Dictionary<nuint, IMethodDescriptor> mappedPointers = new();
private nuint pointer = 13;
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Creates a new marshaller for the provided virtual machine.
/// </summary>
/// <param name="machine"></param>
public FunctionMarshaller(CilVirtualMachine machine)
{
Machine = machine;
}

/// <inheritdoc/>
public CilVirtualMachine Machine
{
get;
}

/// <inheritdoc/>
public nuint GetFunctionPointer(IMethodDescriptor method)
{
foreach (var pair in mappedPointers)
{
if (pair.Value == method)
return pair.Key;
}
mappedPointers.Add(pointer, method);
return pointer++;
}

/// <inheritdoc/>
public IMethodDescriptor? ResolveMethodPointer(nuint pointer)
{
if (mappedPointers.TryGetValue(pointer, out var result))
return result;
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using AsmResolver.DotNet;

namespace Echo.Platforms.AsmResolver.Emulation;
/// <summary>
/// Provides methods for marshalling functions into pointers
/// </summary>
public interface IFunctionMarshaller
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Gets the machine the marshaller is targeting.
/// </summary>
CilVirtualMachine Machine
{
get;
}

/// <summary>
/// Returns pointer for method descriptor
/// </summary>
nuint GetFunctionPointer(IMethodDescriptor method);
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Returns method descriptor for pointer
/// </summary>
IMethodDescriptor? ResolveMethodPointer(nuint pointer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public static class DefaultInvokers
/// </summary>
public static UnsafeInvoker UnsafeShim => UnsafeInvoker.Instance;

/// <summary>
/// Gets the default shim for the <see cref="System.Delegate"/> class.
/// </summary>
public static DelegateInvoker DelegateShim => DelegateInvoker.Instance;

/// <summary>
/// Gets the default shim for the <see cref="System.Runtime.CompilerServices.RuntimeHelpers"/> class.
/// </summary>
Expand Down Expand Up @@ -96,7 +101,8 @@ public static IMethodInvoker CreateDefaultShims() => StringShim
.WithFallback(UnsafeShim)
.WithFallback(RuntimeHelpersShim)
.WithFallback(IntrinsicsShim)
.WithFallback(MemoryMarshalShim);
.WithFallback(MemoryMarshalShim)
.WithFallback(DelegateShim);

/// <summary>
/// Chains the first method invoker with the provided method invoker in such a way that if the result of the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;
using Echo.Memory;
using Echo.Platforms.AsmResolver.Emulation.Dispatch;

namespace Echo.Platforms.AsmResolver.Emulation.Invocation;
/// <summary>
/// Wrapper for Delegates
/// </summary>
public class DelegateInvoker : IMethodInvoker
{
/// <summary>
/// Instance
/// </summary>
public static DelegateInvoker Instance { get; } = new();

/// <inheritdoc />
public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList<BitVector> arguments)
{
if (method is not { Name: { } name, DeclaringType: { } declaringType, Signature: { } signature })
return InvocationResult.Inconclusive();

if (declaringType.Resolve()?.BaseType?.IsTypeOf("System", "MulticastDelegate") == false)
return InvocationResult.Inconclusive();
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

if (method.Name == ".ctor")
{
return ConstructDelegate(context, arguments);
}

if (method.Name == "Invoke")
{
return InvokeDelegate(context, arguments);
}

return InvocationResult.Inconclusive();
}

private InvocationResult ConstructDelegate(CilExecutionContext context, IList<BitVector> arguments)
{
var vm = context.Machine;
var corlib = vm.ContextModule.CorLibTypeFactory;

var self = arguments[0].AsObjectHandle(vm);
var obj = arguments[1];
var methodPtr = arguments[2];

var _delegate = self.GetObjectType().Resolve()!.BaseType!.Resolve()!.BaseType;
IFieldDescriptor _target = new MemberReference(_delegate, "_target", new FieldSignature(corlib.Object));
IFieldDescriptor _methodPtr = new MemberReference(_delegate, "_methodPtr", new FieldSignature(corlib.IntPtr));
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

if (obj.IsFullyKnown && obj.AsObjectHandle(vm).IsNull)
self.WriteField(_target, obj);
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

self.WriteField(_methodPtr, methodPtr);

//var methodBase = vm.FunctionMarshaller.ResolveMethodPointer((nuint)methodPtr.AsSpan().ReadNativeInteger(vm.Is32Bit));
//if (methodBase != null)
// self.WriteField(_methodBase, marshall IMethodDescriptor to RuntimeMethodInfo);
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

return InvocationResult.StepOver(null);
}

private InvocationResult InvokeDelegate(CilExecutionContext context, IList<BitVector> arguments)
{
var vm = context.Machine;
var stack = context.CurrentFrame.EvaluationStack;
var corlib = vm.ContextModule.CorLibTypeFactory;

var self = arguments[0].AsObjectHandle(vm);

var _delegate = self.GetObjectType().Resolve()!.BaseType!.Resolve()!.BaseType;
IFieldDescriptor _target = new MemberReference(_delegate, "_target", new FieldSignature(corlib.Object));
IFieldDescriptor _methodPtr = new MemberReference(_delegate, "_methodPtr", new FieldSignature(corlib.IntPtr));

var methodPtr = self.ReadField(_methodPtr).AsSpan().ReadNativeInteger(vm.Is32Bit);
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved
var method = vm.FunctionMarshaller.ResolveMethodPointer((nuint)methodPtr);

if (method == null)
throw new Exception("Cant resolve method");
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

var arg1 = self.ReadField(_target);
if (arg1.AsSpan().ReadNativeInteger(vm.Is32Bit) != 0)
stack.Push(arg1.AsObjectHandle(vm));
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved

// skip 1 for delegate "this"
for (var i = 1; i < arguments.Count; i++)
stack.Push(arguments[i], method.Signature!.ParameterTypes[i]);

var result = vm.Dispatcher.Dispatch(context, new(CilOpCodes.Call, method));

if (!result.IsSuccess)
return InvocationResult.Exception(result.ExceptionObject);

return InvocationResult.StepOver(null);
BadRyuner marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -662,5 +662,36 @@ public void StepPrefixedInstructionsShouldStepOverAllInstructions()
_mainThread.Step();
Assert.Equal(instructions[3].Offset, frame.ProgramCounter);
}

[Fact]
public void CallDelegate()
Washi1337 marked this conversation as resolved.
Show resolved Hide resolved
{
var method = _fixture.MockModule
.LookupMember<TypeDefinition>(typeof(TestClass).MetadataToken)
.Methods.First(m => m.Name == nameof(TestClass.TestDelegateCall));

_vm.Invoker = DefaultInvokers.DelegateShim.WithFallback(DefaultInvokers.StepIn);

_mainThread.CallStack.Push(method);
bool entered = false;
bool retIsFive = false;
while ((entered & retIsFive) != true)
{
_mainThread.Step();
if (!entered && _mainThread.CallStack.Count == 3
&& _mainThread.CallStack.Peek().Method.Name == "ReturnAnyInt")
entered = true;
else if (entered && !retIsFive && _mainThread.CallStack.Count == 2)
{
var stack = _mainThread.CallStack.Peek().EvaluationStack;
if (stack.Count == 1 && stack.Peek().Contents.AsSpan().U32 == 5)
{
retIsFive = true;
}
}
}
Assert.True(entered);
Assert.True(retIsFive);
}
}
}
12 changes: 11 additions & 1 deletion test/Platforms/Mocks/TestClass.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Net;
using InlineIL;
Expand Down Expand Up @@ -285,5 +285,15 @@ public static int CatchExceptionInChildMethod(bool @throw)

return result;
}

public delegate int ReturnAnyIntDelegate();

private static int ReturnAnyInt() => 5;

public static int TestDelegateCall()
{
ReturnAnyIntDelegate del = ReturnAnyInt;
return del();
}
}
}