0
mirror of https://github.com/sampletext32/ParkanPlayground.git synced 2025-05-19 03:41:18 +03:00

Fixed byte order handling in SUB instruction handlers and updated tests

Implemented SUB r32, r/m32 instruction handlers and tests

Added comprehensive tests for Push/Pop, Xchg, Sub instructions and enhanced segment override tests
This commit is contained in:
bird_egop 2025-04-13 14:25:27 +03:00
parent 44c73321ea
commit 2c85192d13
9 changed files with 959 additions and 3 deletions

View File

@ -77,6 +77,7 @@ public class InstructionHandlerFactory
RegisterFloatingPointHandlers(); RegisterFloatingPointHandlers();
RegisterStringHandlers(); RegisterStringHandlers();
RegisterMovHandlers(); RegisterMovHandlers();
RegisterSubHandlers(); // Register SUB handlers
} }
/// <summary> /// <summary>
@ -367,6 +368,16 @@ public class InstructionHandlerFactory
_handlers.Add(new AndEaxImmHandler(_codeBuffer, _decoder, _length)); _handlers.Add(new AndEaxImmHandler(_codeBuffer, _decoder, _length));
} }
/// <summary>
/// Registers all SUB instruction handlers
/// </summary>
private void RegisterSubHandlers()
{
// Add SUB register/memory handlers
_handlers.Add(new Sub.SubRm32R32Handler(_codeBuffer, _decoder, _length));
_handlers.Add(new Sub.SubR32Rm32Handler(_codeBuffer, _decoder, _length));
}
/// <summary> /// <summary>
/// Gets the handler that can decode the given opcode /// Gets the handler that can decode the given opcode
/// </summary> /// </summary>

View File

@ -73,7 +73,16 @@ public class SubImmFromRm32Handler : InstructionHandler
return false; return false;
} }
uint imm32 = BitConverter.ToUInt32(CodeBuffer, position); // Read the immediate value in little-endian format and convert to big-endian for display
byte b0 = CodeBuffer[position];
byte b1 = CodeBuffer[position + 1];
byte b2 = CodeBuffer[position + 2];
byte b3 = CodeBuffer[position + 3];
// Convert from little-endian to big-endian for display
uint imm32 = (uint)((b3 << 24) | (b2 << 16) | (b1 << 8) | b0);
// Advance the position
Decoder.SetPosition(position + 4); Decoder.SetPosition(position + 4);
// Set the operands // Set the operands

View File

@ -73,12 +73,26 @@ public class SubImmFromRm32SignExtendedHandler : InstructionHandler
return false; return false;
} }
// Read the immediate value as a signed byte and sign-extend it // Read the immediate value as a signed byte and sign-extend it to 32 bits
sbyte imm8 = (sbyte)CodeBuffer[position++]; sbyte imm8 = (sbyte)CodeBuffer[position++];
int imm32 = imm8; // Automatic sign extension from sbyte to int
Decoder.SetPosition(position); Decoder.SetPosition(position);
// Format the immediate value based on whether it's positive or negative
string immStr;
if (imm8 < 0)
{
// For negative values, show the full 32-bit representation
immStr = $"0x{(uint)imm32:X8}";
}
else
{
// For positive values, just show the value
immStr = $"0x{(byte)imm8:X2}";
}
// Set the operands // Set the operands
instruction.Operands = $"{destOperand}, 0x{(uint)imm8:X2}"; instruction.Operands = $"{destOperand}, {immStr}";
return true; return true;
} }

View File

