diff --git a/X86Disassembler/X86/Handlers/InstructionHandlerFactory.cs b/X86Disassembler/X86/Handlers/InstructionHandlerFactory.cs
index 63df69d..39cbe58 100644
--- a/X86Disassembler/X86/Handlers/InstructionHandlerFactory.cs
+++ b/X86Disassembler/X86/Handlers/InstructionHandlerFactory.cs
@@ -77,6 +77,7 @@ public class InstructionHandlerFactory
RegisterFloatingPointHandlers();
RegisterStringHandlers();
RegisterMovHandlers();
+ RegisterSubHandlers(); // Register SUB handlers
}
///
@@ -367,6 +368,16 @@ public class InstructionHandlerFactory
_handlers.Add(new AndEaxImmHandler(_codeBuffer, _decoder, _length));
}
+ ///
+ /// Registers all SUB instruction handlers
+ ///
+ private void RegisterSubHandlers()
+ {
+ // Add SUB register/memory handlers
+ _handlers.Add(new Sub.SubRm32R32Handler(_codeBuffer, _decoder, _length));
+ _handlers.Add(new Sub.SubR32Rm32Handler(_codeBuffer, _decoder, _length));
+ }
+
///
/// Gets the handler that can decode the given opcode
///
diff --git a/X86Disassembler/X86/Handlers/Sub/SubImmFromRm32Handler.cs b/X86Disassembler/X86/Handlers/Sub/SubImmFromRm32Handler.cs
index ddb8494..36f15a5 100644
--- a/X86Disassembler/X86/Handlers/Sub/SubImmFromRm32Handler.cs
+++ b/X86Disassembler/X86/Handlers/Sub/SubImmFromRm32Handler.cs
@@ -73,7 +73,16 @@ public class SubImmFromRm32Handler : InstructionHandler
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);
// Set the operands
diff --git a/X86Disassembler/X86/Handlers/Sub/SubImmFromRm32SignExtendedHandler.cs b/X86Disassembler/X86/Handlers/Sub/SubImmFromRm32SignExtendedHandler.cs
index 23212d5..0d77a5c 100644
--- a/X86Disassembler/X86/Handlers/Sub/SubImmFromRm32SignExtendedHandler.cs
+++ b/X86Disassembler/X86/Handlers/Sub/SubImmFromRm32SignExtendedHandler.cs
@@ -73,12 +73,26 @@ public class SubImmFromRm32SignExtendedHandler : InstructionHandler
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++];
+ int imm32 = imm8; // Automatic sign extension from sbyte to int
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
- instruction.Operands = $"{destOperand}, 0x{(uint)imm8:X2}";
+ instruction.Operands = $"{destOperand}, {immStr}";
return true;
}
diff --git a/X86Disassembler/X86/Handlers/Sub/SubR32Rm32Handler.cs b/X86Disassembler/X86/Handlers/Sub/SubR32Rm32Handler.cs
new file mode 100644
index 0000000..d8f1598
--- /dev/null
+++ b/X86Disassembler/X86/Handlers/Sub/SubR32Rm32Handler.cs
@@ -0,0 +1,73 @@
+namespace X86Disassembler.X86.Handlers.Sub;
+
+///
+/// Handler for SUB r32, r/m32 instruction (0x2B)
+///
+public class SubR32Rm32Handler : InstructionHandler
+{
+ ///
+ /// Initializes a new instance of the SubR32Rm32Handler class
+ ///
+ /// The buffer containing the code to decode
+ /// The instruction decoder that owns this handler
+ /// The length of the buffer
+ public SubR32Rm32Handler(byte[] codeBuffer, InstructionDecoder decoder, int length)
+ : base(codeBuffer, decoder, length)
+ {
+ }
+
+ ///
+ /// Checks if this handler can decode the given opcode
+ ///
+ /// The opcode to check
+ /// True if this handler can decode the opcode
+ public override bool CanHandle(byte opcode)
+ {
+ return opcode == 0x2B;
+ }
+
+ ///
+ /// Decodes a SUB r32, r/m32 instruction
+ ///
+ /// The opcode of the instruction
+ /// The instruction object to populate
+ /// True if the instruction was successfully decoded
+ 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;
+ }
+}
diff --git a/X86Disassembler/X86/Handlers/Sub/SubRm32R32Handler.cs b/X86Disassembler/X86/Handlers/Sub/SubRm32R32Handler.cs
new file mode 100644
index 0000000..e47688c
--- /dev/null
+++ b/X86Disassembler/X86/Handlers/Sub/SubRm32R32Handler.cs
@@ -0,0 +1,73 @@
+namespace X86Disassembler.X86.Handlers.Sub;
+
+///
+/// Handler for SUB r/m32, r32 instruction (0x29)
+///
+public class SubRm32R32Handler : InstructionHandler
+{
+ ///
+ /// Initializes a new instance of the SubRm32R32Handler class
+ ///
+ /// The buffer containing the code to decode
+ /// The instruction decoder that owns this handler
+ /// The length of the buffer
+ public SubRm32R32Handler(byte[] codeBuffer, InstructionDecoder decoder, int length)
+ : base(codeBuffer, decoder, length)
+ {
+ }
+
+ ///
+ /// Checks if this handler can decode the given opcode
+ ///
+ /// The opcode to check
+ /// True if this handler can decode the opcode
+ public override bool CanHandle(byte opcode)
+ {
+ return opcode == 0x29;
+ }
+
+ ///
+ /// Decodes a SUB r/m32, r32 instruction
+ ///
+ /// The opcode of the instruction
+ /// The instruction object to populate
+ /// True if the instruction was successfully decoded
+ 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;
+ }
+}
diff --git a/X86DisassemblerTests/PushPopInstructionTests.cs b/X86DisassemblerTests/PushPopInstructionTests.cs
new file mode 100644
index 0000000..585a9ab
--- /dev/null
+++ b/X86DisassemblerTests/PushPopInstructionTests.cs
@@ -0,0 +1,187 @@
+namespace X86DisassemblerTests;
+
+using System;
+using Xunit;
+using X86Disassembler.X86;
+using X86Disassembler.X86.Handlers.Push;
+using X86Disassembler.X86.Handlers.Pop;
+
+///
+/// Tests for push and pop instruction handlers
+///
+public class PushPopInstructionTests
+{
+ ///
+ /// Tests the PushRegHandler for decoding PUSH r32 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the PushRegHandler for decoding PUSH r32 instruction with different register
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the PushImm8Handler for decoding PUSH imm8 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the PushImm32Handler for decoding PUSH imm32 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the PopRegHandler for decoding POP r32 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the PopRegHandler for decoding POP r32 instruction with different register
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests a common function prologue sequence (PUSH EBP, MOV EBP, ESP)
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests a common function epilogue sequence (POP EBP, RET)
+ ///
+ [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);
+ }
+}
diff --git a/X86DisassemblerTests/SegmentOverrideTests.cs b/X86DisassemblerTests/SegmentOverrideTests.cs
index bfef06a..649f44b 100644
--- a/X86DisassemblerTests/SegmentOverrideTests.cs
+++ b/X86DisassemblerTests/SegmentOverrideTests.cs
@@ -9,6 +9,66 @@ using X86Disassembler.X86;
///
public class SegmentOverrideTests
{
+ ///
+ /// Tests that the CS segment override prefix (0x2E) is correctly recognized
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests that the DS segment override prefix (0x3E) is correctly recognized
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests that the ES segment override prefix (0x26) is correctly recognized
+ ///
+ [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);
+ }
+
///
/// Tests that the FS segment override prefix (0x64) is correctly recognized
///
@@ -29,6 +89,46 @@ public class SegmentOverrideTests
Assert.Equal("dword ptr fs:[0x00000000], esp", instruction.Operands);
}
+ ///
+ /// Tests that the GS segment override prefix (0x65) is correctly recognized
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests that the SS segment override prefix (0x36) is correctly recognized
+ ///
+ [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);
+ }
+
///
/// Tests that the FS segment override prefix (0x64) is correctly recognized when it's the only byte
///
@@ -48,4 +148,65 @@ public class SegmentOverrideTests
Assert.Equal("fs", instruction.Mnemonic);
Assert.Equal("", instruction.Operands);
}
+
+ ///
+ /// Tests segment override with a complex addressing mode
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests segment override with a string instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests segment override with a REP prefix
+ ///
+ [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);
+ }
}
diff --git a/X86DisassemblerTests/SubInstructionTests.cs b/X86DisassemblerTests/SubInstructionTests.cs
new file mode 100644
index 0000000..2837896
--- /dev/null
+++ b/X86DisassemblerTests/SubInstructionTests.cs
@@ -0,0 +1,227 @@
+namespace X86DisassemblerTests;
+
+using System;
+using Xunit;
+using X86Disassembler.X86;
+using X86Disassembler.X86.Handlers.Sub;
+
+///
+/// Tests for SUB instruction handlers
+///
+public class SubInstructionTests
+{
+ ///
+ /// Tests the SubImmFromRm32Handler for decoding SUB r/m32, imm32 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the SubImmFromRm32Handler for decoding SUB memory, imm32 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the SubImmFromRm32SignExtendedHandler for decoding SUB r/m32, imm8 instruction (sign-extended)
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the SubImmFromRm32SignExtendedHandler for decoding SUB r/m32, imm8 instruction with negative value
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the SubImmFromRm32SignExtendedHandler for decoding SUB memory, imm8 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the SubRm32R32Handler for decoding SUB r/m32, r32 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the SubRm32R32Handler for decoding SUB memory, r32 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the SubR32Rm32Handler for decoding SUB r32, r/m32 instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the SubR32Rm32Handler for decoding SUB r32, memory instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests a sequence of SUB instructions with different encoding
+ ///
+ [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);
+ }
+}
diff --git a/X86DisassemblerTests/XchgInstructionTests.cs b/X86DisassemblerTests/XchgInstructionTests.cs
new file mode 100644
index 0000000..4fc19d2
--- /dev/null
+++ b/X86DisassemblerTests/XchgInstructionTests.cs
@@ -0,0 +1,201 @@
+namespace X86DisassemblerTests;
+
+using System;
+using Xunit;
+using X86Disassembler.X86;
+using X86Disassembler.X86.Handlers.Xchg;
+
+///
+/// Tests for exchange instruction handlers
+///
+public class XchgInstructionTests
+{
+ ///
+ /// Tests the XchgEaxRegHandler for decoding NOP instruction (XCHG EAX, EAX)
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the XchgEaxRegHandler for decoding XCHG EAX, ECX instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the XchgEaxRegHandler for decoding XCHG EAX, EDX instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the XchgEaxRegHandler for decoding XCHG EAX, EBX instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the XchgEaxRegHandler for decoding XCHG EAX, ESP instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the XchgEaxRegHandler for decoding XCHG EAX, EBP instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the XchgEaxRegHandler for decoding XCHG EAX, ESI instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests the XchgEaxRegHandler for decoding XCHG EAX, EDI instruction
+ ///
+ [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);
+ }
+
+ ///
+ /// Tests a sequence with NOP instructions
+ ///
+ [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);
+ }
+}