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); + } +}