diff --git a/X86Disassembler/X86/Handlers/Group3Handler.cs b/X86Disassembler/X86/Handlers/Group3Handler.cs new file mode 100644 index 0000000..219e1f9 --- /dev/null +++ b/X86Disassembler/X86/Handlers/Group3Handler.cs @@ -0,0 +1,99 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for Group 3 instructions (TEST, NOT, NEG, MUL, IMUL, DIV, IDIV) +/// +public class Group3Handler : InstructionHandler +{ + /// + /// Initializes a new instance of the Group3Handler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public Group3Handler(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 OpcodeMap.IsGroup3Opcode(opcode); + } + + /// + /// Decodes a Group 3 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 + var (mod, reg, rm, destOperand) = ModRMDecoder.ReadModRM(); + + // Determine the operation based on reg field + instruction.Mnemonic = OpcodeMap.Group3Operations[reg]; + + // For TEST instruction (reg = 0), we need to read an immediate value + if (reg == 0) // TEST + { + position = Decoder.GetPosition(); + string immOperand; + + switch (opcode) + { + case 0xF6: // 8-bit TEST + if (position < Length) + { + byte imm8 = CodeBuffer[position]; + Decoder.SetPosition(position + 1); + immOperand = $"0x{imm8:X2}"; + } + else + { + immOperand = "???"; + } + break; + + case 0xF7: // 32-bit TEST + if (position + 3 < Length) + { + uint imm32 = BitConverter.ToUInt32(CodeBuffer, position); + Decoder.SetPosition(position + 4); + immOperand = $"0x{imm32:X8}"; + } + else + { + immOperand = "???"; + } + break; + + default: + return false; + } + + // Set the operands + instruction.Operands = $"{destOperand}, {immOperand}"; + } + else + { + // For other Group 3 instructions (NOT, NEG, MUL, etc.), there's only one operand + instruction.Operands = destOperand; + } + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/TestHandler.cs b/X86Disassembler/X86/Handlers/TestHandler.cs new file mode 100644 index 0000000..37b22f3 --- /dev/null +++ b/X86Disassembler/X86/Handlers/TestHandler.cs @@ -0,0 +1,101 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for TEST instructions +/// +public class TestHandler : InstructionHandler +{ + /// + /// Initializes a new instance of the TestHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public TestHandler(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 == 0x84 || opcode == 0x85 || opcode == 0xA8 || opcode == 0xA9; + } + + /// + /// Decodes a TEST 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; + } + + // Set the mnemonic + instruction.Mnemonic = "test"; + + switch (opcode) + { + case 0x84: // TEST r/m8, r8 + case 0x85: // TEST r/m32, r32 + // Read the ModR/M byte + var (mod, reg, rm, destOperand) = ModRMDecoder.ReadModRM(); + + // Determine the source register + string sourceReg; + if (opcode == 0x84) // 8-bit registers + { + sourceReg = ModRMDecoder.GetRegister8(reg); + } + else // 32-bit registers + { + sourceReg = ModRMDecoder.GetRegister32(reg); + } + + // Set the operands + instruction.Operands = $"{destOperand}, {sourceReg}"; + break; + + case 0xA8: // TEST AL, imm8 + if (position < Length) + { + byte imm8 = CodeBuffer[position]; + Decoder.SetPosition(position + 1); + instruction.Operands = $"al, 0x{imm8:X2}"; + } + else + { + instruction.Operands = "al, ???"; + } + break; + + case 0xA9: // TEST EAX, imm32 + if (position + 3 < Length) + { + uint imm32 = BitConverter.ToUInt32(CodeBuffer, position); + Decoder.SetPosition(position + 4); + instruction.Operands = $"eax, 0x{imm32:X8}"; + } + else + { + instruction.Operands = "eax, ???"; + } + break; + + default: + return false; + } + + return true; + } +} diff --git a/X86Disassembler/X86/InstructionDecoder.cs b/X86Disassembler/X86/InstructionDecoder.cs index 78f40b9..f95c21e 100644 --- a/X86Disassembler/X86/InstructionDecoder.cs +++ b/X86Disassembler/X86/InstructionDecoder.cs @@ -48,7 +48,9 @@ public class InstructionDecoder new Group1Handler(_codeBuffer, this, _length), new FloatingPointHandler(_codeBuffer, this, _length), new DataTransferHandler(_codeBuffer, this, _length), - new ControlFlowHandler(_codeBuffer, this, _length) + new ControlFlowHandler(_codeBuffer, this, _length), + new Group3Handler(_codeBuffer, this, _length), + new TestHandler(_codeBuffer, this, _length) }; } diff --git a/X86Disassembler/X86/ModRMDecoder.cs b/X86Disassembler/X86/ModRMDecoder.cs index dcf3ba5..5f6bf03 100644 --- a/X86Disassembler/X86/ModRMDecoder.cs +++ b/X86Disassembler/X86/ModRMDecoder.cs @@ -239,4 +239,34 @@ public class ModRMDecoder _ => RegisterNames32[index] }; } + + /// + /// Gets the 8-bit register name based on the register number + /// + /// The register number (0-7) + /// The register name + public static string GetRegister8(int reg) + { + if (reg >= 0 && reg < RegisterNames8.Length) + { + return RegisterNames8[reg]; + } + + return $"r{reg}?"; + } + + /// + /// Gets the 32-bit register name based on the register number + /// + /// The register number (0-7) + /// The register name + public static string GetRegister32(int reg) + { + if (reg >= 0 && reg < RegisterNames32.Length) + { + return RegisterNames32[reg]; + } + + return $"r{reg}?"; + } } diff --git a/X86Disassembler/X86/OpcodeMap.cs b/X86Disassembler/X86/OpcodeMap.cs index 9a0ee44..bad0108 100644 --- a/X86Disassembler/X86/OpcodeMap.cs +++ b/X86Disassembler/X86/OpcodeMap.cs @@ -19,6 +19,11 @@ public class OpcodeMap "add", "or", "adc", "sbb", "and", "sub", "xor", "cmp" }; + // Group 3 operations (used with opcodes 0xF6, 0xF7) + public static readonly string[] Group3Operations = { + "test", "???", "not", "neg", "mul", "imul", "div", "idiv" + }; + // Static constructor to initialize the opcode maps static OpcodeMap() { @@ -51,6 +56,16 @@ public class OpcodeMap OneByteOpcodes[0x81] = "group1d"; OneByteOpcodes[0x83] = "group1s"; // Sign-extended immediate + // Group 3 instructions (TEST, NOT, NEG, MUL, IMUL, DIV, IDIV) + OneByteOpcodes[0xF6] = "group3b"; // 8-bit operations + OneByteOpcodes[0xF7] = "group3d"; // 32-bit operations + + // TEST instructions + OneByteOpcodes[0x84] = "test"; // TEST r/m8, r8 + OneByteOpcodes[0x85] = "test"; // TEST r/m32, r32 + OneByteOpcodes[0xA8] = "test"; // TEST AL, imm8 + OneByteOpcodes[0xA9] = "test"; // TEST EAX, imm32 + // Data transfer instructions for (int i = 0x88; i <= 0x8B; i++) { @@ -125,6 +140,16 @@ public class OpcodeMap return opcode == 0x80 || opcode == 0x81 || opcode == 0x83; } + /// + /// Checks if the opcode is a Group 3 opcode + /// + /// The opcode to check + /// True if the opcode is a Group 3 opcode + public static bool IsGroup3Opcode(byte opcode) + { + return opcode == 0xF6 || opcode == 0xF7; + } + /// /// Checks if the opcode is a floating-point instruction ///