diff --git a/src/Core/Echo/Memory/VirtualMemory.cs b/src/Core/Echo/Memory/VirtualMemory.cs index cdd907c0..6fc52f23 100644 --- a/src/Core/Echo/Memory/VirtualMemory.cs +++ b/src/Core/Echo/Memory/VirtualMemory.cs @@ -49,12 +49,18 @@ public AddressRange AddressRange /// Occurs when the address was already in use. public void Map(long address, IMemorySpace space) { + if (space.AddressRange.Length == 0) + throw new ArgumentException("Cannot map an empty memory space."); + if (!AddressRange.Contains(address)) - throw new ArgumentException($"Address {address:X8} does not fall within the virtual memory."); + throw new ArgumentException($"Address 0x{address:X8} does not fall within the virtual memory."); + + if (!AddressRange.Contains(address + space.AddressRange.Length - 1)) + throw new ArgumentException($"Insufficient space available at address 0x{address:X8} to map {space.AddressRange.Length} bytes within the virtual memory."); int index = GetMemorySpaceIndex(address); if (index != -1) - throw new ArgumentException($"Address {address:X8} is already in use."); + throw new ArgumentException($"Address 0x{address:X8} is already in use."); // Insertion sort to ensure _spaces remains sorted. int i = 0; diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilThread.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilThread.cs new file mode 100644 index 00000000..bf398c42 --- /dev/null +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilThread.cs @@ -0,0 +1,292 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Cil; +using Echo.Memory; +using Echo.Platforms.AsmResolver.Emulation.Dispatch; +using Echo.Platforms.AsmResolver.Emulation.Stack; + +namespace Echo.Platforms.AsmResolver.Emulation +{ + /// + /// Represents a single execution thread in a virtualized .NET process. + /// + public class CilThread + { + private CilExecutionContext? _singleStepContext; + + internal CilThread(CilVirtualMachine machine, CallStack callStack) + { + Machine = machine; + CallStack = callStack; + IsAlive = true; + } + + /// + /// Gets the parent machine the thread is running in. + /// + public CilVirtualMachine Machine + { + get; + } + + /// + /// Gets the current state of the call stack. + /// + /// + /// The call stack is also addressable from . + /// + public CallStack CallStack + { + get; + } + + /// + /// Gets a value indicating whether the thread is alive and present in the parent machine. + /// + public bool IsAlive + { + get; + internal set; + } + + /// + /// Runs the virtual machine until it halts. + /// + public void Run() => Run(CancellationToken.None); + + /// + /// Runs the virtual machine until it halts. + /// + /// A token that can be used for canceling the emulation. + public void Run(CancellationToken cancellationToken) + { + StepWhile(cancellationToken, context => !context.CurrentFrame.IsRoot); + } + + /// + /// Calls the provided method in the context of the virtual machine. + /// + /// The method to call. + /// The arguments. + /// The return value, or null if the provided method does not return a value. + /// + /// This method is blocking until the emulation of the call completes. + /// + public BitVector? Call(IMethodDescriptor method, object[] arguments) + { + // Short circuit before we do expensive marshalling... + if (arguments.Length != method.Signature!.GetTotalParameterCount()) + throw new TargetParameterCountException(); + + var marshalled = arguments.Select(x => Machine.ObjectMarshaller.ToBitVector(x)).ToArray(); + return Call(method, CancellationToken.None, marshalled); + } + + /// + /// Calls the provided method in the context of the virtual machine. + /// + /// The method to call. + /// The arguments. + /// The return value, or null if the provided method does not return a value. + /// + /// This method is blocking until the emulation of the call completes. + /// + public BitVector? Call(IMethodDescriptor method, BitVector[] arguments) + { + return Call(method, CancellationToken.None, arguments); + } + + /// + /// Calls the provided method in the context of the virtual machine. + /// + /// The method to call. + /// A token that can be used for canceling the emulation. + /// The arguments. + /// The return value, or null if the provided method does not return a value. + /// + /// This method is blocking until the emulation of the call completes or the emulation is canceled. + /// + public BitVector? Call(IMethodDescriptor method, CancellationToken cancellationToken, BitVector[] arguments) + { + if (arguments.Length != method.Signature!.GetTotalParameterCount()) + throw new TargetParameterCountException(); + + var pool = Machine.ValueFactory.BitVectorPool; + + // Instantiate any generic types if available. + var context = GenericContext.FromMethod(method); + var signature = method.Signature.InstantiateGenericTypes(context); + + // Set up callee frame. + var frame = CallStack.Push(method); + for (int i = 0; i < arguments.Length; i++) + { + var slot = Machine.ValueFactory.Marshaller.ToCliValue(arguments[i], signature.ParameterTypes[i]); + frame.WriteArgument(i, slot.Contents); + pool.Return(slot.Contents); + } + + // Run until we return. + StepOut(cancellationToken); + + // If void, then we don't have anything else to do. + if (!signature.ReturnsValue) + return null; + + // If we produced a return value, return a copy of it to the caller. + // As the return value may be a rented bit vector, we should copy it to avoid unwanted side-effects. + var callResult = CallStack.Peek().EvaluationStack.Pop(signature.ReturnType); + var result = callResult.Clone(); + pool.Return(callResult); + + return result; + } + + /// + /// Continues execution of the virtual machine while the provided predicate returns true. + /// + /// A token that can be used for canceling the emulation. + /// + /// A predicate that is evaluated on every step of the emulation, determining whether execution should continue. + /// + public void StepWhile(CancellationToken cancellationToken, Predicate condition) + { + var context = new CilExecutionContext(this, cancellationToken); + + do + { + Step(context); + cancellationToken.ThrowIfCancellationRequested(); + } while (condition(context)); + } + + /// + /// Performs a single step in the virtual machine. If the current instruction performs a call, the emulation + /// is treated as a single instruction. + /// + public void StepOver() => StepOver(CancellationToken.None); + + /// + /// Performs a single step in the virtual machine. If the current instruction performs a call, the emulation + /// is treated as a single instruction. + /// + /// A token that can be used for canceling the emulation. + public void StepOver(CancellationToken cancellationToken) + { + int stackDepth = CallStack.Count; + StepWhile(cancellationToken, context => context.Thread.CallStack.Count > stackDepth); + } + + /// + /// Continues execution of the virtual machine until the current call frame is popped from the stack. + /// + public void StepOut() => StepOut(CancellationToken.None); + + /// + /// Continues execution of the virtual machine until the current call frame is popped from the stack. + /// + /// A token that can be used for canceling the emulation. + public void StepOut(CancellationToken cancellationToken) + { + int stackDepth = CallStack.Count; + StepWhile(cancellationToken, context => context.Thread.CallStack.Count >= stackDepth); + } + + /// + /// Performs a single step in the virtual machine. + /// + public void Step() + { + _singleStepContext ??= new CilExecutionContext(this, CancellationToken.None); + Step(_singleStepContext); + } + + /// + /// Performs a single step in the virtual machine. + /// + /// A token that can be used for canceling the emulation. + public void Step(CancellationToken cancellationToken) => Step(new CilExecutionContext(this, cancellationToken)); + + private void Step(CilExecutionContext context) + { + if (!IsAlive) + throw new CilEmulatorException("The thread is not alive."); + + if (CallStack.Peek().IsRoot) + throw new CilEmulatorException("No method is currently being executed."); + + var currentFrame = CallStack.Peek(); + if (currentFrame.Body is not { } body) + throw new CilEmulatorException("Emulator only supports managed method bodies."); + + // Determine the next instruction to dispatch. + int pc = currentFrame.ProgramCounter; + var instruction = body.Instructions.GetByOffset(pc); + if (instruction is null) + throw new CilEmulatorException($"Invalid program counter in {currentFrame}."); + + // Are we entering any protected regions? + UpdateExceptionHandlerStack(); + + // Dispatch the instruction. + var result = Machine.Dispatcher.Dispatch(context, instruction); + + if (!result.IsSuccess) + { + var exceptionObject = result.ExceptionObject; + if (exceptionObject.IsNull) + throw new CilEmulatorException("A null exception object was thrown."); + + // If there were any errors thrown after dispatching, it may trigger the execution of one of the + // exception handlers in the entire call stack. + if (!UnwindCallStack(exceptionObject)) + throw new EmulatedException(exceptionObject); + } + } + + private void UpdateExceptionHandlerStack() + { + var currentFrame = CallStack.Peek(); + + int pc = currentFrame.ProgramCounter; + var availableHandlers = currentFrame.ExceptionHandlers; + var activeHandlers = currentFrame.ExceptionHandlerStack; + + for (int i = 0; i < availableHandlers.Count; i++) + { + var handler = availableHandlers[i]; + if (handler.ProtectedRange.Contains(pc) && handler.Enter() && !activeHandlers.Contains(handler)) + activeHandlers.Push(handler); + } + } + + private bool UnwindCallStack(ObjectHandle exceptionObject) + { + while (!CallStack.Peek().IsRoot) + { + var currentFrame = CallStack.Peek(); + + var result = currentFrame.ExceptionHandlerStack.RegisterException(exceptionObject); + if (result.IsSuccess) + { + // We found a handler that needs to be called. Jump to it. + currentFrame.ProgramCounter = result.NextOffset; + + // Push the exception on the stack. + currentFrame.EvaluationStack.Clear(); + currentFrame.EvaluationStack.Push(exceptionObject); + + return true; + } + + CallStack.Pop(); + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilVirtualMachine.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilVirtualMachine.cs index 268d5726..c958d7b2 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilVirtualMachine.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilVirtualMachine.cs @@ -1,10 +1,8 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; -using System.Reflection; -using System.Threading; using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Cil; using Echo.Memory; using Echo.Platforms.AsmResolver.Emulation.Dispatch; using Echo.Platforms.AsmResolver.Emulation.Heap; @@ -19,7 +17,18 @@ namespace Echo.Platforms.AsmResolver.Emulation /// public class CilVirtualMachine { - private CilExecutionContext? _singleStepContext; + /// + /// Fires when a new thread was created. + /// + public event EventHandler? ThreadCreated; + + /// + /// Fires when a thread was destroyed. + /// + public event EventHandler? ThreadDestroyed; + + private readonly List _threads = new(); + private readonly CallStackMemory _callStackMemory; /// /// Creates a new CIL virtual machine. @@ -34,25 +43,26 @@ public CilVirtualMachine(ModuleDefinition contextModule, bool is32Bit) ValueFactory = new ValueFactory(contextModule, is32Bit); ObjectMapMemory = new ObjectMapMemory(this, 0x1000_0000); ObjectMarshaller = new ObjectMarshaller(this); - + if (is32Bit) { Memory.Map(0x1000_0000, Heap = new ManagedObjectHeap(0x0100_0000, ValueFactory)); Memory.Map(0x6000_0000, ObjectMapMemory); Memory.Map(0x7000_0000, StaticFields = new StaticFieldStorage(ValueFactory, 0x0100_0000)); Memory.Map(0x7100_0000, ValueFactory.ClrMockMemory); - Memory.Map(0x7fe0_0000, CallStack = new CallStack(0x10_0000, ValueFactory)); + Memory.Map(0x7f00_0000, _callStackMemory = new CallStackMemory(0x100_0000, ValueFactory)); } else { Memory.Map(0x0000_0100_0000_0000, Heap = new ManagedObjectHeap(0x01000_0000, ValueFactory)); - Memory.Map(0x0000_7ffe_0000_0000, ObjectMapMemory); - Memory.Map(0x0000_7fff_0000_0000, StaticFields = new StaticFieldStorage(ValueFactory, 0x1000_0000)); - Memory.Map(0x0000_7fff_1000_0000, ValueFactory.ClrMockMemory); - Memory.Map(0x0000_7fff_8000_0000, CallStack = new CallStack(0x100_0000, ValueFactory)); + Memory.Map(0x0000_7ffd_0000_0000, ObjectMapMemory); + Memory.Map(0x0000_7ffe_0000_0000, StaticFields = new StaticFieldStorage(ValueFactory, 0x1000_0000)); + Memory.Map(0x0000_7ffe_1000_0000, ValueFactory.ClrMockMemory); + Memory.Map(0x0000_7ffe_8000_0000, _callStackMemory = new CallStackMemory(0x1000_0000, ValueFactory)); } Dispatcher = new CilDispatcher(); + Threads = new ReadOnlyCollection(_threads); } /// @@ -87,17 +97,6 @@ public StaticFieldStorage StaticFields get; } - /// - /// Gets the current state of the call stack. - /// - /// - /// The call stack is also addressable from . - /// - public CallStack CallStack - { - get; - } - /// /// Gets the memory manager that embeds managed objects into virtual memory. /// @@ -161,239 +160,50 @@ public IObjectMarshaller ObjectMarshaller get; set; } - - /// - /// Runs the virtual machine until it halts. - /// - public void Run() => Run(CancellationToken.None); - - /// - /// Runs the virtual machine until it halts. - /// - /// A token that can be used for canceling the emulation. - public void Run(CancellationToken cancellationToken) - { - StepWhile(cancellationToken, context => !context.CurrentFrame.IsRoot); - } /// - /// Calls the provided method in the context of the virtual machine. + /// Gets a collection of threads that are currently active in the machine. /// - /// The method to call. - /// The arguments. - /// The return value, or null if the provided method does not return a value. - /// - /// This method is blocking until the emulation of the call completes. - /// - public BitVector? Call(IMethodDescriptor method, object[] arguments) + public IReadOnlyList Threads { - // Short circuit before we do expensive marshalling... - if (arguments.Length != method.Signature!.GetTotalParameterCount()) - throw new TargetParameterCountException(); - - var marshalled = arguments.Select(x => ObjectMarshaller.ToBitVector(x)).ToArray(); - return Call(method, CancellationToken.None, marshalled); + get; } - + /// - /// Calls the provided method in the context of the virtual machine. + /// Creates a new thread in the machine. /// - /// The method to call. - /// The arguments. - /// The return value, or null if the provided method does not return a value. - /// - /// This method is blocking until the emulation of the call completes. - /// - public BitVector? Call(IMethodDescriptor method, BitVector[] arguments) + /// The amount of memory to allocate for the thread's stack. + /// The created thread. + public CilThread CreateThread(uint stackSize = 0x0010_0000) { - return Call(method, CancellationToken.None, arguments); + var stack = _callStackMemory.Allocate(stackSize); + var thread = new CilThread(this, stack); + _threads.Add(thread); + ThreadCreated?.Invoke(this, thread); + return thread; } /// - /// Calls the provided method in the context of the virtual machine. + /// Removes a thread and its stack from the machine. /// - /// The method to call. - /// A token that can be used for canceling the emulation. - /// The arguments. - /// The return value, or null if the provided method does not return a value. + /// The thread to remove. /// - /// This method is blocking until the emulation of the call completes or the emulation is canceled. + /// This does not gracefully terminate a thread. Any code that is still running will remain executing, and may + /// have unwanted side-effects. Therefore, be sure to only call this method only when it is certain that no code + /// is running. /// - public BitVector? Call(IMethodDescriptor method, CancellationToken cancellationToken, BitVector[] arguments) - { - if (arguments.Length != method.Signature!.GetTotalParameterCount()) - throw new TargetParameterCountException(); - - var pool = ValueFactory.BitVectorPool; - - // Instantiate any generic types if available. - var context = GenericContext.FromMethod(method); - var signature = method.Signature.InstantiateGenericTypes(context); - - // Set up callee frame. - var frame = CallStack.Push(method); - for (int i = 0; i < arguments.Length; i++) - { - var slot = ValueFactory.Marshaller.ToCliValue(arguments[i], signature.ParameterTypes[i]); - frame.WriteArgument(i, slot.Contents); - pool.Return(slot.Contents); - } - - // Run until we return. - StepOut(cancellationToken); - - // If void, then we don't have anything else to do. - if (!signature.ReturnsValue) - return null; - - // If we produced a return value, return a copy of it to the caller. - // As the return value may be a rented bit vector, we should copy it to avoid unwanted side-effects. - var callResult = CallStack.Peek().EvaluationStack.Pop(signature.ReturnType); - var result = callResult.Clone(); - pool.Return(callResult); - - return result; - } - - /// - /// Continues execution of the virtual machine while the provided predicate returns true. - /// - /// A token that can be used for canceling the emulation. - /// - /// A predicate that is evaluated on every step of the emulation, determining whether execution should continue. - /// - public void StepWhile(CancellationToken cancellationToken, Predicate condition) - { - var context = new CilExecutionContext(this, cancellationToken); - - do - { - Step(context); - cancellationToken.ThrowIfCancellationRequested(); - } while (condition(context)); - } - - /// - /// Performs a single step in the virtual machine. If the current instruction performs a call, the emulation - /// is treated as a single instruction. - /// - public void StepOver() => StepOver(CancellationToken.None); - - /// - /// Performs a single step in the virtual machine. If the current instruction performs a call, the emulation - /// is treated as a single instruction. - /// - /// A token that can be used for canceling the emulation. - public void StepOver(CancellationToken cancellationToken) + public void DestroyThread(CilThread thread) { - int stackDepth = CallStack.Count; - StepWhile(cancellationToken, context => context.Machine.CallStack.Count > stackDepth); - } - - /// - /// Continues execution of the virtual machine until the current call frame is popped from the stack. - /// - public void StepOut() => StepOut(CancellationToken.None); - - /// - /// Continues execution of the virtual machine until the current call frame is popped from the stack. - /// - /// A token that can be used for canceling the emulation. - public void StepOut(CancellationToken cancellationToken) - { - int stackDepth = CallStack.Count; - StepWhile(cancellationToken, context => context.Machine.CallStack.Count >= stackDepth); - } - - /// - /// Performs a single step in the virtual machine. - /// - public void Step() - { - _singleStepContext ??= new CilExecutionContext(this, CancellationToken.None); - Step(_singleStepContext); - } - - /// - /// Performs a single step in the virtual machine. - /// - /// A token that can be used for canceling the emulation. - public void Step(CancellationToken cancellationToken) => Step(new CilExecutionContext(this, cancellationToken)); - - private void Step(CilExecutionContext context) - { - if (CallStack.Peek().IsRoot) - throw new CilEmulatorException("No method is currently being executed."); - - var currentFrame = CallStack.Peek(); - if (currentFrame.Body is not { } body) - throw new CilEmulatorException("Emulator only supports managed method bodies."); + if (thread.Machine != this) + throw new ArgumentException("Cannot remove a thread from a different machine."); + if (!thread.IsAlive) + throw new ArgumentException("Cannot destroy a thread that is already destroyed."); - // Determine the next instruction to dispatch. - int pc = currentFrame.ProgramCounter; - var instruction = body.Instructions.GetByOffset(pc); - if (instruction is null) - throw new CilEmulatorException($"Invalid program counter in {currentFrame}."); - - // Are we entering any protected regions? - UpdateExceptionHandlerStack(); - - // Dispatch the instruction. - var result = Dispatcher.Dispatch(context, instruction); - - if (!result.IsSuccess) - { - var exceptionObject = result.ExceptionObject; - if (exceptionObject.IsNull) - throw new CilEmulatorException("A null exception object was thrown."); - - // If there were any errors thrown after dispatching, it may trigger the execution of one of the - // exception handlers in the entire call stack. - if (!UnwindCallStack(exceptionObject)) - throw new EmulatedException(exceptionObject); - } - } - - private void UpdateExceptionHandlerStack() - { - var currentFrame = CallStack.Peek(); - - int pc = currentFrame.ProgramCounter; - var availableHandlers = currentFrame.ExceptionHandlers; - var activeHandlers = currentFrame.ExceptionHandlerStack; - - for (int i = 0; i < availableHandlers.Count; i++) - { - var handler = availableHandlers[i]; - if (handler.ProtectedRange.Contains(pc) && handler.Enter() && !activeHandlers.Contains(handler)) - activeHandlers.Push(handler); - } - } - - private bool UnwindCallStack(ObjectHandle exceptionObject) - { - while (!CallStack.Peek().IsRoot) - { - var currentFrame = CallStack.Peek(); - - var result = currentFrame.ExceptionHandlerStack.RegisterException(exceptionObject); - if (result.IsSuccess) - { - // We found a handler that needs to be called. Jump to it. - currentFrame.ProgramCounter = result.NextOffset; - - // Push the exception on the stack. - currentFrame.EvaluationStack.Clear(); - currentFrame.EvaluationStack.Push(exceptionObject); - - return true; - } - - CallStack.Pop(); - } + thread.IsAlive = false; + _threads.Remove(thread); + _callStackMemory.Free(thread.CallStack); - return false; + ThreadDestroyed?.Invoke(this, thread); } - } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/CilExecutionContext.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/CilExecutionContext.cs index b95ac837..001842da 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/CilExecutionContext.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/CilExecutionContext.cs @@ -13,24 +13,26 @@ public class CilExecutionContext /// /// The parent machine the instruction is executed on. /// A token used for canceling the emulation. - public CilExecutionContext(CilVirtualMachine machine, CancellationToken cancellationToken) + public CilExecutionContext(CilThread thread, CancellationToken cancellationToken) { - Machine = machine; + Thread = thread; CancellationToken = cancellationToken; } - /// - /// Gets the parent machine the instruction is executed on. - /// - public CilVirtualMachine Machine + public CilThread Thread { get; } + /// + /// Gets the parent machine the instruction is executed on. + /// + public CilVirtualMachine Machine => Thread.Machine; + /// /// Gets the current active stack frame. /// - public CallFrame CurrentFrame => Machine.CallStack.Peek(); + public CallFrame CurrentFrame => Thread.CallStack.Peek(); /// /// Gets a token used for canceling the emulation. 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 c0461809..68d53019 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs @@ -13,7 +13,7 @@ public class RetHandler : ICilOpCodeHandler /// public CilDispatchResult Dispatch(CilExecutionContext context, CilInstruction instruction) { - var frame = context.Machine.CallStack.Pop(); + var frame = context.Thread.CallStack.Pop(); var genericContext = GenericContext.FromMethod(frame.Method); if (frame.Method.Signature!.ReturnsValue) { 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 f44fcef1..c78e8c1c 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs @@ -155,7 +155,7 @@ private static CilDispatchResult Invoke(CilExecutionContext context, IMethodDesc for (int i = 0; i < arguments.Count; i++) frame.WriteArgument(i, arguments[i]); - context.Machine.CallStack.Push(frame); + context.Thread.CallStack.Push(frame); return CilDispatchResult.Success(); case InvocationResultType.StepOver: diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallStack.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallStack.cs index 8bd4a845..76f6b1bb 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallStack.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallStack.cs @@ -30,7 +30,7 @@ public class CallStack : IndexableStack, IMemorySpace /// /// The maximum number of bytes the stack can hold. /// The service responsible for managing types. - public CallStack(int maxSize, ValueFactory factory) + public CallStack(uint maxSize, ValueFactory factory) { _factory = factory; AddressRange = new AddressRange(0, maxSize); diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallStackMemory.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallStackMemory.cs new file mode 100644 index 00000000..680944ba --- /dev/null +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallStackMemory.cs @@ -0,0 +1,55 @@ +using System; +using Echo.Memory; + +namespace Echo.Platforms.AsmResolver.Emulation.Stack +{ + internal class CallStackMemory : IMemorySpace + { + private readonly VirtualMemory _mapping; + private readonly ValueFactory _factory; + + public CallStackMemory(uint totalSize, ValueFactory factory) + { + _mapping = new VirtualMemory(totalSize); + _factory = factory; + } + + /// + public AddressRange AddressRange => _mapping.AddressRange; + + /// + public bool IsValidAddress(long address) => _mapping.IsValidAddress(address); + + /// + public void Rebase(long baseAddress) => _mapping.Rebase(baseAddress); + + /// + public void Read(long address, BitVectorSpan buffer) => _mapping.Read(address, buffer); + + /// + public void Write(long address, BitVectorSpan buffer) => _mapping.Write(address, buffer); + + /// + public void Write(long address, ReadOnlySpan buffer) => _mapping.Write(address, buffer); + + public CallStack Allocate(uint size) + { + var result = new CallStack(size, _factory); + + long last = AddressRange.Start; + foreach (var range in _mapping.GetMappedRanges()) + { + long available = last - range.Start; + if (available >= size) + break; + + last = range.End; + } + + _mapping.Map(last, result); + return result; + } + + public void Free(CallStack stack) => _mapping.Unmap(stack.AddressRange.Start); + } +} \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs index 9d623f77..3c698ae9 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs @@ -27,12 +27,29 @@ public class CilVirtualMachineTest : IClassFixture private readonly MockModuleFixture _fixture; private readonly ITestOutputHelper _testOutputHelper; private readonly CilVirtualMachine _vm; + private readonly CilThread _mainThread; public CilVirtualMachineTest(MockModuleFixture fixture, ITestOutputHelper testOutputHelper) { _fixture = fixture; _testOutputHelper = testOutputHelper; _vm = new CilVirtualMachine(fixture.MockModule, false); + _mainThread = _vm.CreateThread(); + } + + [Fact] + public void CreateSingleThread() + { + Assert.Contains(_mainThread, _vm.Threads); + } + + [Fact] + public void CreateSecondaryThread() + { + var thread = _vm.CreateThread(); + Assert.Contains(_mainThread, _vm.Threads); + Assert.False(thread.CallStack.AddressRange.Contains(_mainThread.CallStack.AddressRange.Start)); + Assert.False(thread.CallStack.AddressRange.Contains(_mainThread.CallStack.AddressRange.End - 1)); } [Fact] @@ -52,20 +69,20 @@ public void SingleStep() dummyMethod.CilMethodBody = body; // Push frame on stack. - _vm.CallStack.Push(dummyMethod); + _mainThread.CallStack.Push(dummyMethod); // Execute all nops. for (int i = 0; i < 100; i++) - _vm.Step(); + _mainThread.Step(); // Check if we're still in the dummy method. - Assert.Equal(2, _vm.CallStack.Count); + Assert.Equal(2, _mainThread.CallStack.Count); // Execute return. - _vm.Step(); + _mainThread.Step(); // Check if we exited. - Assert.True(Assert.Single(_vm.CallStack).IsRoot); + Assert.True(Assert.Single(_mainThread.CallStack).IsRoot); } [Fact(Timeout = 5000)] @@ -85,12 +102,12 @@ public void RunShouldTerminate() dummyMethod.CilMethodBody = body; // Push frame on stack. - _vm.CallStack.Push(dummyMethod); + _mainThread.CallStack.Push(dummyMethod); - _vm.Run(); + _mainThread.Run(); // Check if we exited. - Assert.True(Assert.Single(_vm.CallStack).IsRoot); + Assert.True(Assert.Single(_mainThread.CallStack).IsRoot); } [Fact(Timeout = 5000)] @@ -111,7 +128,7 @@ public void CancelShouldThrow() dummyMethod.CilMethodBody = body; // Push frame on stack. - _vm.CallStack.Push(dummyMethod); + _mainThread.CallStack.Push(dummyMethod); var tokenSource = new CancellationTokenSource(); @@ -123,7 +140,7 @@ public void CancelShouldThrow() tokenSource.Cancel(); }; - Assert.Throws(() => _vm.Run(tokenSource.Token));; + Assert.Throws(() => _mainThread.Run(tokenSource.Token));; } [Fact] @@ -151,10 +168,10 @@ public void StepMultiple() dummyMethod.CilMethodBody = body; // Push frame on stack. - var frame = _vm.CallStack.Push(dummyMethod); + var frame = _mainThread.CallStack.Push(dummyMethod); for (int i = 0; i < 5; i++) - _vm.Step(); + _mainThread.Step(); var result = frame.EvaluationStack.Peek(); Assert.Equal((3 + 4) * 5, result.Contents.AsSpan().I32); @@ -180,13 +197,13 @@ public void StepOverNonCallsShouldStepOnce() dummyMethod.CilMethodBody.Instructions.CalculateOffsets(); // Step into method. - _vm.CallStack.Push(dummyMethod); + _mainThread.CallStack.Push(dummyMethod); // Step over first instruction. - _vm.StepOver(); + _mainThread.StepOver(); // We expect to just have moved to the second instruction. - Assert.Equal(dummyMethod.CilMethodBody.Instructions[1].Offset, _vm.CallStack.Peek().ProgramCounter); + Assert.Equal(dummyMethod.CilMethodBody.Instructions[1].Offset, _mainThread.CallStack.Peek().ProgramCounter); } [Fact] @@ -224,15 +241,15 @@ public void StepCallShouldContinueInFunction() bar.CilMethodBody.Instructions.CalculateOffsets(); // Step into method. - _vm.CallStack.Push(foo); + _mainThread.CallStack.Push(foo); // Single-step instruction. _vm.Invoker = DefaultInvokers.StepIn; - _vm.Step(); + _mainThread.Step(); // We expect to have completed "Bar" in its entirety, and moved to the second instruction. - Assert.Equal(bar, _vm.CallStack.Peek().Method); - Assert.Equal(bar.CilMethodBody.Instructions[0].Offset, _vm.CallStack.Peek().ProgramCounter); + Assert.Equal(bar, _mainThread.CallStack.Peek().Method); + Assert.Equal(bar.CilMethodBody.Instructions[0].Offset, _mainThread.CallStack.Peek().ProgramCounter); } [Fact] @@ -270,14 +287,14 @@ public void StepOverCallShouldContinueUntilInstructionAfter() bar.CilMethodBody.Instructions.CalculateOffsets(); // Step into method. - _vm.CallStack.Push(foo); + _mainThread.CallStack.Push(foo); // Step over first instruction. - _vm.StepOver(); + _mainThread.StepOver(); // We expect to have completed "Bar" in its entirety, and moved to the second instruction. - Assert.Equal(foo, _vm.CallStack.Peek().Method); - Assert.Equal(foo.CilMethodBody.Instructions[1].Offset, _vm.CallStack.Peek().ProgramCounter); + Assert.Equal(foo, _mainThread.CallStack.Peek().Method); + Assert.Equal(foo.CilMethodBody.Instructions[1].Offset, _mainThread.CallStack.Peek().ProgramCounter); } [Fact] @@ -304,14 +321,14 @@ public void StepOverBranchShouldContinueAtBranchTarget() label.Instruction = foo.CilMethodBody.Instructions[^1]; // Step into method. - _vm.CallStack.Push(foo); + _mainThread.CallStack.Push(foo); // Step over first instruction. - _vm.StepOver(); + _mainThread.StepOver(); // We expect to have jumped. - Assert.Equal(foo, _vm.CallStack.Peek().Method); - Assert.Equal(label.Offset, _vm.CallStack.Peek().ProgramCounter); + Assert.Equal(foo, _mainThread.CallStack.Peek().Method); + Assert.Equal(label.Offset, _mainThread.CallStack.Peek().ProgramCounter); } [Fact] @@ -378,7 +395,7 @@ public void CallFunctionWithLoop() arraySpan.SliceArrayElement(_vm.ValueFactory, factory.Int32, i).Write(100 + i); // Call Sum. - var returnValue = _vm.Call(sum, new BitVector[] { arrayAddress }); + var returnValue = _mainThread.Call(sum, new BitVector[] { arrayAddress }); Assert.NotNull(returnValue); Assert.Equal(Enumerable.Range(100, 10).Sum(), returnValue!.AsSpan().I32); } @@ -432,7 +449,7 @@ public void CallNestedFunction() _vm.Invoker = DefaultInvokers.StepIn; // Call Foo. - var returnValue = _vm.Call(foo, Array.Empty()); + var returnValue = _mainThread.Call(foo, Array.Empty()); Assert.NotNull(returnValue); Assert.Equal((3 + 4) * 5, returnValue!.AsSpan().I32); } @@ -486,7 +503,7 @@ public void CallWithReflection() // Call it with a real string builder. var builder = new StringBuilder(); - var returnValue = _vm.Call(foo, new object[] { builder, "John Doe" }); + var returnValue = _mainThread.Call(foo, new object[] { builder, "John Doe" }); // Check result. Assert.Null(returnValue); @@ -498,7 +515,7 @@ public void CallTryFinallyNoException() { var method = _fixture.GetTestMethod(nameof(TestClass.TryFinally)); - var result = _vm.Call(method, new object[] { false }); + var result = _mainThread.Call(method, new object[] { false }); Assert.NotNull(result); Assert.Equal(101, result!.AsSpan().I32); } @@ -508,7 +525,7 @@ public void CallTryFinallyException() { var method = _fixture.GetTestMethod(nameof(TestClass.TryFinally)); - var result = Assert.Throws(() => _vm.Call(method, new object[] {true})); + var result = Assert.Throws(() => _mainThread.Call(method, new object[] {true})); Assert.Equal("System.Exception", result.ExceptionObject.GetObjectType().FullName); } @@ -537,7 +554,7 @@ public void CallAndThrowHandledException(string methodName, object parameter) var reflectionMethod = typeof(TestClass).GetMethod(methodName); int expectedResult = (int) reflectionMethod!.Invoke(null, new[] {parameter})!; - var result = _vm.Call(method, new[] {parameter}); + var result = _mainThread.Call(method, new[] {parameter}); Assert.NotNull(result); Assert.Equal(expectedResult, result!.AsSpan().I32); @@ -566,7 +583,7 @@ public void CallAndThrowUnhandledException(string methodName, object parameter) expectedException = ex.InnerException!; } - var result = Assert.Throws(() => _vm.Call(method, new[] {parameter})); + var result = Assert.Throws(() => _mainThread.Call(method, new[] {parameter})); Assert.Equal(expectedException.GetType().FullName, result.ExceptionObject.GetObjectType().FullName); } diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/CilDispatcherTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/CilDispatcherTest.cs index d71ee0f0..29a6b101 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/CilDispatcherTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/CilDispatcherTest.cs @@ -29,9 +29,10 @@ public CilDispatcherTest(MockModuleFixture fixture) body.Instructions.Add(CilOpCodes.Ret); var vm = new CilVirtualMachine(fixture.MockModule, false); - vm.CallStack.Push(dummyMethod); + var thread = vm.CreateThread(); + thread.CallStack.Push(dummyMethod); - _context = new CilExecutionContext(vm, CancellationToken.None); + _context = new CilExecutionContext(thread, CancellationToken.None); } [Fact] diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/CilOpCodeHandlerTestBase.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/CilOpCodeHandlerTestBase.cs index d8e4ade2..67533130 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/CilOpCodeHandlerTestBase.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/CilOpCodeHandlerTestBase.cs @@ -26,9 +26,10 @@ protected CilOpCodeHandlerTestBase(MockModuleFixture fixture) body.Instructions.Add(CilOpCodes.Ret); var vm = new CilVirtualMachine(fixture.MockModule, false); - vm.CallStack.Push(dummyMethod); + var thread = vm.CreateThread(); + thread.CallStack.Push(dummyMethod); - Context = new CilExecutionContext(vm, CancellationToken.None); + Context = new CilExecutionContext(thread, CancellationToken.None); Dispatcher = new CilDispatcher(); } diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ControlFlow/RetHandlerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ControlFlow/RetHandlerTest.cs index 34eaabd9..4f3e73eb 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ControlFlow/RetHandlerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ControlFlow/RetHandlerTest.cs @@ -18,13 +18,13 @@ public RetHandlerTest(MockModuleFixture fixture) [Fact] public void RetFromVoidShouldPopFromCallStack() { - int currentFrameCount = Context.Machine.CallStack.Count; + int currentFrameCount = Context.Thread.CallStack.Count; var instruction = new CilInstruction(CilOpCodes.Ret); var result = Dispatcher.Dispatch(Context, instruction); Assert.True(result.IsSuccess); - Assert.Equal(currentFrameCount - 1, Context.Machine.CallStack.Count); + Assert.Equal(currentFrameCount - 1, Context.Thread.CallStack.Count); } [Fact] @@ -32,7 +32,7 @@ public void ReturnShouldPushValueOntoStackMarshalled() { var method = new MethodDefinition("Dummy", MethodAttributes.Static, MethodSignature.CreateStatic(ModuleFixture.MockModule.CorLibTypeFactory.Int32)); - var frame = Context.Machine.CallStack.Push(method); + var frame = Context.Thread.CallStack.Push(method); frame.EvaluationStack.Push(new StackSlot(0x0123456789abcdef, StackSlotTypeHint.Integer)); var calleeFrame = Context.CurrentFrame; diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallHandlerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallHandlerTest.cs index 887a4fa5..41f4ce7e 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallHandlerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallHandlerTest.cs @@ -162,7 +162,7 @@ public void ReturnShouldPushValueOntoStack() { var method = new MethodDefinition("Dummy", MethodAttributes.Static, MethodSignature.CreateStatic(ModuleFixture.MockModule.CorLibTypeFactory.Int32)); - var frame = Context.Machine.CallStack.Push(method); + var frame = Context.Thread.CallStack.Push(method); frame.EvaluationStack.Push(new StackSlot(0x1337, StackSlotTypeHint.Integer)); var calleeFrame = Context.CurrentFrame; diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CastHandlerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CastHandlerTest.cs index 831e8d4c..d9949267 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CastHandlerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CastHandlerTest.cs @@ -195,7 +195,7 @@ public void CastGenericTypeReturnsInt16() var methodSpecification = genericMethod.MakeGenericInstanceMethod(ModuleFixture.MockModule.CorLibTypeFactory.Int16); - Context.Machine.CallStack.Push(methodSpecification); + Context.Thread.CallStack.Push(methodSpecification); var value = 32000; @@ -225,7 +225,7 @@ public void CastGenericTypeReturnsInt32() var methodSpecification = genericMethod.MakeGenericInstanceMethod(ModuleFixture.MockModule.CorLibTypeFactory.Int32); - Context.Machine.CallStack.Push(methodSpecification); + Context.Thread.CallStack.Push(methodSpecification); var value = 214000000; @@ -254,7 +254,7 @@ public void CastGenericTypeReturnsInt64() var methodSpecification = genericMethod.MakeGenericInstanceMethod(ModuleFixture.MockModule.CorLibTypeFactory.Int64); - Context.Machine.CallStack.Push(methodSpecification); + Context.Thread.CallStack.Push(methodSpecification); var value = 922000000000000000L; diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/LdFldHandlerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/LdFldHandlerTest.cs index 3d491c81..43d92506 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/LdFldHandlerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/LdFldHandlerTest.cs @@ -114,7 +114,7 @@ public void ReadInstanceFieldFromStructureByReference() LocalVariables = {new CilLocalVariable(structType.ToTypeSignature())} }; - Context.Machine.CallStack.Push(method); + Context.Thread.CallStack.Push(method); // Initialize variable with field set to 1337. var instance = factory.CreateValue(structType.ToTypeSignature(), true); diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/LdFldaHandlerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/LdFldaHandlerTest.cs index d4dfd645..0f8626ea 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/LdFldaHandlerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/LdFldaHandlerTest.cs @@ -60,7 +60,7 @@ public void GetInstanceFieldFromStructureByReference() LocalVariables = {new CilLocalVariable(structType.ToTypeSignature())} }; - Context.Machine.CallStack.Push(method); + Context.Thread.CallStack.Push(method); // Push address to struct. var stack = Context.CurrentFrame.EvaluationStack; diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/StFldHandlerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/StFldHandlerTest.cs index 979aa68f..53a4a1ca 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/StFldHandlerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/StFldHandlerTest.cs @@ -87,7 +87,7 @@ public void WriteInstanceFieldFromStructureByReference() LocalVariables = {new CilLocalVariable(structType.ToTypeSignature())} }; - Context.Machine.CallStack.Push(method); + Context.Thread.CallStack.Push(method); // Push address to struct. var stack = Context.CurrentFrame.EvaluationStack; diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/Variables/ArgumentsTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/Variables/ArgumentsTest.cs index 8aba0b7d..12ba25b5 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/Variables/ArgumentsTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/Variables/ArgumentsTest.cs @@ -18,7 +18,7 @@ public ArgumentsTest(MockModuleFixture fixture) { } - private void PrepareMethodWithArgument(int localCount, TypeSignature localType) + private void PrepareMethodWithArgument(int localCount, TypeSignature localType) { var factory = ModuleFixture.MockModule.CorLibTypeFactory; var method = new MethodDefinition("DummyMethod", MethodAttributes.Static, @@ -31,7 +31,7 @@ private void PrepareMethodWithArgument(int localCount, TypeSignature localType) var body = new CilMethodBody(method); method.CilMethodBody = body; - Context.Machine.CallStack.Push(method); + Context.Thread.CallStack.Push(method); } [Theory] diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/Variables/LocalsTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/Variables/LocalsTest.cs index 3a993bf8..81cdf290 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/Variables/LocalsTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/Variables/LocalsTest.cs @@ -29,7 +29,7 @@ private void PrepareMethodWithLocal(int localCount, TypeSignature localType) body.LocalVariables.Add(new CilLocalVariable(localType)); method.CilMethodBody = body; - Context.Machine.CallStack.Push(method); + Context.Thread.CallStack.Push(method); } [Theory] diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Invocation/MethodInvokerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Invocation/MethodInvokerTest.cs index a18ccb12..897a0dc7 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Invocation/MethodInvokerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Invocation/MethodInvokerTest.cs @@ -22,8 +22,9 @@ public MethodInvokerTest(MockModuleFixture fixture) _fixture = fixture; var machine = new CilVirtualMachine(fixture.MockModule, false); - _context = new CilExecutionContext(machine, CancellationToken.None); - _context.Machine.CallStack.Push(fixture.MockModule.GetOrCreateModuleConstructor()); + var thread = machine.CreateThread(); + _context = new CilExecutionContext(thread, CancellationToken.None); + _context.Thread.CallStack.Push(fixture.MockModule.GetOrCreateModuleConstructor()); } [Fact] diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Stack/ExceptionHandlerFrameTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Stack/ExceptionHandlerFrameTest.cs index d18dbb5f..ef0d3846 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Stack/ExceptionHandlerFrameTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Stack/ExceptionHandlerFrameTest.cs @@ -15,16 +15,18 @@ public class ExceptionHandlerFrameTest : IClassFixture { private readonly MockModuleFixture _fixture; private readonly CilVirtualMachine _vm; + private readonly CilThread _mainThread; public ExceptionHandlerFrameTest(MockModuleFixture fixture) { _fixture = fixture; _vm = new CilVirtualMachine(_fixture.MockModule, false); + _mainThread = _vm.CreateThread(); } private CallFrame GetMockFrame(string methodName) { - return _vm.CallStack.Push(_fixture.GetTestMethod(methodName)); + return _mainThread.CallStack.Push(_fixture.GetTestMethod(methodName)); } [Fact] diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Stack/ExceptionHandlerStackTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Stack/ExceptionHandlerStackTest.cs index be9b844d..e425407b 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Stack/ExceptionHandlerStackTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Stack/ExceptionHandlerStackTest.cs @@ -20,16 +20,18 @@ public class ExceptionHandlerStackTest : IClassFixture { private readonly MockModuleFixture _fixture; private readonly CilVirtualMachine _vm; + private readonly CilThread _mainThread; public ExceptionHandlerStackTest(MockModuleFixture fixture) { _fixture = fixture; _vm = new CilVirtualMachine(_fixture.MockModule, false); + _mainThread = _vm.CreateThread(); } private CallFrame GetMockFrame(string methodName) { - return _vm.CallStack.Push(_fixture.GetTestMethod(methodName)); + return _mainThread.CallStack.Push(_fixture.GetTestMethod(methodName)); } private ObjectHandle GetMockException(Type type)