From d24c5a7fb2cb68888102132ca4f56c52e6d7a54f Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 1 Apr 2024 14:23:50 +0200 Subject: [PATCH] BUGFIX: NewObj on structure should return the entire structure as opposed to a pointer. --- src/Core/Echo/Memory/BitVectorSpan.cs | 13 +++ .../Dispatch/ControlFlow/RetHandler.cs | 66 ++++++++++- .../Dispatch/ObjectModel/NewObjHandler.cs | 108 ++++++++++++------ 3 files changed, 148 insertions(+), 39 deletions(-) diff --git a/src/Core/Echo/Memory/BitVectorSpan.cs b/src/Core/Echo/Memory/BitVectorSpan.cs index dab97aef..27349d06 100644 --- a/src/Core/Echo/Memory/BitVectorSpan.cs +++ b/src/Core/Echo/Memory/BitVectorSpan.cs @@ -362,7 +362,20 @@ public string ToHexString() /// /// Copies the span into a new bit vector. /// + /// The vector. public BitVector ToVector() => new(this); + + /// + /// Copies the span into a new bit vector that is rented from the provided pool. + /// + /// The pool to rent the vector from. + /// The vector. + public BitVector ToVector(BitVectorPool pool) + { + var result = pool.Rent(Count, false); + CopyTo(result); + return result; + } private void AssertSameBitSize(BitVectorSpan other) { diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs index 68d53019..c0ad0fc7 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs @@ -1,6 +1,8 @@ using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Cil; +using Echo.Memory; +using Echo.Platforms.AsmResolver.Emulation.Stack; namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ControlFlow { @@ -13,17 +15,69 @@ public class RetHandler : ICilOpCodeHandler /// public CilDispatchResult Dispatch(CilExecutionContext context, CilInstruction instruction) { - var frame = context.Thread.CallStack.Pop(); - var genericContext = GenericContext.FromMethod(frame.Method); - if (frame.Method.Signature!.ReturnsValue) + var calleeFrame = context.Thread.CallStack.Pop(); + var callerFrame = context.CurrentFrame; + + var genericContext = GenericContext.FromMethod(calleeFrame.Method); + if (calleeFrame.Method.Signature!.ReturnsValue) + { + // The method returns the value on top of the stack. Push it in the caller frame. + var returnType = calleeFrame.Method.Signature.ReturnType.InstantiateGenericTypes(genericContext); + var value = calleeFrame.EvaluationStack.Pop(returnType); + callerFrame.EvaluationStack.Push(value, returnType, true); + } + else if (callerFrame.Body is { } body) { - var returnType = frame.Method.Signature.ReturnType.InstantiateGenericTypes(genericContext); + // The method may still be a constructor called via newobj. + // In that case we need to push the created value, stored in the `this` pointer. + + int index = body.Instructions.GetIndexByOffset(callerFrame.ProgramCounter) - 1; + if (index != -1 && body.Instructions[index].OpCode.Code == CilCode.Newobj) + { + var resultingType = calleeFrame.Method.DeclaringType! + .ToTypeSignature() + .InstantiateGenericTypes(genericContext); - var value = frame.EvaluationStack.Pop(returnType); - context.CurrentFrame.EvaluationStack.Push(value, returnType, true); + var slot = CreateResultingStackSlot( + context, + resultingType, + calleeFrame.ReadArgument(0) + ); + + callerFrame.EvaluationStack.Push(slot); + } } return CilDispatchResult.Success(); } + + internal static StackSlot CreateResultingStackSlot( + CilExecutionContext context, + TypeSignature type, + BitVectorSpan thisPointer) + { + // For reference types, we can just wrap the address into a stack slot. + if (!type.IsValueType) + { + return new StackSlot( + thisPointer.ToVector(context.Machine.ValueFactory.BitVectorPool), + StackSlotTypeHint.Integer + ); + } + + // For value types, we need to push the resulting structure in its entirety. + var contents = context.Machine.ValueFactory.CreateValue(type, false); + + // Read the entire structure behind the this-pointer. + if (thisPointer.IsFullyKnown) + { + context.Machine.Memory.Read( + thisPointer.ReadNativeInteger(context.Machine.Is32Bit), + contents + ); + } + + return new StackSlot(contents, StackSlotTypeHint.Structure); + } } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs index 21229296..ac31b489 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Cil; using Echo.Memory; +using Echo.Platforms.AsmResolver.Emulation.Dispatch.ControlFlow; using Echo.Platforms.AsmResolver.Emulation.Invocation; namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel @@ -16,44 +18,15 @@ public class NewObjHandler : CallHandlerBase /// public override CilDispatchResult Dispatch(CilExecutionContext context, CilInstruction instruction) { - var stack = context.CurrentFrame.EvaluationStack; - - // Allocate the new object. - var constructor = (IMethodDescriptor) instruction.Operand!; + var constructor = (IMethodDescriptor)instruction.Operand!; var instanceType = constructor.DeclaringType!.ToTypeSignature(); var arguments = GetArguments(context, constructor); try { - var allocation = context.Machine.Allocator.Allocate(context, constructor, arguments); - switch (allocation.ResultType) - { - case AllocationResultType.Inconclusive: - throw new CilEmulatorException($"Allocation of object of type {instanceType} was inconclusive"); - - case AllocationResultType.Allocated: - // Insert the allocated "this" pointer into the arguments and call constructor. - arguments.Insert(0, allocation.Address!); - var result = HandleCall(context, instruction, constructor, arguments); - - // If successful, push the resulting object onto the stack. - if (result.IsSuccess) - stack.Push(allocation.Address!, instanceType); - - return result; - - case AllocationResultType.FullyConstructed: - // Fully constructed objects do not have to be post-processed. - stack.Push(allocation.Address!, instanceType); - context.CurrentFrame.ProgramCounter += instruction.Size; - return CilDispatchResult.Success(); - - case AllocationResultType.Exception: - return CilDispatchResult.Exception(allocation.ExceptionObject); - - default: - throw new ArgumentOutOfRangeException(); - } + return instanceType.IsValueType + ? HandleValueTypeNewObj(context, instruction, constructor, instanceType, arguments) + : HandleReferenceTypeNewObj(context, instruction, constructor, instanceType, arguments); } finally { @@ -62,6 +35,75 @@ public override CilDispatchResult Dispatch(CilExecutionContext context, CilInstr } } + private CilDispatchResult HandleValueTypeNewObj( + CilExecutionContext context, + CilInstruction instruction, + IMethodDescriptor constructor, + TypeSignature instanceType, + IList arguments) + { + var factory = context.Machine.ValueFactory; + var callerFrame = context.CurrentFrame; + var callerStack = callerFrame.EvaluationStack; + + // Stack allocate the structure. + long address = context.CurrentFrame.Allocate((int)factory.GetTypeContentsMemoryLayout(instanceType).Size); + var thisPointer = factory.CreateNativeInteger(address); + + // Call the constructor with the constructor. + arguments.Insert(0, thisPointer); + var result = HandleCall(context, instruction, constructor, arguments); + + // If we stepped over the call, we need to push the stack value ourselves. + if (result.IsSuccess && callerFrame == context.CurrentFrame) + callerStack.Push(RetHandler.CreateResultingStackSlot(context, instanceType, thisPointer)); + + return result; + } + + private CilDispatchResult HandleReferenceTypeNewObj( + CilExecutionContext context, + CilInstruction instruction, + IMethodDescriptor constructor, + TypeSignature instanceType, + IList arguments) + { + var callerFrame = context.CurrentFrame; + var callerStack = callerFrame.EvaluationStack; + + var allocation = context.Machine.Allocator.Allocate(context, constructor, arguments); + switch (allocation.ResultType) + { + case AllocationResultType.Inconclusive: + throw new CilEmulatorException( + $"Allocation of object of type {instanceType} was inconclusive"); + + case AllocationResultType.Allocated: + // Insert the allocated "this" pointer into the arguments and call constructor. + arguments.Insert(0, allocation.Address!); + var result = HandleCall(context, instruction, constructor, arguments); + + // If we stepped over the call, we need to push the stack value ourselves. + if (result.IsSuccess && callerFrame == context.CurrentFrame) + callerStack.Push(allocation.Address!, instanceType); + + return result; + + case AllocationResultType.FullyConstructed: + // Fully constructed objects do not have to be post-processed. + callerStack.Push(allocation.Address!, instanceType); + callerFrame.ProgramCounter += instruction.Size; + return CilDispatchResult.Success(); + + case AllocationResultType.Exception: + return CilDispatchResult.Exception(allocation.ExceptionObject); + + default: + throw new ArgumentOutOfRangeException(); + } + + } + /// protected override bool ShouldPopInstanceObject(IMethodDescriptor method) => false;