2025-04-13 17:02:46 +03:00
|
|
|
using System.Globalization;
|
|
|
|
using System.Reflection;
|
|
|
|
using CsvHelper;
|
|
|
|
using CsvHelper.Configuration;
|
|
|
|
using X86Disassembler.X86;
|
2025-04-14 23:08:52 +03:00
|
|
|
using X86Disassembler.X86.Operands;
|
2025-04-13 17:02:46 +03:00
|
|
|
using Xunit.Abstractions;
|
|
|
|
|
|
|
|
namespace X86DisassemblerTests;
|
|
|
|
|
|
|
|
public class RawFromFileDisassemblyTests(ITestOutputHelper output)
|
|
|
|
{
|
|
|
|
[Theory]
|
|
|
|
[InlineData("pushreg_tests.csv")]
|
|
|
|
[InlineData("popreg_tests.csv")]
|
2025-04-13 17:17:28 +03:00
|
|
|
[InlineData("pushimm_tests.csv")]
|
|
|
|
[InlineData("nop_tests.csv")]
|
|
|
|
[InlineData("xchg_tests.csv")]
|
|
|
|
[InlineData("sub_tests.csv")]
|
2025-04-13 19:28:56 +03:00
|
|
|
[InlineData("xor_tests.csv")]
|
2025-04-13 17:17:28 +03:00
|
|
|
[InlineData("segment_override_tests.csv")]
|
2025-04-13 17:02:46 +03:00
|
|
|
public void RunTests(string file)
|
|
|
|
{
|
|
|
|
// Load the CSV test file from embedded resources
|
|
|
|
using var stream = Assembly.GetExecutingAssembly()
|
|
|
|
.GetManifestResourceStream($"X86DisassemblerTests.TestData.{file}");
|
|
|
|
|
|
|
|
if (stream == null)
|
|
|
|
{
|
|
|
|
throw new InvalidOperationException($"Could not find {file} embedded resource");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure CSV reader with semicolon delimiter
|
|
|
|
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
|
|
|
{
|
|
|
|
HasHeaderRecord = true,
|
|
|
|
Delimiter = ";",
|
2025-04-13 17:17:28 +03:00
|
|
|
BadDataFound = null, // Ignore bad data
|
|
|
|
AllowComments = true, // Enable comments in CSV files
|
|
|
|
Comment = '#', // Use # as the comment character
|
|
|
|
IgnoreBlankLines = true // Skip empty lines
|
2025-04-13 17:02:46 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
using var streamReader = new StreamReader(stream);
|
|
|
|
using var csvReader = new CsvReader(streamReader, config);
|
|
|
|
|
|
|
|
// Register class map for TestFromFileEntry
|
|
|
|
csvReader.Context.RegisterClassMap<TestFromFileEntryMap>();
|
|
|
|
|
|
|
|
// Read all records from CSV
|
|
|
|
var tests = csvReader.GetRecords<TestFromFileEntry>()
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
// Run tests for each instruction
|
|
|
|
for (var index = 0; index < tests.Count; index++)
|
|
|
|
{
|
|
|
|
var test = tests[index];
|
|
|
|
|
|
|
|
// Convert hex string to byte array
|
|
|
|
byte[] code = HexStringToByteArray(test.RawBytes);
|
|
|
|
|
|
|
|
// Create a disassembler with the code
|
|
|
|
Disassembler disassembler = new Disassembler(code, 0x1000);
|
|
|
|
|
|
|
|
// Disassemble the code
|
|
|
|
var disassembledInstructions = disassembler.Disassemble();
|
|
|
|
|
|
|
|
// Verify the number of instructions
|
|
|
|
if (test.Instructions.Count != disassembledInstructions.Count)
|
|
|
|
{
|
|
|
|
AssertFailWithReason(
|
|
|
|
index,
|
|
|
|
file,
|
|
|
|
test,
|
|
|
|
disassembledInstructions,
|
|
|
|
"Instruction count mismatch"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify each instruction
|
|
|
|
for (int i = 0; i < test.Instructions.Count; i++)
|
|
|
|
{
|
|
|
|
var expected = test.Instructions[i];
|
|
|
|
var actual = disassembledInstructions[i];
|
|
|
|
|
2025-04-14 23:08:52 +03:00
|
|
|
// Compare instruction type instead of mnemonic
|
|
|
|
if (expected.Type != actual.Type)
|
2025-04-13 17:02:46 +03:00
|
|
|
{
|
|
|
|
AssertFailWithReason(
|
|
|
|
index,
|
|
|
|
file,
|
|
|
|
test,
|
|
|
|
disassembledInstructions,
|
2025-04-14 23:08:52 +03:00
|
|
|
$"Type mismatch: Expected {expected.Type}, got {actual.Type}"
|
2025-04-13 17:02:46 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-04-14 23:08:52 +03:00
|
|
|
// For operands, we need to do a string comparison since the CSV contains string operands
|
|
|
|
// and we now have structured operands in the actual instruction
|
|
|
|
string actualOperandsString = string.Join(", ", actual.StructuredOperands);
|
|
|
|
if (!CompareOperands(expected.Operands, actualOperandsString))
|
2025-04-13 17:02:46 +03:00
|
|
|
{
|
|
|
|
AssertFailWithReason(
|
|
|
|
index,
|
|
|
|
file,
|
|
|
|
test,
|
|
|
|
disassembledInstructions,
|
2025-04-14 23:08:52 +03:00
|
|
|
$"Operands mismatch: Expected '{expected.Operands}', got '{actualOperandsString}'"
|
2025-04-13 17:02:46 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-14 23:08:52 +03:00
|
|
|
// Compare operands with some flexibility since the string representation might be slightly different
|
|
|
|
private bool CompareOperands(string expected, string actual)
|
2025-04-13 17:02:46 +03:00
|
|
|
{
|
2025-04-14 23:08:52 +03:00
|
|
|
// Normalize strings for comparison
|
|
|
|
expected = NormalizeOperandString(expected);
|
|
|
|
actual = NormalizeOperandString(actual);
|
|
|
|
|
|
|
|
return expected == actual;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize operand strings to handle slight formatting differences
|
|
|
|
private string NormalizeOperandString(string operands)
|
|
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(operands))
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
|
// Remove all spaces
|
|
|
|
operands = operands.Replace(" ", "");
|
|
|
|
|
|
|
|
// Convert to lowercase
|
|
|
|
operands = operands.ToLowerInvariant();
|
|
|
|
|
|
|
|
// Normalize hex values (remove 0x prefix if present)
|
|
|
|
operands = operands.Replace("0x", "");
|
|
|
|
|
|
|
|
return operands;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void AssertFailWithReason(int index, string file, TestFromFileEntry test, List<Instruction> disassembledInstructions, string reason)
|
|
|
|
{
|
|
|
|
output.WriteLine($"Test {index} in {file} failed: {reason}");
|
|
|
|
output.WriteLine($"Raw bytes: {test.RawBytes}");
|
|
|
|
output.WriteLine("Expected instructions:");
|
|
|
|
foreach (var instruction in test.Instructions)
|
|
|
|
{
|
|
|
|
output.WriteLine($" {instruction.Mnemonic} {instruction.Operands}");
|
|
|
|
}
|
|
|
|
output.WriteLine("Actual instructions:");
|
|
|
|
foreach (var instruction in disassembledInstructions)
|
|
|
|
{
|
|
|
|
output.WriteLine($" {instruction.Type} {string.Join(", ", instruction.StructuredOperands)}");
|
|
|
|
}
|
|
|
|
Assert.True(false, reason);
|
2025-04-13 17:02:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private static byte[] HexStringToByteArray(string hex)
|
|
|
|
{
|
2025-04-14 23:08:52 +03:00
|
|
|
// Remove any spaces or other formatting characters
|
|
|
|
hex = hex.Replace(" ", "").Replace("-", "").Replace("0x", "");
|
2025-04-13 17:02:46 +03:00
|
|
|
|
2025-04-14 23:08:52 +03:00
|
|
|
// Create a byte array that will hold the converted hex string
|
2025-04-13 17:02:46 +03:00
|
|
|
byte[] bytes = new byte[hex.Length / 2];
|
|
|
|
|
2025-04-14 23:08:52 +03:00
|
|
|
// Convert each pair of hex characters to a byte
|
|
|
|
for (int i = 0; i < hex.Length; i += 2)
|
2025-04-13 17:02:46 +03:00
|
|
|
{
|
2025-04-14 23:08:52 +03:00
|
|
|
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
2025-04-13 17:02:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
}
|