Skip to content

Commit

Permalink
Implemented some more families:
Browse files Browse the repository at this point in the history
- Add/subtract imm (with tags)
- Data processing (register, 1-source)
- Add/subtract with carry
- Rotate right into flags
- Evaluate into flags
- Floating point conversion to/from fixed point
  • Loading branch information
SamboyCoding committed Sep 2, 2024
1 parent db7e28c commit 72d9aca
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 9 deletions.
36 changes: 36 additions & 0 deletions Disarm.Tests/DataProcessingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ public void DisassemblingBitfieldsWorks()
Assert.Equal(Arm64Register.X21, insn.Op0Reg);
Assert.Equal(Arm64Register.W20, insn.Op1Reg);
}

[Fact]
public void DataProcessing1Source()
{
//Mostly just mnemonic checks but with some register checks sprinkled in
DisassembleAndCheckMnemonic(0x5AC00914, Arm64Mnemonic.REV);
DisassembleAndCheckMnemonic(0xDAC00D14, Arm64Mnemonic.REV); //but 64-bit
DisassembleAndCheckMnemonic(0xDAC00914, Arm64Mnemonic.REV32);
DisassembleAndCheckMnemonic(0xDAC00514, Arm64Mnemonic.REV16);

Assert.Equal(Arm64Register.X8, DisassembleAndCheckMnemonic(0xDAC01114, Arm64Mnemonic.CLZ).Op1Reg);
DisassembleAndCheckMnemonic(0xDAC01514, Arm64Mnemonic.CLS);

Assert.Equal(Arm64Register.W20, DisassembleAndCheckMnemonic(0x5AC00114, Arm64Mnemonic.RBIT).Op0Reg);
}

[Fact]
public void DataProcessing2Source()
Expand All @@ -29,4 +44,25 @@ public void ConditionalCompareImmediate()
Assert.Equal(2, insn.Op2Imm);
Assert.Equal(Arm64ConditionCode.LT, insn.FinalOpConditionCode);
}

[Fact]
public void AddSubWithCarry()
{
//These are all one code path except for the mnemonic. So we validate the registers on one and then check only the mnemonic on the others.
var ins = DisassembleAndCheckMnemonic(0x9A020020, Arm64Mnemonic.ADC);

Assert.Equal(Arm64Register.X0, ins.Op0Reg);
Assert.Equal(Arm64Register.X1, ins.Op1Reg);
Assert.Equal(Arm64Register.X2, ins.Op2Reg);

DisassembleAndCheckMnemonic(0xBA020020, Arm64Mnemonic.ADCS);
DisassembleAndCheckMnemonic(0xDA020020, Arm64Mnemonic.SBC);
DisassembleAndCheckMnemonic(0xFA020020, Arm64Mnemonic.SBCS);

//And 32-bit
DisassembleAndCheckMnemonic(0x1A020020, Arm64Mnemonic.ADC);
DisassembleAndCheckMnemonic(0x3A020020, Arm64Mnemonic.ADCS);
DisassembleAndCheckMnemonic(0x5A020020, Arm64Mnemonic.SBC);
DisassembleAndCheckMnemonic(0x7A020020, Arm64Mnemonic.SBCS);
}
}
39 changes: 39 additions & 0 deletions Disarm.Tests/FloatingPointTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Disarm.InternalDisassembly;
using Xunit.Abstractions;

namespace Disarm.Tests;