@ -0,0 +1,73 @@
namespace X86Disassembler.X86.Handlers.Sub;
/// <summary>
/// Handler for SUB r32, r/m32 instruction (0x2B)
/// </summary>
public class SubR32Rm32Handler : InstructionHandler
{
/// <summary>
/// Initializes a new instance of the SubR32Rm32Handler class
/// </summary>
/// <param name="codeBuffer">The buffer containing the code to decode</param>
/// <param name="decoder">The instruction decoder that owns this handler</param>
/// <param name="length">The length of the buffer</param>
public SubR32Rm32Handler(byte[] codeBuffer, InstructionDecoder decoder, int length)
: base(codeBuffer, decoder, length)
{
}
/// <summary>
/// Checks if this handler can decode the given opcode
/// </summary>
/// <param name="opcode">The opcode to check</param>
/// <returns>True if this handler can decode the opcode</returns>
public override bool CanHandle(byte opcode)
{
return opcode == 0x2B;
}
/// <summary>
/// Decodes a SUB r32, r/m32 instruction
/// </summary>
/// <param name="opcode">The opcode of the instruction</param>
/// <param name="instruction">The instruction object to populate</param>
/// <returns>True if the instruction was successfully decoded</returns>
public override bool Decode(byte opcode, Instruction instruction)
{
int position = Decoder.GetPosition();
if (position >= Length)
{
return false;
}
// Read the ModR/M byte
byte modRM = CodeBuffer[position++];
Decoder.SetPosition(position);
// Extract the fields from the ModR/M byte
byte mod = (byte)((modRM & 0xC0) >> 6);
byte reg = (byte)((modRM & 0x38) >> 3);
byte rm = (byte)(modRM & 0x07);
// Set the mnemonic
instruction.Mnemonic = "sub";
// Get the register name
string regName = GetRegister32(reg);
// For memory operands, set the operand
if (mod != 3) // Memory operand
{
string operand = ModRMDecoder.DecodeModRM(mod, rm, false);
instruction.Operands = $"{regName}, {operand}";
}
else // Register operand
{
string rmName = GetRegister32(rm);
instruction.Operands = $"{regName}, {rmName}";
}
return true;
}
}

View File

@ -0,0 +1,73 @@
namespace X86Disassembler.X86.Handlers.Sub;
/// <summary>
/// Handler for SUB r/m32, r32 instruction (0x29)
/// </summary>
public class SubRm32R32Handler : InstructionHandler
{
/// <summary>
/// Initializes a new instance of the SubRm32R32Handler class
/// </summary>
/// <param name="codeBuffer">The buffer containing the code to decode</param>
/// <param name="decoder">The instruction decoder that owns this handler</param>
/// <param name="length">The length of the buffer</param>
public SubRm32R32Handler(byte[] codeBuffer, InstructionDecoder decoder, int length)
: base(codeBuffer, decoder, length)
{
}
/// <summary>
/// Checks if this handler can decode the given opcode
/// </summary>
/// <param name="opcode">The opcode to check</param>
/// <returns>True if this handler can decode the opcode</returns>
public override bool CanHandle(byte opcode)
{
return opcode == 0x29;
}
/// <summary>
/// Decodes a SUB r/m32, r32 instruction
/// </summary>
/// <param name="opcode">The opcode of the instruction</param>
/// <param name="instruction">The instruction object to populate</param>
/// <returns>True if the instruction was successfully decoded</returns>
public override bool Decode(byte opcode, Instruction instruction)
{
int position = Decoder.GetPosition();
if (position >= Length)
{
return false;
}
// Read the ModR/M byte
byte modRM = CodeBuffer[position++];
Decoder.SetPosition(position);
// Extract the fields from the ModR/M byte
byte mod = (byte)((modRM & 0xC0) >> 6);
byte reg = (byte)((modRM & 0x38) >> 3);
byte rm = (byte)(modRM & 0x07);
// Set the mnemonic
instruction.Mnemonic = "sub";
// Get the register name
string regName = GetRegister32(reg);
// For memory operands, set the operand
if (mod != 3) // Memory operand
{
string operand = ModRMDecoder.DecodeModRM(mod, rm, false);
instruction.Operands = $"{operand}, {regName}";
}
else // Register operand
{
string rmName = GetRegister32(rm);
instruction.Operands = $"{rmName}, {regName}";
}
return true;
}
}

View File

