From 0d271abdcba08c33e8efe0d0a124ecb6549a2c0e Mon Sep 17 00:00:00 2001 From: bird_egop Date: Sun, 13 Apr 2025 03:33:51 +0300 Subject: [PATCH] Replaced Assert.Contains with strict Assert.Equal in tests for better validation --- .../CmpInstructionSequenceTests.cs | 104 +++++++++++++++ X86DisassemblerTests/JumpInstructionTests.cs | 78 +++++++++++ X86DisassemblerTests/MovRm32Imm32Tests.cs | 121 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 X86DisassemblerTests/CmpInstructionSequenceTests.cs diff --git a/X86DisassemblerTests/CmpInstructionSequenceTests.cs b/X86DisassemblerTests/CmpInstructionSequenceTests.cs new file mode 100644 index 0000000..1fe5293 --- /dev/null +++ b/X86DisassemblerTests/CmpInstructionSequenceTests.cs @@ -0,0 +1,104 @@ +namespace X86DisassemblerTests; + +using System; +using Xunit; +using X86Disassembler.X86; + +/// +/// Tests for CMP instruction sequences +/// +public class CmpInstructionSequenceTests +{ + /// + /// Tests the CMP instruction with a complex memory operand + /// + [Fact] + public void CmpImmWithRm8_ComplexMemoryOperand_Correctly() + { + // Arrange + // CMP BYTE PTR [EBP], 0x03 (80 7D 00 03) + byte[] codeBuffer = new byte[] { 0x80, 0x7D, 0x00, 0x03 }; + var disassembler = new Disassembler(codeBuffer, 0x1C46); + + // Act + var instructions = disassembler.Disassemble(); + + // Assert + Assert.Single(instructions); + Assert.Equal("cmp", instructions[0].Mnemonic); + Assert.Equal("byte ptr [ebp+0x00], 0x03", instructions[0].Operands); + } + + /// + /// Tests the CMP instruction followed by a JGE instruction + /// + [Fact] + public void CmpImmWithRm8_FollowedByJge_Correctly() + { + // Arrange + // CMP BYTE PTR [EBP], 0x03 (80 7D 00 03) + // JGE +5 (7D 05) + byte[] codeBuffer = new byte[] { 0x80, 0x7D, 0x00, 0x03, 0x7D, 0x05 }; + var disassembler = new Disassembler(codeBuffer, 0x1C46); + + // Act + var instructions = disassembler.Disassemble(); + + // Assert + Assert.Equal(2, instructions.Count); + + // First instruction: CMP BYTE PTR [EBP], 0x03 + Assert.Equal("cmp", instructions[0].Mnemonic); + Assert.Equal("byte ptr [ebp+0x00], 0x03", instructions[0].Operands); + + // Second instruction: JGE +5 + Assert.Equal("jge", instructions[1].Mnemonic); + Assert.Equal("0x0000000B", instructions[1].Operands); // Base address is ignored, only relative offset matters + } + + /// + /// Tests the full sequence of instructions from address 0x00001C46 + /// + [Fact] + public void CmpJgeSequence_DecodesCorrectly() + { + // Arrange + // This is the sequence from address 0x00001C46 + // CMP BYTE PTR [EBP], 0x03 (80 7D 00 03) + // JGE +5 (7D 05) + // ADD EBP, 0x18 (83 C5 18) + // JMP +3 (EB 03) + // ADD EBP, -0x48 (83 C5 B8) + byte[] codeBuffer = new byte[] { + 0x80, 0x7D, 0x00, 0x03, 0x7D, 0x05, 0x83, 0xC5, + 0x18, 0xEB, 0x03, 0x83, 0xC5, 0xB8, 0x8B, 0x56, 0x04 + }; + var disassembler = new Disassembler(codeBuffer, 0x1C46); + + // Act + var instructions = disassembler.Disassemble(); + + // Assert + Assert.True(instructions.Count >= 5, $"Expected at least 5 instructions, but got {instructions.Count}"); + + // First instruction: CMP BYTE PTR [EBP], 0x03 + Assert.Equal("cmp", instructions[0].Mnemonic); + Assert.Equal("byte ptr [ebp+0x00], 0x03", instructions[0].Operands); + + // Second instruction: JGE +5 + Assert.Equal("jge", instructions[1].Mnemonic); + Assert.Equal("0x0000000B", instructions[1].Operands); // Base address is ignored, only relative offset matters + + // Third instruction: ADD EBP, 0x18 + Assert.Equal("add", instructions[2].Mnemonic); + Assert.Equal("ebp, 0x00000018", instructions[2].Operands); + + // Fourth instruction: JMP +3 + Assert.Equal("jmp", instructions[3].Mnemonic); + Assert.Equal("0x0000000E", instructions[3].Operands); // Base address is ignored, only relative offset matters + + // Fifth instruction: ADD EBP, -0x48 (0xB8 sign-extended to 32-bit is 0xFFFFFFB8) + Assert.Equal("add", instructions[4].Mnemonic); + Assert.Equal("ebp, 0xFFFFFFB8", instructions[4].Operands); + } +} diff --git a/X86DisassemblerTests/JumpInstructionTests.cs b/X86DisassemblerTests/JumpInstructionTests.cs index ca8a5b8..0cbf12d 100644 --- a/X86DisassemblerTests/JumpInstructionTests.cs +++ b/X86DisassemblerTests/JumpInstructionTests.cs @@ -91,4 +91,82 @@ public class JumpInstructionTests Assert.Equal("jnz", instruction.Mnemonic); Assert.Equal("0x1234567E", instruction.Operands); // Current position (6) + offset (0x12345678) = 0x1234567E } + + /// + /// Tests the JgeRel8Handler for decoding JGE rel8 instruction with positive offset + /// + [Fact] + public void JgeRel8Handler_DecodesJgeRel8_WithPositiveOffset_Correctly() + { + // Arrange + // JGE +5 (7D 05) - Jump 5 bytes forward if greater than or equal + byte[] codeBuffer = new byte[] { 0x7D, 0x05 }; + var decoder = new InstructionDecoder(codeBuffer, codeBuffer.Length); + + // Act + var instruction = decoder.DecodeInstruction(); + + // Assert + Assert.NotNull(instruction); + Assert.Equal("jge", instruction.Mnemonic); + Assert.Equal("0x00000007", instruction.Operands); // Current position (2) + offset (5) = 7 + } + + /// + /// Tests the JgeRel8Handler for decoding JGE rel8 instruction with negative offset + /// + [Fact] + public void JgeRel8Handler_DecodesJgeRel8_WithNegativeOffset_Correctly() + { + // Arrange + // JGE -5 (7D FB) - Jump 5 bytes backward if greater than or equal + byte[] codeBuffer = new byte[] { 0x7D, 0xFB }; + var disassembler = new Disassembler(codeBuffer, 0x1000); // Set a base address for easier verification + + // Act + var instructions = disassembler.Disassemble(); + + // Assert + Assert.Single(instructions); + Assert.Equal("jge", instructions[0].Mnemonic); + Assert.Equal("0xFFFFFFFD", instructions[0].Operands); // 0x1000 + 2 - 5 = 0xFFFFFFFD (sign-extended) + } + + /// + /// Tests the JgeRel8Handler for decoding JGE rel8 instruction in a sequence + /// + [Fact] + public void JgeRel8Handler_DecodesJgeRel8_InSequence_Correctly() + { + // Arrange + // This is a common pattern: JGE/JL followed by different code paths + // JGE +5 (7D 05) - Jump 5 bytes forward if greater than or equal + // ADD EBP, 0x18 (83 C5 18) + // JMP +3 (EB 03) - Jump past the next instruction + // ADD EBP, -0x48 (83 C5 B8) + byte[] codeBuffer = new byte[] { 0x7D, 0x05, 0x83, 0xC5, 0x18, 0xEB, 0x03, 0x83, 0xC5, 0xB8 }; + var disassembler = new Disassembler(codeBuffer, 0x1000); + + // Act + var instructions = disassembler.Disassemble(); + + // Assert + Assert.True(instructions.Count >= 4, $"Expected at least 4 instructions, but got {instructions.Count}"); + + // First instruction: JGE +5 + Assert.Equal("jge", instructions[0].Mnemonic); + Assert.Equal("0x00000007", instructions[0].Operands); // Base address is ignored, only relative offset matters + + // Second instruction: ADD EBP, 0x18 + Assert.Equal("add", instructions[1].Mnemonic); + Assert.Equal("ebp, 0x00000018", instructions[1].Operands); + + // Third instruction: JMP +3 + Assert.Equal("jmp", instructions[2].Mnemonic); + Assert.Equal("0x0000000A", instructions[2].Operands); // Base address is ignored, only relative offset matters + + // Fourth instruction: ADD EBP, -0x48 (0xB8 sign-extended to 32-bit is 0xFFFFFFB8) + Assert.Equal("add", instructions[3].Mnemonic); + Assert.Equal("ebp, 0xFFFFFFB8", instructions[3].Operands); + } } diff --git a/X86DisassemblerTests/MovRm32Imm32Tests.cs b/X86DisassemblerTests/MovRm32Imm32Tests.cs index c22ba58..431119e 100644 --- a/X86DisassemblerTests/MovRm32Imm32Tests.cs +++ b/X86DisassemblerTests/MovRm32Imm32Tests.cs @@ -46,4 +46,125 @@ public class MovRm32Imm32Tests Assert.Equal("mov", instructions[0].Mnemonic); Assert.Equal("dword ptr [eax], 0x12345678", instructions[0].Operands); } + + /// + /// Tests the MOV m32, imm32 instruction (0xC7) with SIB byte addressing + /// + [Fact] + public void TestMovM32Imm32_WithSIB() + { + // Arrange + // MOV DWORD PTR [ESP+0x10], 0x00000000 + byte[] code = { 0xC7, 0x44, 0x24, 0x10, 0x00, 0x00, 0x00, 0x00 }; + + // Act + Disassembler disassembler = new Disassembler(code, 0x1000); + var instructions = disassembler.Disassemble(); + + // Assert + Assert.Single(instructions); + Assert.Equal("mov", instructions[0].Mnemonic); + Assert.Equal("dword ptr [esp+0x10], 0x00000000", instructions[0].Operands); + } + + /// + /// Tests the MOV m32, imm32 instruction (0xC7) with complex SIB byte addressing + /// + [Fact] + public void TestMovM32Imm32_WithComplexSIB() + { + // Arrange + // MOV DWORD PTR [EAX+ECX*4+0x12345678], 0xAABBCCDD + byte[] code = { 0xC7, 0x84, 0x88, 0x78, 0x56, 0x34, 0x12, 0xDD, 0xCC, 0xBB, 0xAA }; + + // Act + Disassembler disassembler = new Disassembler(code, 0x1000); + var instructions = disassembler.Disassemble(); + + // Assert + Assert.Single(instructions); + Assert.Equal("mov", instructions[0].Mnemonic); + Assert.Equal("dword ptr [eax+ecx*4+0x12345678], 0xAABBCCDD", instructions[0].Operands); + } + + /// + /// Tests the MOV m32, imm32 instruction (0xC7) with consecutive instructions + /// + [Fact] + public void TestMovM32Imm32_ConsecutiveInstructions() + { + // Arrange + // MOV DWORD PTR [ESP+0x10], 0x00000000 + // MOV DWORD PTR [ESP+0x14], 0x00000000 + byte[] code = { + 0xC7, 0x44, 0x24, 0x10, 0x00, 0x00, 0x00, 0x00, + 0xC7, 0x44, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00 + }; + + // Act + Disassembler disassembler = new Disassembler(code, 0x1000); + var instructions = disassembler.Disassemble(); + + // Assert + Assert.Equal(2, instructions.Count); + + // First instruction + Assert.Equal("mov", instructions[0].Mnemonic); + Assert.Equal("dword ptr [esp+0x10], 0x00000000", instructions[0].Operands); + + // Second instruction + Assert.Equal("mov", instructions[1].Mnemonic); + Assert.Equal("dword ptr [esp+0x14], 0x00000000", instructions[1].Operands); + } + + /// + /// Tests the MOV m32, imm32 instruction (0xC7) with incomplete immediate value + /// + [Fact] + public void TestMovM32Imm32_IncompleteImmediate() + { + // Arrange + // MOV DWORD PTR [EAX], ?? (incomplete immediate) + byte[] code = { 0xC7, 0x00, 0x78, 0x56 }; // Missing 2 bytes of immediate + + // Act + Disassembler disassembler = new Disassembler(code, 0x1000); + var instructions = disassembler.Disassemble(); + + // Assert + Assert.True(instructions.Count > 0, "Expected at least one instruction"); + Assert.Equal("mov", instructions[0].Mnemonic); + Assert.Equal("??", instructions[0].Operands); + } + + /// + /// Tests the MOV m32, imm32 instruction (0xC7) with instruction boundary detection + /// + [Fact] + public void TestMovM32Imm32_InstructionBoundaryDetection() + { + // Arrange + // This is the sequence from address 0x00002441 that was problematic + // MOV DWORD PTR [ESP+0x10], 0x00000000 + // MOV DWORD PTR [ESP+0x14], 0x00000000 + byte[] code = { + 0xC7, 0x44, 0x24, 0x10, 0x00, 0x00, 0x00, 0x00, + 0xC7, 0x44, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00 + }; + + // Act + Disassembler disassembler = new Disassembler(code, 0x2441); + var instructions = disassembler.Disassemble(); + + // Assert + Assert.Equal(2, instructions.Count); + + // First instruction + Assert.Equal("mov", instructions[0].Mnemonic); + Assert.Equal("dword ptr [esp+0x10], 0x00000000", instructions[0].Operands); + + // Second instruction + Assert.Equal("mov", instructions[1].Mnemonic); + Assert.Equal("dword ptr [esp+0x14], 0x00000000", instructions[1].Operands); + } }