public class FloatingPointTests : BaseDisarmTest
{
public FloatingPointTests(ITestOutputHelper outputHelper) : base(outputHelper)
{
}

[Fact]
public void FloatingToFromFixedTests()
{
var insn = DisassembleAndCheckMnemonic(0x1E02C060, Arm64Mnemonic.SCVTF);

Assert.Equal(Arm64OperandKind.Register, insn.Op0Kind);
Assert.Equal(Arm64OperandKind.Register, insn.Op1Kind);
Assert.Equal(Arm64OperandKind.Immediate, insn.Op2Kind);

Assert.Equal(Arm64Register.S0, insn.Op0Reg);
Assert.Equal(Arm64Register.W3, insn.Op1Reg);
Assert.Equal(16, insn.Op2Imm);

Assert.Equal("0x00000000 SCVTF S0, W3, 0x10", insn.ToString());

insn = DisassembleAndCheckMnemonic(0x1E18C060, Arm64Mnemonic.FCVTZS);

Assert.Equal(Arm64OperandKind.Register, insn.Op0Kind);
Assert.Equal(Arm64OperandKind.Register, insn.Op1Kind);
Assert.Equal(Arm64OperandKind.Immediate, insn.Op2Kind);

Assert.Equal(Arm64Register.W0, insn.Op0Reg);
Assert.Equal(Arm64Register.S3, insn.Op1Reg);
Assert.Equal(16, insn.Op2Imm);

Assert.Equal("0x00000000 FCVTZS W0, S3, 0x10", insn.ToString());
}
}
15 changes: 15 additions & 0 deletions Disarm/Arm64Mnemonic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ public enum Arm64Mnemonic
{
INVALID,
UNIMPLEMENTED,
ADC,
ADCS,
ADD,
ADDG,
ADDHN,
ADDHN2,
ADDP,
Expand All @@ -28,6 +31,8 @@ public enum Arm64Mnemonic
CCMN,
CCMP,
CINC,
CLS,
CLZ,
CMGE,
CMGT,
CMP,
Expand Down Expand Up @@ -138,6 +143,11 @@ public enum Arm64Mnemonic
PRFUM,
RADDHN,
RADDHN2,
RBIT,
REV,
REV16,
REV32,
RMIF,
RORV,
RSUBHN,
RSUBHN2,
Expand All @@ -152,9 +162,13 @@ public enum Arm64Mnemonic
SADDL2,
SADDW,
SADDW2,
SBC,
SBCS,
SBFM,
SCVTF,
SDIV,
SETF16,
SETF8,
SMADDL,
SHADD,
SHSUB,
Expand Down Expand Up @@ -199,6 +213,7 @@ public enum Arm64Mnemonic
STURB,
STURH,
SUB,
SUBG,
SUBHN,
SUBHN2,
SUBP,
Expand Down
36 changes: 35 additions & 1 deletion Disarm/InternalDisassembly/Arm64DataProcessingImmediate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,44 @@ public static Arm64Instruction AddSubtractImmediate(uint instruction)

public static Arm64Instruction AddSubtractImmediateWithTags(uint instruction)
{
//Technically this is all MTE extension which isn't really supported, but it's simple enough to disassemble
var sf = instruction.TestBit(31);
var s = instruction.TestBit(29);
var o2 = instruction.TestBit(22);

if(o2)
throw new Arm64UndefinedInstructionException("Add/subtract immediate (with tags): o2 set");

if(!sf)
throw new Arm64UndefinedInstructionException("Add/subtract immediate (with tags): sf not set");

if(s)
throw new Arm64UndefinedInstructionException("Add/subtract immediate (with tags): S set");

var op = instruction.TestBit(30);
var uimm6 = (instruction >> 16) & 0b11_1111;
var op3 = (int) (instruction >> 14) & 0b11;
var uimm4 = (instruction >> 10) & 0b1111;
var rn = (int) (instruction >> 5) & 0b1_1111;
var rd = (int) instruction & 0b1_1111;

var regN = Arm64Register.X0 + rn;
var regD = Arm64Register.X0 + rd;

uimm6 <<= 4; //Multiple of 16

return new()
{
Mnemonic = Arm64Mnemonic.UNIMPLEMENTED,
Mnemonic = op ? Arm64Mnemonic.SUBG : Arm64Mnemonic.ADDG,
MnemonicCategory = Arm64MnemonicCategory.Math,
Op0Kind = Arm64OperandKind.Register,
Op1Kind = Arm64OperandKind.Register,
Op2Kind = Arm64OperandKind.Immediate,
Op3Kind = Arm64OperandKind.Immediate,
Op0Reg = regD,
Op1Reg = regN,
Op2Imm = uimm6,
Op3Imm = uimm4,
};
}

Expand Down
154 changes: 148 additions & 6 deletions Disarm/InternalDisassembly/Arm64DataProcessingRegister.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,64 @@ 0b0010 when op3.TestBit(1) => ConditionalCompare(instruction, false),

private static Arm64Instruction DataProcessing1Source(uint instruction)
{
var sf = instruction.TestBit(31);
var sFlag = instruction.TestBit(29);
var opcode = (instruction >> 10) & 0b11_1111;
var opcode2 = (instruction >> 16) & 0b1_1111;
var rn = (int)(instruction >> 5) & 0b1_1111;
var rd = (int)instruction & 0b1_1111;

if(sFlag)
throw new Arm64UndefinedInstructionException("DataProcessing1Source: S flag set");

if(opcode.TestBit(6))
throw new Arm64UndefinedInstructionException("DataProcessing1Source: top bit of opcode set");

if(opcode2 > 1)
throw new Arm64UndefinedInstructionException("DataProcessing1Source: opcode2 > 1");

if (opcode2 == 1)
{
//FEAT_PAUTH stuff. Not implemented in disarm, yet.
//But also literally not defined if sf == 0

if(!sf)
throw new Arm64UndefinedInstructionException("DataProcessing1Source: opcode2 == 1 and sf == 0");

return new()
{
Mnemonic = Arm64Mnemonic.UNIMPLEMENTED,
MnemonicCategory = Arm64MnemonicCategory.PointerAuthentication,
};
}

if(opcode > 0b00_0101)
//Anything above this is FEAT_PAUTH stuff, so not defined if that's not set.
throw new Arm64UndefinedInstructionException($"DataProcessing1Source: opcode > 0b00_0101: {opcode:X} when opcode2 == 0");

var baseReg = sf ? Arm64Register.X0 : Arm64Register.W0; //sf == 1 means 64-bit variant

var mnemonic = opcode switch
{
0b00_0000 => Arm64Mnemonic.RBIT,
0b00_0001 => Arm64Mnemonic.REV16,
0b00_0010 when !sf => Arm64Mnemonic.REV,
0b00_0010 => Arm64Mnemonic.REV32,
//This would be REV64 but on a 32-bit register, which is invalid
0b00_0011 when !sf => throw new Arm64UndefinedInstructionException("DataProcessing1Source: opcode == 0b00_0011 and sf == 0"),
0b00_0011 => Arm64Mnemonic.REV,
0b00_0100 => Arm64Mnemonic.CLZ,
0b00_0101 => Arm64Mnemonic.CLS,
};

return new()
{
Mnemonic = Arm64Mnemonic.UNIMPLEMENTED,
MnemonicCategory = Arm64MnemonicCategory.GeneralDataProcessing,
Mnemonic = mnemonic,
MnemonicCategory = Arm64MnemonicCategory.GeneralDataProcessing,
Op0Kind = Arm64OperandKind.Register,
Op1Kind = Arm64OperandKind.Register,
Op0Reg = baseReg + rd,
Op1Reg = baseReg + rn,
};
}

Expand Down Expand Up @@ -289,28 +343,116 @@ private static Arm64Instruction AddSubtractExtendedRegister(uint instruction)

private static Arm64Instruction AddSubtractWithCarry(uint instruction)
{
var sf = instruction.TestBit(31);
var op = instruction.TestBit(30);
var sFlag = instruction.TestBit(29);
var rm = (int) (instruction >> 16) & 0b1_1111;
var rn = (int) (instruction >> 5) & 0b1_1111;
var rd = (int) instruction & 0b1_1111;

var mnemonic = op
? sFlag ? Arm64Mnemonic.SBCS : Arm64Mnemonic.SBC
: sFlag ? Arm64Mnemonic.ADCS : Arm64Mnemonic.ADC;

var baseReg = sf ? Arm64Register.X0 : Arm64Register.W0;

var regM = baseReg + rm;
var regN = baseReg + rn;
var regD = baseReg + rd;

return new()
{
Mnemonic = Arm64Mnemonic.UNIMPLEMENTED,
MnemonicCategory = Arm64MnemonicCategory.Math,
Mnemonic = mnemonic,
MnemonicCategory = Arm64MnemonicCategory.Math,
Op0Kind = Arm64OperandKind.Register,
Op1Kind = Arm64OperandKind.Register,
Op2Kind = Arm64OperandKind.Register,
Op0Reg = regD,
Op1Reg = regN,
Op2Reg = regM,
};
}

private static Arm64Instruction RotateRightIntoFlags(uint instruction)
{
var sf = instruction.TestBit(31);
var op = instruction.TestBit(30);
var sFlag = instruction.TestBit(29);
var imm6 = (instruction >> 15) & 0b11_1111;
var rn = (int) (instruction >> 5) & 0b1_1111;
var o2 = instruction.TestBit(4);
var mask = instruction & 0b1111;

if(!sf)
throw new Arm64UndefinedInstructionException("RotateRightIntoFlags: sf == 0");

if(op)
throw new Arm64UndefinedInstructionException("RotateRightIntoFlags: op == 1");

if(!sFlag)
throw new Arm64UndefinedInstructionException("RotateRightIntoFlags: S == 0");

if(o2)
throw new Arm64UndefinedInstructionException("RotateRightIntoFlags: o2 == 1");

//The ONLY valid encoding is sf, no op, S, no o2, which is RMIF
//This entire block is FEAT_FlagM stuff but it's trivial to implement so.

var regN = Arm64Register.X0 + rn;

return new()
{
Mnemonic = Arm64Mnemonic.UNIMPLEMENTED,
Mnemonic = Arm64Mnemonic.RMIF,
MnemonicCategory = Arm64MnemonicCategory.FlagMath,
Op0Kind = Arm64OperandKind.Register,
Op1Kind = Arm64OperandKind.Immediate,
Op2Kind = Arm64OperandKind.Immediate,
Op0Reg = regN,
Op1Imm = imm6,
Op2Imm = mask,
};
}

private static Arm64Instruction EvaluateIntoFlags(uint instruction)
{
var sf = instruction.TestBit(31);
var op = instruction.TestBit(30);
var sFlag = instruction.TestBit(29);
var opcode2 = (instruction >> 15) & 0b11_1111;
var sz = instruction.TestBit(14);
var rn = (int) (instruction >> 5) & 0b1_1111;
var o3 = instruction.TestBit(4);
var mask = instruction & 0b1111;

//Only valid encoding is no sf, no op, S set, opcode2 == 0, o3 clear, mask == 0b1101
//Again this is FEAT_FlagM stuff but trivial to implement
if(sf)
throw new Arm64UndefinedInstructionException("EvaluateIntoFlags: sf == 1");

if(op)
throw new Arm64UndefinedInstructionException("EvaluateIntoFlags: op == 1");

if(!sFlag)
throw new Arm64UndefinedInstructionException("EvaluateIntoFlags: S == 0");

if(opcode2 != 0)
throw new Arm64UndefinedInstructionException("EvaluateIntoFlags: opcode2 != 0");

if(o3)
throw new Arm64UndefinedInstructionException("EvaluateIntoFlags: o3 == 1");

if(mask != 0b1101)
throw new Arm64UndefinedInstructionException("EvaluateIntoFlags: mask != 0b1101");

var regN = Arm64Register.W0 + rn;
var mnemonic = sz ? Arm64Mnemonic.SETF16 : Arm64Mnemonic.SETF8;

return new()
{
Mnemonic = Arm64Mnemonic.UNIMPLEMENTED,
Mnemonic = mnemonic,
MnemonicCategory = Arm64MnemonicCategory.FlagMath,
Op0Kind = Arm64OperandKind.Register,
Op0Reg = regN,
};
}

Expand Down
Loading

0 comments on commit 72d9aca

Please sign in to comment.