@ -0,0 +1,187 @@
namespace X86DisassemblerTests;
using System;
using Xunit;
using X86Disassembler.X86;
using X86Disassembler.X86.Handlers.Push;
using X86Disassembler.X86.Handlers.Pop;
/// <summary>
/// Tests for push and pop instruction handlers
/// </summary>
public class PushPopInstructionTests
{
/// <summary>
/// Tests the PushRegHandler for decoding PUSH r32 instruction
/// </summary>
[Fact]
public void PushRegHandler_DecodesPushReg_Correctly()
{
// Arrange
// PUSH EAX (50) - Push EAX onto the stack
byte[] codeBuffer = new byte[] { 0x50 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("push", instruction.Mnemonic);
Assert.Equal("eax", instruction.Operands);
}
/// <summary>
/// Tests the PushRegHandler for decoding PUSH r32 instruction with different register
/// </summary>
[Fact]
public void PushRegHandler_DecodesPushEbp_Correctly()
{
// Arrange
// PUSH EBP (55) - Push EBP onto the stack
byte[] codeBuffer = new byte[] { 0x55 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("push", instruction.Mnemonic);
Assert.Equal("ebp", instruction.Operands);
}
/// <summary>
/// Tests the PushImm8Handler for decoding PUSH imm8 instruction
/// </summary>
[Fact]
public void PushImm8Handler_DecodesPushImm8_Correctly()
{
// Arrange
// PUSH 0x42 (6A 42) - Push 8-bit immediate value onto the stack
byte[] codeBuffer = new byte[] { 0x6A, 0x42 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("push", instruction.Mnemonic);
Assert.Equal("0x42", instruction.Operands);
}
/// <summary>
/// Tests the PushImm32Handler for decoding PUSH imm32 instruction
/// </summary>
[Fact]
public void PushImm32Handler_DecodesPushImm32_Correctly()
{
// Arrange
// PUSH 0x12345678 (68 78 56 34 12) - Push 32-bit immediate value onto the stack
byte[] codeBuffer = new byte[] { 0x68, 0x78, 0x56, 0x34, 0x12 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("push", instruction.Mnemonic);
Assert.Equal("0x12345678", instruction.Operands);
}
/// <summary>
/// Tests the PopRegHandler for decoding POP r32 instruction
/// </summary>
[Fact]
public void PopRegHandler_DecodesPopReg_Correctly()
{
// Arrange
// POP EAX (58) - Pop value from stack into EAX
byte[] codeBuffer = new byte[] { 0x58 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("pop", instruction.Mnemonic);
Assert.Equal("eax", instruction.Operands);
}
/// <summary>
/// Tests the PopRegHandler for decoding POP r32 instruction with different register
/// </summary>
[Fact]
public void PopRegHandler_DecodesPopEbp_Correctly()
{
// Arrange
// POP EBP (5D) - Pop value from stack into EBP
byte[] codeBuffer = new byte[] { 0x5D };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("pop", instruction.Mnemonic);
Assert.Equal("ebp", instruction.Operands);
}
/// <summary>
/// Tests a common function prologue sequence (PUSH EBP, MOV EBP, ESP)
/// </summary>
[Fact]
public void PushPop_DecodesFunctionPrologue_Correctly()
{
// Arrange
// PUSH EBP (55)
// MOV EBP, ESP (89 E5)
byte[] codeBuffer = new byte[] { 0x55, 0x89, 0xE5 };
var disassembler = new Disassembler(codeBuffer, 0);
// Act
var instructions = disassembler.Disassemble();
// Assert
Assert.Equal(2, instructions.Count);
// First instruction: PUSH EBP
Assert.Equal("push", instructions[0].Mnemonic);
Assert.Equal("ebp", instructions[0].Operands);
// Second instruction: MOV EBP, ESP
Assert.Equal("mov", instructions[1].Mnemonic);
Assert.Equal("ebp, esp", instructions[1].Operands);
}
/// <summary>
/// Tests a common function epilogue sequence (POP EBP, RET)
/// </summary>
[Fact]
public void PushPop_DecodesFunctionEpilogue_Correctly()
{
// Arrange
// POP EBP (5D)
// RET (C3)
byte[] codeBuffer = new byte[] { 0x5D, 0xC3 };
var disassembler = new Disassembler(codeBuffer, 0);
// Act
var instructions = disassembler.Disassemble();
// Assert
Assert.Equal(2, instructions.Count);
// First instruction: POP EBP
Assert.Equal("pop", instructions[0].Mnemonic);
Assert.Equal("ebp", instructions[0].Operands);
// Second instruction: RET
Assert.Equal("ret", instructions[1].Mnemonic);
Assert.Equal("", instructions[1].Operands);
}
}

View File

@ -9,6 +9,66 @@ using X86Disassembler.X86;
/// </summary> /// </summary>
public class SegmentOverrideTests public class SegmentOverrideTests
{ {
/// <summary>
/// Tests that the CS segment override prefix (0x2E) is correctly recognized
/// </summary>
[Fact]
public void CsSegmentOverride_IsRecognized()
{
// Arrange
// CS segment override prefix (0x2E) followed by MOV EAX, [0] (8B 05 00 00 00 00)
byte[] codeBuffer = new byte[] { 0x2E, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("mov", instruction.Mnemonic);
Assert.Equal("eax, dword ptr cs:[0x00000000]", instruction.Operands);
}
/// <summary>
/// Tests that the DS segment override prefix (0x3E) is correctly recognized
/// </summary>
[Fact]
public void DsSegmentOverride_IsRecognized()
{
// Arrange
// DS segment override prefix (0x3E) followed by MOV EAX, [0] (8B 05 00 00 00 00)
byte[] codeBuffer = new byte[] { 0x3E, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("mov", instruction.Mnemonic);
Assert.Equal("eax, dword ptr ds:[0x00000000]", instruction.Operands);
}
/// <summary>
/// Tests that the ES segment override prefix (0x26) is correctly recognized
/// </summary>
[Fact]
public void EsSegmentOverride_IsRecognized()
{
// Arrange
// ES segment override prefix (0x26) followed by MOV EAX, [0] (8B 05 00 00 00 00)
byte[] codeBuffer = new byte[] { 0x26, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("mov", instruction.Mnemonic);
Assert.Equal("eax, dword ptr es:[0x00000000]", instruction.Operands);
}
/// <summary> /// <summary>
/// Tests that the FS segment override prefix (0x64) is correctly recognized /// Tests that the FS segment override prefix (0x64) is correctly recognized
/// </summary> /// </summary>
@ -29,6 +89,46 @@ public class SegmentOverrideTests
Assert.Equal("dword ptr fs:[0x00000000], esp", instruction.Operands); Assert.Equal("dword ptr fs:[0x00000000], esp", instruction.Operands);
} }
/// <summary>
/// Tests that the GS segment override prefix (0x65) is correctly recognized
/// </summary>
[Fact]
public void GsSegmentOverride_IsRecognized()
{
// Arrange
// GS segment override prefix (0x65) followed by MOV EAX, [0] (8B 05 00 00 00 00)
byte[] codeBuffer = new byte[] { 0x65, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("mov", instruction.Mnemonic);
Assert.Equal("eax, dword ptr gs:[0x00000000]", instruction.Operands);
}
/// <summary>
/// Tests that the SS segment override prefix (0x36) is correctly recognized
/// </summary>
[Fact]
public void SsSegmentOverride_IsRecognized()
{
// Arrange
// SS segment override prefix (0x36) followed by MOV EAX, [EBP-4] (8B 45 FC)
byte[] codeBuffer = new byte[] { 0x36, 0x8B, 0x45, 0xFC };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("mov", instruction.Mnemonic);
Assert.Equal("eax, dword ptr ss:[ebp-0x04]", instruction.Operands);
}
/// <summary> /// <summary>
/// Tests that the FS segment override prefix (0x64) is correctly recognized when it's the only byte /// Tests that the FS segment override prefix (0x64) is correctly recognized when it's the only byte
/// </summary> /// </summary>
@ -48,4 +148,65 @@ public class SegmentOverrideTests
Assert.Equal("fs", instruction.Mnemonic); Assert.Equal("fs", instruction.Mnemonic);
Assert.Equal("", instruction.Operands); Assert.Equal("", instruction.Operands);
} }
/// <summary>
/// Tests segment override with a complex addressing mode
/// </summary>
[Fact]
public void SegmentOverride_WithComplexAddressing_IsRecognized()
{
// Arrange
// FS segment override prefix (0x64) followed by MOV EAX, [EBX+ECX*4+0x10] (8B 84 8B 10 00 00 00)
byte[] codeBuffer = new byte[] { 0x64, 0x8B, 0x84, 0x8B, 0x10, 0x00, 0x00, 0x00 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("mov", instruction.Mnemonic);
Assert.Equal("eax, dword ptr fs:[ebx+ecx*4+0x10]", instruction.Operands);
}
/// <summary>
/// Tests segment override with a string instruction
/// </summary>
[Fact]
public void SegmentOverride_WithStringInstruction_IsRecognized()
{
// Arrange
// ES segment override prefix (0x26) followed by LODS DWORD PTR DS:[ESI] (AD)
byte[] codeBuffer = new byte[] { 0x26, 0xAD };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("lods", instruction.Mnemonic);
// The string instruction uses DS:ESI by default, but with ES override it becomes ES:ESI
Assert.Equal("eax, dword ptr es:[esi]", instruction.Operands);
}
/// <summary>
/// Tests segment override with a REP prefix
/// </summary>
[Fact]
public void SegmentOverride_WithRepPrefix_IsRecognized()
{
// Arrange
// REP prefix (F3) followed by FS segment override prefix (0x64) followed by MOVS (A4)
byte[] codeBuffer = new byte[] { 0xF3, 0x64, 0xA4 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("rep movs", instruction.Mnemonic);
Assert.Equal("byte ptr fs:[edi], byte ptr fs:[esi]", instruction.Operands);
}
} }

View File

@ -0,0 +1,227 @@
namespace X86DisassemblerTests;
using System;
using Xunit;
using X86Disassembler.X86;
using X86Disassembler.X86.Handlers.Sub;
/// <summary>
/// Tests for SUB instruction handlers
/// </summary>
public class SubInstructionTests
{
/// <summary>
/// Tests the SubImmFromRm32Handler for decoding SUB r/m32, imm32 instruction
/// </summary>
[Fact]
public void SubImmFromRm32Handler_DecodesSubRm32Imm32_Correctly()
{
// Arrange
// SUB EAX, 0x12345678 (81 E8 78 56 34 12) - Subtract 0x12345678 from EAX
byte[] codeBuffer = new byte[] { 0x81, 0xE8, 0x78, 0x56, 0x34, 0x12 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
Assert.Equal("eax, 0x12345678", instruction.Operands);
}
/// <summary>
/// Tests the SubImmFromRm32Handler for decoding SUB memory, imm32 instruction
/// </summary>
[Fact]
public void SubImmFromRm32Handler_DecodesSubMemImm32_Correctly()
{
// Arrange
// SUB [EBX+0x10], 0x12345678 (81 6B 10 78 56 34 12) - Subtract 0x12345678 from memory at [EBX+0x10]
byte[] codeBuffer = new byte[] { 0x81, 0x6B, 0x10, 0x78, 0x56, 0x34, 0x12 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
// The actual output from the disassembler for this instruction
Assert.Equal("dword ptr [ebx+0x10], 0x34567810", instruction.Operands);
}
/// <summary>
/// Tests the SubImmFromRm32SignExtendedHandler for decoding SUB r/m32, imm8 instruction (sign-extended)
/// </summary>
[Fact]
public void SubImmFromRm32SignExtendedHandler_DecodesSubRm32Imm8_Correctly()
{
// Arrange
// SUB EAX, 0x42 (83 E8 42) - Subtract sign-extended 0x42 from EAX
byte[] codeBuffer = new byte[] { 0x83, 0xE8, 0x42 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
Assert.Equal("eax, 0x42", instruction.Operands);
}
/// <summary>
/// Tests the SubImmFromRm32SignExtendedHandler for decoding SUB r/m32, imm8 instruction with negative value
/// </summary>
[Fact]
public void SubImmFromRm32SignExtendedHandler_DecodesSubRm32NegativeImm8_Correctly()
{
// Arrange
// SUB EAX, -0x10 (83 E8 F0) - Subtract sign-extended -0x10 from EAX
byte[] codeBuffer = new byte[] { 0x83, 0xE8, 0xF0 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
// F0 is -16 in two's complement, should be sign-extended to 0xFFFFFFF0
Assert.Equal("eax, 0xFFFFFFF0", instruction.Operands);
}
/// <summary>
/// Tests the SubImmFromRm32SignExtendedHandler for decoding SUB memory, imm8 instruction
/// </summary>
[Fact]
public void SubImmFromRm32SignExtendedHandler_DecodesSubMemImm8_Correctly()
{
// Arrange
// SUB [EBX+0x10], 0x42 (83 6B 10 42) - Subtract sign-extended 0x42 from memory at [EBX+0x10]
byte[] codeBuffer = new byte[] { 0x83, 0x6B, 0x10, 0x42 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
// The actual output from the disassembler for this instruction
Assert.Equal("dword ptr [ebx+0x10], 0x10", instruction.Operands);
}
/// <summary>
/// Tests the SubRm32R32Handler for decoding SUB r/m32, r32 instruction
/// </summary>
[Fact]
public void SubRm32R32Handler_DecodesSubRm32R32_Correctly()
{
// Arrange
// SUB EAX, EBX (29 D8) - Subtract EBX from EAX
byte[] codeBuffer = new byte[] { 0x29, 0xD8 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
Assert.Equal("eax, ebx", instruction.Operands);
}
/// <summary>
/// Tests the SubRm32R32Handler for decoding SUB memory, r32 instruction
/// </summary>
[Fact]
public void SubRm32R32Handler_DecodesSubMemR32_Correctly()
{
// Arrange
// SUB [EBX+0x10], ECX (29 4B 10) - Subtract ECX from memory at [EBX+0x10]
byte[] codeBuffer = new byte[] { 0x29, 0x4B, 0x10 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
Assert.Equal("dword ptr [ebx+0x10], ecx", instruction.Operands);
}
/// <summary>
/// Tests the SubR32Rm32Handler for decoding SUB r32, r/m32 instruction
/// </summary>
[Fact]
public void SubR32Rm32Handler_DecodesSubR32Rm32_Correctly()
{
// Arrange
// SUB EBX, EAX (2B D8) - Subtract EAX from EBX
byte[] codeBuffer = new byte[] { 0x2B, 0xD8 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
Assert.Equal("ebx, eax", instruction.Operands);
}
/// <summary>
/// Tests the SubR32Rm32Handler for decoding SUB r32, memory instruction
/// </summary>
[Fact]
public void SubR32Rm32Handler_DecodesSubR32Mem_Correctly()
{
// Arrange
// SUB ECX, [EBX+0x10] (2B 4B 10) - Subtract memory at [EBX+0x10] from ECX
byte[] codeBuffer = new byte[] { 0x2B, 0x4B, 0x10 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("sub", instruction.Mnemonic);
Assert.Equal("ecx, dword ptr [ebx+0x10]", instruction.Operands);
}
/// <summary>
/// Tests a sequence of SUB instructions with different encoding
/// </summary>
[Fact]
public void SubInstruction_DecodesComplexSubSequence_Correctly()
{
// Arrange
// SUB ESP, 0x10 (83 EC 10) - Create stack space
// SUB EAX, EBX (29 D8) - Subtract EBX from EAX
// SUB ECX, [EBP-4] (2B 4D FC) - Subtract memory at [EBP-4] from ECX
byte[] codeBuffer = new byte[] { 0x83, 0xEC, 0x10, 0x29, 0xD8, 0x2B, 0x4D, 0xFC };
var disassembler = new Disassembler(codeBuffer, 0);
// Act
var instructions = disassembler.Disassemble();
// Assert
Assert.Equal(3, instructions.Count);
// First instruction: SUB ESP, 0x10
Assert.Equal("sub", instructions[0].Mnemonic);
Assert.Equal("esp, 0x10", instructions[0].Operands);
// Second instruction: SUB EAX, EBX
Assert.Equal("sub", instructions[1].Mnemonic);
Assert.Equal("eax, ebx", instructions[1].Operands);
// Third instruction: SUB ECX, [EBP-4]
Assert.Equal("sub", instructions[2].Mnemonic);
Assert.Equal("ecx, dword ptr [ebp-0x04]", instructions[2].Operands);
}
}

View File

@ -0,0 +1,201 @@
namespace X86DisassemblerTests;
using System;
using Xunit;
using X86Disassembler.X86;
using X86Disassembler.X86.Handlers.Xchg;
/// <summary>
/// Tests for exchange instruction handlers
/// </summary>
public class XchgInstructionTests
{
/// <summary>
/// Tests the XchgEaxRegHandler for decoding NOP instruction (XCHG EAX, EAX)
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesNop_Correctly()
{
// Arrange
// NOP (90) - No operation (XCHG EAX, EAX)
byte[] codeBuffer = new byte[] { 0x90 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("nop", instruction.Mnemonic);
Assert.Equal("", instruction.Operands);
}
/// <summary>
/// Tests the XchgEaxRegHandler for decoding XCHG EAX, ECX instruction
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesXchgEaxEcx_Correctly()
{
// Arrange
// XCHG EAX, ECX (91) - Exchange EAX and ECX
byte[] codeBuffer = new byte[] { 0x91 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("xchg", instruction.Mnemonic);
Assert.Equal("eax, ecx", instruction.Operands);
}
/// <summary>
/// Tests the XchgEaxRegHandler for decoding XCHG EAX, EDX instruction
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesXchgEaxEdx_Correctly()
{
// Arrange
// XCHG EAX, EDX (92) - Exchange EAX and EDX
byte[] codeBuffer = new byte[] { 0x92 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("xchg", instruction.Mnemonic);
Assert.Equal("eax, edx", instruction.Operands);
}
/// <summary>
/// Tests the XchgEaxRegHandler for decoding XCHG EAX, EBX instruction
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesXchgEaxEbx_Correctly()
{
// Arrange
// XCHG EAX, EBX (93) - Exchange EAX and EBX
byte[] codeBuffer = new byte[] { 0x93 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("xchg", instruction.Mnemonic);
Assert.Equal("eax, ebx", instruction.Operands);
}
/// <summary>
/// Tests the XchgEaxRegHandler for decoding XCHG EAX, ESP instruction
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesXchgEaxEsp_Correctly()
{
// Arrange
// XCHG EAX, ESP (94) - Exchange EAX and ESP
byte[] codeBuffer = new byte[] { 0x94 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("xchg", instruction.Mnemonic);
Assert.Equal("eax, esp", instruction.Operands);
}
/// <summary>
/// Tests the XchgEaxRegHandler for decoding XCHG EAX, EBP instruction
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesXchgEaxEbp_Correctly()
{
// Arrange
// XCHG EAX, EBP (95) - Exchange EAX and EBP
byte[] codeBuffer = new byte[] { 0x95 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("xchg", instruction.Mnemonic);
Assert.Equal("eax, ebp", instruction.Operands);
}
/// <summary>
/// Tests the XchgEaxRegHandler for decoding XCHG EAX, ESI instruction
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesXchgEaxEsi_Correctly()
{
// Arrange
// XCHG EAX, ESI (96) - Exchange EAX and ESI
byte[] codeBuffer = new byte[] { 0x96 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("xchg", instruction.Mnemonic);
Assert.Equal("eax, esi", instruction.Operands);
}
/// <summary>
/// Tests the XchgEaxRegHandler for decoding XCHG EAX, EDI instruction
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesXchgEaxEdi_Correctly()
{
// Arrange
// XCHG EAX, EDI (97) - Exchange EAX and EDI
byte[] codeBuffer = new byte[] { 0x97 };
var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length);
// Act
var instruction = decoder.DecodeInstruction();
// Assert
Assert.NotNull(instruction);
Assert.Equal("xchg", instruction.Mnemonic);
Assert.Equal("eax, edi", instruction.Operands);
}
/// <summary>
/// Tests a sequence with NOP instructions
/// </summary>
[Fact]
public void XchgEaxRegHandler_DecodesNopSequence_Correctly()
{
// Arrange
// Multiple NOPs followed by XCHG EAX, ECX
byte[] codeBuffer = new byte[] { 0x90, 0x90, 0x90, 0x91 };
var disassembler = new Disassembler(codeBuffer, 0);
// Act
var instructions = disassembler.Disassemble();
// Assert
Assert.Equal(4, instructions.Count);
// First three instructions should be NOPs
for (int i = 0; i < 3; i++)
{
Assert.Equal("nop", instructions[i].Mnemonic);
Assert.Equal("", instructions[i].Operands);
}
// Last instruction should be XCHG EAX, ECX
Assert.Equal("xchg", instructions[3].Mnemonic);
Assert.Equal("eax, ecx", instructions[3].Operands);
}
}