diff --git a/AssEmbly.Test/AssEmbly.Test.csproj b/AssEmbly.Test/AssEmbly.Test.csproj index 79728f5..ccece0d 100644 --- a/AssEmbly.Test/AssEmbly.Test.csproj +++ b/AssEmbly.Test/AssEmbly.Test.csproj @@ -21,4 +21,13 @@ <ProjectReference Include="..\AssEmbly.csproj" /> </ItemGroup> + <ItemGroup> + <None Update="test-invalid.dll"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="test.dll"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + </ItemGroup> + </Project> diff --git a/AssEmbly.Test/ProcessorTests.cs b/AssEmbly.Test/ProcessorTests.cs index 2ba74e0..fb5553a 100644 --- a/AssEmbly.Test/ProcessorTests.cs +++ b/AssEmbly.Test/ProcessorTests.cs @@ -1,3 +1,4 @@ +using System.Runtime.Loader; using System.Text; namespace AssEmbly.Test @@ -13685,6 +13686,729 @@ public void EXTD_BSW_Register() Assert.AreEqual(0xF0DEBC9A78563412, testProcessor.Registers[(int)Register.rg7], "Instruction did not produce correct result"); Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); } + + [TestMethod] + public void ASMX_LDA_Address() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x00, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("test.dll\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.IsNotNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not create load context"); + Assert.IsNotNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not open assembly"); + Assert.AreEqual(11UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x00, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("test-invalid.dll\0").CopyTo(testProcessor.Memory, 552); + _ = Assert.ThrowsException<InvalidAssemblyException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when loading invalid assembly"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x00, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("test-missing.dll\0").CopyTo(testProcessor.Memory, 552); + _ = Assert.ThrowsException<InvalidAssemblyException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when loading non-existent assembly"); + } + } + + [TestMethod] + public void ASMX_LDA_Pointer() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x01, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("test.dll\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.IsNotNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not create load context"); + Assert.IsNotNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not open assembly"); + Assert.AreEqual(4UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x01, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("test-invalid.dll\0").CopyTo(testProcessor.Memory, 552); + _ = Assert.ThrowsException<InvalidAssemblyException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when loading invalid assembly"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x01, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("test-missing.dll\0").CopyTo(testProcessor.Memory, 552); + _ = Assert.ThrowsException<InvalidAssemblyException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when loading non-existent assembly"); + } + } + + [TestMethod] + public void ASMX_LDF_Address() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x02, 0xD2, 4, 0, 0, 0, 0, 0, 0 + }); + Encoding.UTF8.GetBytes("TestMethod\0").CopyTo(testProcessor.Memory, 1234); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.IsNotNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not open function"); + Assert.AreEqual(11UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x02, 0xD2, 4, 0, 0, 0, 0, 0, 0 + }); + Encoding.UTF8.GetBytes("InvalidMethod\0").CopyTo(testProcessor.Memory, 1234); + _ = Assert.ThrowsException<InvalidFunctionException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when loading invalid function"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x02, 0xD2, 4, 0, 0, 0, 0, 0, 0 + }); + Encoding.UTF8.GetBytes("TestMethod3\0").CopyTo(testProcessor.Memory, 1234); + _ = Assert.ThrowsException<InvalidFunctionException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when loading non-existent function"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x02, 0xD2, 4, 0, 0, 0, 0, 0, 0 + }); + Encoding.UTF8.GetBytes("TestMethod\0").CopyTo(testProcessor.Memory, 1234); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading an assembly"); + } + } + + [TestMethod] + public void ASMX_LDF_Pointer() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.Registers[(int)Register.rg8] = 1234; + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x03, (int)Register.rg8 + }); + Encoding.UTF8.GetBytes("TestMethod\0").CopyTo(testProcessor.Memory, 1234); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.IsNotNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not open function"); + Assert.AreEqual(4UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.Registers[(int)Register.rg8] = 1234; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x03, (int)Register.rg8 + }); + Encoding.UTF8.GetBytes("InvalidMethod\0").CopyTo(testProcessor.Memory, 1234); + _ = Assert.ThrowsException<InvalidFunctionException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when loading invalid function"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.Registers[(int)Register.rg8] = 1234; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x03, (int)Register.rg8 + }); + Encoding.UTF8.GetBytes("TestMethod3\0").CopyTo(testProcessor.Memory, 1234); + _ = Assert.ThrowsException<InvalidFunctionException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when loading non-existent function"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg8] = 1234; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x03, (int)Register.rg8 + }); + Encoding.UTF8.GetBytes("TestMethod\0").CopyTo(testProcessor.Memory, 1234); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading an assembly"); + } + } + + [TestMethod] + public void ASMX_CLA() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly?.GetMethod("TestMethod", BindingFlags.Default, Processor.ExternalMethodParamTypes)); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x10 + }); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.IsNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not remove load context"); + Assert.IsNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not remove open assembly"); + Assert.IsNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not remove open function"); + Assert.AreEqual(3UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x10 + }); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading an assembly"); + } + } + + [TestMethod] + public void ASMX_CLF() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly?.GetMethod("TestMethod", BindingFlags.Default, Processor.ExternalMethodParamTypes)); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x11 + }); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.IsNotNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed load context"); + Assert.IsNotNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open assembly"); + Assert.IsNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction did not remove open function"); + Assert.AreEqual(3UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x11 + }); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading a function"); + } + } + + [TestMethod] + public void ASMX_AEX_Register_Address() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x20, (int)Register.rg0, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("test.dll\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(12UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(1UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x20, (int)Register.rg0, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("test-invalid.dll\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(12UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x20, (int)Register.rg0, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("test-missing.dll\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(12UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + } + + [TestMethod] + public void ASMX_AEX_Register_Pointer() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x21, (int)Register.rg0, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("test.dll\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(5UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(1UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x21, (int)Register.rg0, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("test-invalid.dll\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(5UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x21, (int)Register.rg0, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("test-missing.dll\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(5UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + } + + [TestMethod] + public void ASMX_FEX_Register_Address() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x22, (int)Register.rg0, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("TestMethod\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(12UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(1UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x22, (int)Register.rg0, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("InvalidMethod\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(12UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x22, (int)Register.rg0, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("TestMethod2\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(12UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x22, (int)Register.rg0, 0x28, 2, 0, 0, 0, 0, 0, 0 }); + Encoding.UTF8.GetBytes("TestMethod\0").CopyTo(testProcessor.Memory, 552); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading an assembly"); + } + } + + [TestMethod] + public void ASMX_FEX_Register_Pointer() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x23, (int)Register.rg0, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("TestMethod\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(5UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(1UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x23, (int)Register.rg0, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("InvalidMethod\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(5UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop")); + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x23, (int)Register.rg0, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("TestMethod2\0").CopyTo(testProcessor.Memory, 552); + // Test that no exception is thrown + _ = testProcessor.Execute(false); + Assert.AreEqual(5UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg0], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + } + + using (Processor testProcessor = new(2046)) + { + testProcessor.Registers[(int)Register.rg7] = 552; + testProcessor.LoadProgram(new byte[] { 0xFF, 0x04, 0x23, (int)Register.rg0, (int)Register.rg7 }); + Encoding.UTF8.GetBytes("TestMethod\0").CopyTo(testProcessor.Memory, 552); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading an assembly"); + } + } + + [TestMethod] + public void ASMX_CAL() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly?.GetMethod("TestMethod", BindingFlags.Default, Processor.ExternalMethodParamTypes)); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x30 + }); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.AreEqual(3UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0xABUL, testProcessor.Memory[1234], "Instruction did not produce correct result"); + Assert.AreEqual(0UL, testProcessor.Registers[(int)Register.rg8], "Instruction did not produce correct result"); + Assert.AreEqual(0xCDUL, testProcessor.Registers[(int)Register.rg9], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + Assert.IsNotNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed load context"); + Assert.IsNotNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open assembly"); + Assert.IsNotNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open function"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x30 + }); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading a function"); + } + } + + [TestMethod] + public void ASMX_CAL_Register() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly?.GetMethod("TestMethod", BindingFlags.Default, Processor.ExternalMethodParamTypes)); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.Registers[(int)Register.rg3] = 0x123456789ABCDEF0; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x31, (int)Register.rg3 + }); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.AreEqual(3UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0xABUL, testProcessor.Memory[1234], "Instruction did not produce correct result"); + Assert.AreEqual(0x123456789ABCDEF0UL, testProcessor.Registers[(int)Register.rg8], "Instruction did not produce correct result"); + Assert.AreEqual(0xCDUL, testProcessor.Registers[(int)Register.rg9], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + Assert.IsNotNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed load context"); + Assert.IsNotNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open assembly"); + Assert.IsNotNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open function"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x31, (int)Register.rg3 + }); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading a function"); + } + } + + [TestMethod] + public void ASMX_CAL_Literal() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly?.GetMethod("TestMethod", BindingFlags.Default, Processor.ExternalMethodParamTypes)); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x32, 0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12 + }); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.AreEqual(11UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0xABUL, testProcessor.Memory[1234], "Instruction did not produce correct result"); + Assert.AreEqual(0x123456789ABCDEF0UL, testProcessor.Registers[(int)Register.rg8], "Instruction did not produce correct result"); + Assert.AreEqual(0xCDUL, testProcessor.Registers[(int)Register.rg9], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + Assert.IsNotNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed load context"); + Assert.IsNotNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open assembly"); + Assert.IsNotNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open function"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x32, (int)Register.rg3 + }); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading a function"); + } + } + + [TestMethod] + public void ASMX_CAL_Address() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly?.GetMethod("TestMethod", BindingFlags.Default, Processor.ExternalMethodParamTypes)); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x33, 0x28, 2, 0, 0, 0, 0, 0, 0 + }); + testProcessor.WriteMemoryQWord(552, 0x123456789ABCDEF0); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.AreEqual(11UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0xABUL, testProcessor.Memory[1234], "Instruction did not produce correct result"); + Assert.AreEqual(0x123456789ABCDEF0UL, testProcessor.Registers[(int)Register.rg8], "Instruction did not produce correct result"); + Assert.AreEqual(0xCDUL, testProcessor.Registers[(int)Register.rg9], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + Assert.IsNotNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed load context"); + Assert.IsNotNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open assembly"); + Assert.IsNotNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open function"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x33, 0x28, 2, 0, 0, 0, 0, 0, 0 + }); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading a function"); + } + } + + [TestMethod] + public void ASMX_CAL_Pointer() + { + // "using" is used here so that the open assembly is closed without having to use closing instructions + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly?.GetMethod("TestMethod", BindingFlags.Default, Processor.ExternalMethodParamTypes)); + testProcessor.Registers[(int)Register.rsf] = ulong.MaxValue; + testProcessor.Registers[(int)Register.rg3] = 552; + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x34, (int)Register.rg3 + }); + testProcessor.WriteMemoryQWord(552, 0x123456789ABCDEF0); + // Test that no exception is thrown + _ = testProcessor.Execute(true); + Assert.AreEqual(4UL, testProcessor.Registers[(int)Register.rpo], "Instruction updated the rpo register by an incorrect amount"); + Assert.AreEqual(0xABUL, testProcessor.Memory[1234], "Instruction did not produce correct result"); + Assert.AreEqual(0x123456789ABCDEF0UL, testProcessor.Registers[(int)Register.rg8], "Instruction did not produce correct result"); + Assert.AreEqual(0xCDUL, testProcessor.Registers[(int)Register.rg9], "Instruction did not produce correct result"); + Assert.AreEqual(ulong.MaxValue, testProcessor.Registers[(int)Register.rsf], "Instruction updated the status flags"); + Assert.IsNotNull(typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed load context"); + Assert.IsNotNull(typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open assembly"); + Assert.IsNotNull(typeof(Processor).GetField("openExtFunction", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(testProcessor), "Instruction removed open function"); + } + + using (Processor testProcessor = new(2046)) + { + AssemblyLoadContext loadContext = new("TestLoadContext", true); + typeof(Processor).GetField("extLoadContext", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(testProcessor, loadContext); + Type? loadedAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath("test.dll")).GetType("AssEmblyInterop"); + typeof(Processor).GetField("openExtAssembly", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue( + testProcessor, loadedAssembly); + testProcessor.LoadProgram(new byte[] + { + 0xFF, 0x04, 0x34, (int)Register.rg3 + }); + _ = Assert.ThrowsException<ExternalOperationException>(() => testProcessor.Execute(false), + "Instruction did not throw an exception when used without loading a function"); + } + } } private class AllZeroRandom : Random diff --git a/Exceptions.cs b/Exceptions.cs index adc71a3..cbbfd92 100644 --- a/Exceptions.cs +++ b/Exceptions.cs @@ -196,6 +196,18 @@ public InvalidFunctionException(string message, string consoleMessage) : base(me public InvalidFunctionException(string message, string consoleMessage, Exception inner) : base(message, consoleMessage, inner) { } } + /// <summary> + /// The exception that is thrown when an instruction is used without an external assembly or function loaded when one is required. + /// </summary> + public class ExternalOperationException : RuntimeException + { + public ExternalOperationException() : base() { } + public ExternalOperationException(string message) : base(message) { } + public ExternalOperationException(string message, Exception inner) : base(message, inner) { } + public ExternalOperationException(string message, string consoleMessage) : base(message, consoleMessage) { } + public ExternalOperationException(string message, string consoleMessage, Exception inner) : base(message, consoleMessage, inner) { } + } + /// <summary> /// The exception that is thrown when there is not enough free memory remaining to perform the requested allocation. /// </summary> diff --git a/Processor.cs b/Processor.cs index c1afff0..761aafa 100644 --- a/Processor.cs +++ b/Processor.cs @@ -1,5 +1,7 @@ using System.Buffers.Binary; using System.Globalization; +using System.Reflection; +using System.Runtime.Loader; using System.Text; using AssEmbly.Resources.Localization; @@ -10,6 +12,9 @@ namespace AssEmbly /// </summary> public class Processor : IDisposable { + public static readonly Type[] ExternalMethodParamTypes = + new Type[3] { typeof(byte[]), typeof(byte[]), typeof(ulong?) }; + public readonly byte[] Memory; public readonly ulong[] Registers; public readonly bool UseV1CallStack; @@ -23,6 +28,10 @@ public class Processor : IDisposable private BinaryWriter? fileWrite; private long openFileSize; + private AssemblyLoadContext? extLoadContext; + private Type? openExtAssembly; + private MethodInfo? openExtFunction; + private readonly Random rng = new(); // Because C#'s console methods work with potentially multi-byte characters at a time, @@ -53,9 +62,12 @@ public Processor(ulong memorySize, ulong entryPoint = 0, bool useV1CallStack = f public void Dispose() { + extLoadContext?.Unload(); + openFile?.Dispose(); fileRead?.Dispose(); fileWrite?.Dispose(); + GC.SuppressFinalize(this); }