Skip to content

Commit

Permalink
Core: More Arm64 ISIL conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
SamboyCoding committed Jun 11, 2024
1 parent 93ac84e commit 5b5a34d
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Cpp2IL.Core/ISIL/InstructionSetIndependentOpCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class InstructionSetIndependentOpCode
public static readonly InstructionSetIndependentOpCode Call = new(IsilMnemonic.Call);
public static readonly InstructionSetIndependentOpCode CallNoReturn = new(IsilMnemonic.CallNoReturn);
public static readonly InstructionSetIndependentOpCode Exchange = new(IsilMnemonic.Exchange, 2, InstructionSetIndependentOperand.OperandType.NotStack, InstructionSetIndependentOperand.OperandType.NotStack);
public static readonly InstructionSetIndependentOpCode Add = new(IsilMnemonic.Add, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any);
public static readonly InstructionSetIndependentOpCode Add = new(IsilMnemonic.Add, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any);
public static readonly InstructionSetIndependentOpCode Subtract = new(IsilMnemonic.Subtract, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any);
public static readonly InstructionSetIndependentOpCode Multiply = new(IsilMnemonic.Multiply, 3, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any);
public static readonly InstructionSetIndependentOpCode Divide = new(IsilMnemonic.Divide, 2, InstructionSetIndependentOperand.OperandType.Any, InstructionSetIndependentOperand.OperandType.Any);
Expand Down
9 changes: 7 additions & 2 deletions Cpp2IL.Core/ISIL/InstructionSetIndependentOperand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ private InstructionSetIndependentOperand(OperandType type, IsilOperandData data)
}

public override string? ToString()
=> Data.ToString();
{
if(Data is InstructionSetIndependentInstruction instruction)
return $"{{{instruction.InstructionIndex.ToString()}}}"; //Special case for instructions, we want to show the index in braces. Otherwise we print the entire instruction and it looks weird.

return Data.ToString();
}

[Flags]
public enum OperandType
Expand All @@ -38,4 +43,4 @@ public enum OperandType

Any = Immediate | StackOffset | Register | Memory
}
}
}
2 changes: 1 addition & 1 deletion Cpp2IL.Core/ISIL/IsilBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void FixJumps()
public void Exchange(ulong instructionAddress, InstructionSetIndependentOperand place1, InstructionSetIndependentOperand place2) => AddInstruction(new(InstructionSetIndependentOpCode.Exchange, instructionAddress, IsilFlowControl.Continue, place1, place2));

public void Subtract(ulong instructionAddress, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Subtract, instructionAddress, IsilFlowControl.Continue, left, right));
public void Add(ulong instructionAddress, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Add, instructionAddress, IsilFlowControl.Continue, left, right));
public void Add(ulong instructionAddress, InstructionSetIndependentOperand dest, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Add, instructionAddress, IsilFlowControl.Continue, dest, left, right));
public void Xor(ulong instructionAddress, InstructionSetIndependentOperand left, InstructionSetIndependentOperand right) => AddInstruction(new(InstructionSetIndependentOpCode.Xor, instructionAddress, IsilFlowControl.Continue, left, right));
// The following 4 had their opcode implemented but not the builder func
// I don't know why
Expand Down
9 changes: 5 additions & 4 deletions Cpp2IL.Core/ISIL/IsilMemoryOperand.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Diagnostics;
using System.Text;

Expand Down Expand Up @@ -95,15 +96,15 @@ public override string ToString()
needsPlus = true;
}

if(Addend > 0)
if(Addend != 0)
{
if (needsPlus)
ret.Append("+");
ret.Append(Addend > 0 ? '+' : '-');

if(Addend > 0x10000)
ret.AppendFormat("0x{0:X}", Addend);
ret.AppendFormat("0x{0:X}", Math.Abs(Addend));
else
ret.Append(Addend);
ret.Append(Math.Abs(Addend));
needsPlus = true;
}

Expand Down
153 changes: 148 additions & 5 deletions Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Disarm;
using Cpp2IL.Core.Api;
Expand All @@ -19,7 +20,7 @@ public override Memory<byte> GetRawBytesForMethod(MethodAnalysisContext context,
if (context is not ConcreteGenericMethodAnalysisContext)
{
//Managed method or attr gen => grab raw byte range between a and b
var startOfNextFunction = (int) MiscUtils.GetAddressOfNextFunctionStart(context.UnderlyingPointer) - 1;
var startOfNextFunction = (int) MiscUtils.GetAddressOfNextFunctionStart(context.UnderlyingPointer);
var ptrAsInt = (int) context.UnderlyingPointer;
var count = startOfNextFunction - ptrAsInt;

Expand Down Expand Up @@ -61,10 +62,86 @@ private void ConvertInstructionStatement(Arm64Instruction instruction, IsilBuild
switch (instruction.Mnemonic)
{
case Arm64Mnemonic.MOV:
case Arm64Mnemonic.LDR:
case Arm64Mnemonic.LDRB:
//Load and move are (dest, src)
builder.Move(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
break;
case Arm64Mnemonic.STR:
case Arm64Mnemonic.STRB:
//Store is (src, dest)
builder.Move(instruction.Address, ConvertOperand(instruction, 1), ConvertOperand(instruction, 0));
break;
case Arm64Mnemonic.ADRP:
//Just handle as a move
builder.Move(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
break;
case Arm64Mnemonic.LDP when instruction.Op2Kind == Arm64OperandKind.Memory:
//LDP (dest1, dest2, [mem]) - basically just treat as two loads, with the second offset by the length of the first
var destRegSize = instruction.Op0Reg switch
{
//double
>= Arm64Register.D0 and <= Arm64Register.D31 => 8,
//single
>= Arm64Register.S0 and <= Arm64Register.S31 => 4,
//half
>= Arm64Register.H0 and <= Arm64Register.H31 => 2,
//word
>= Arm64Register.W0 and <= Arm64Register.W31 => 4,
//x
>= Arm64Register.X0 and <= Arm64Register.X31 => 8,
_ => throw new($"Unknown register size for LDP: {instruction.Op0Reg}")
};

var dest1 = ConvertOperand(instruction, 0);
var dest2 = ConvertOperand(instruction, 1);
var mem = ConvertOperand(instruction, 2);

//TODO clean this mess up
var memInternal = mem.Data as IsilMemoryOperand?;
var mem2 = new IsilMemoryOperand(memInternal!.Value.Base!.Value, memInternal.Value.Addend + destRegSize);

builder.Move(instruction.Address, dest1, mem);
builder.Move(instruction.Address, dest2, InstructionSetIndependentOperand.MakeMemory(mem2));
break;
case Arm64Mnemonic.BL:
builder.Call(instruction.Address, (ulong) ((long) instruction.Address + instruction.Op0Imm));
builder.Call(instruction.Address, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray());
break;
case Arm64Mnemonic.RET:
builder.Return(instruction.Address, GetReturnRegisterForContext(context));
break;
case Arm64Mnemonic.B:
var target = instruction.BranchTarget;

if (target < context.UnderlyingPointer || target > context.UnderlyingPointer + (ulong)context.RawBytes.Length)
{
//Unconditional branch to outside the method, treat as call (tail-call, specifically) followed by return
builder.Call(instruction.Address, instruction.BranchTarget, GetArgumentOperandsForCall(context, instruction.BranchTarget).ToArray());
builder.Return(instruction.Address, GetReturnRegisterForContext(context));
}

break;
case Arm64Mnemonic.CBNZ:
case Arm64Mnemonic.CBZ:
//Compare and branch if (non-)zero
var targetAddr = (ulong) ((long) instruction.Address + instruction.Op1Imm);

//Compare to zero...
builder.Compare(instruction.Address, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(0));

//And jump if (not) equal
if (instruction.Mnemonic == Arm64Mnemonic.CBZ)
builder.JumpIfEqual(instruction.Address, targetAddr);
else
builder.JumpIfNotEqual(instruction.Address, targetAddr);
break;
case Arm64Mnemonic.FMUL:
//Multiply is (dest, src1, src2)
builder.Multiply(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
break;
case Arm64Mnemonic.FADD:
//Add is (dest, src1, src2)
builder.Add(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
break;
}
}
Expand Down Expand Up @@ -108,7 +185,7 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc
_ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
};

return InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToLower());
return InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToUpperInvariant());
}

if (kind == Arm64OperandKind.Memory)
Expand All @@ -123,7 +200,7 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc

//TODO Handle more stuff here
return InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand(
InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToLower()),
InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToUpperInvariant()),
offset));
}

Expand All @@ -132,5 +209,71 @@ private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruc

public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new NewArm64KeyFunctionAddresses();

public override string PrintAssembly(MethodAnalysisContext context) => string.Join("\n", Disassembler.Disassemble(context.RawBytes.Span, context.UnderlyingPointer).ToList());
public override string PrintAssembly(MethodAnalysisContext context) => context.RawBytes.Span.Length <= 0 ? "" : string.Join("\n", Disassembler.Disassemble(context.RawBytes.Span, context.UnderlyingPointer, new Disassembler.Options(true, true, false)).ToList());

private InstructionSetIndependentOperand? GetReturnRegisterForContext(MethodAnalysisContext context)
{
var returnType = context.ReturnTypeContext;
if (returnType.Namespace == nameof(System))
{
return returnType.Name switch
{
"Void" => null, //Void is no return
"Double" => InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.V0)), //Builtin double is v0
"Single" => InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.V0)), //Builtin float is v0
_ => InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.X0)), //All other system types are x0 like any other pointer
};
}

//TODO Do certain value types have different return registers?

//Any user type is returned in x0
return InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.X0));
}

private List<InstructionSetIndependentOperand> GetArgumentOperandsForCall(MethodAnalysisContext contextBeingAnalyzed, ulong callAddr)
{
if (!contextBeingAnalyzed.AppContext.MethodsByAddress.TryGetValue(callAddr, out var methodsAtAddress))
//TODO
return new List<InstructionSetIndependentOperand>();

//For the sake of arguments, all we care about is the first method at the address, because they'll only be shared if they have the same signature.
var contextBeingCalled = methodsAtAddress.First();

var vectorCount = 0;
var nonVectorCount = 0;

var ret = new List<InstructionSetIndependentOperand>();

//Handle 'this' if it's an instance method
if (!contextBeingCalled.IsStatic)
{
ret.Add(InstructionSetIndependentOperand.MakeRegister(nameof(Arm64Register.X0)));
nonVectorCount++;
}

foreach (var parameter in contextBeingCalled.Parameters)
{
var paramType = parameter.ParameterTypeContext;
if (paramType.Namespace == nameof(System))
{
switch (paramType.Name)
{
case "Single":
case "Double":
ret.Add(InstructionSetIndependentOperand.MakeRegister((Arm64Register.V0 + vectorCount++).ToString().ToUpperInvariant()));
break;
default:
ret.Add(InstructionSetIndependentOperand.MakeRegister((Arm64Register.X0 + nonVectorCount++).ToString().ToUpperInvariant()));
break;
}
}
else
{
ret.Add(InstructionSetIndependentOperand.MakeRegister((Arm64Register.X0 + nonVectorCount++).ToString().ToUpperInvariant()));
}
}

return ret;
}
}
4 changes: 2 additions & 2 deletions Cpp2IL.Core/InstructionSets/X86InstructionSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private void ConvertInstructionStatement(Instruction instruction, IsilBuilder bu
if (isSubtract)
builder.Subtract(instruction.IP, left, right);
else
builder.Add(instruction.IP, left, right);
builder.Add(instruction.IP, left, left, right);

break;
case Mnemonic.Dec:
Expand All @@ -145,7 +145,7 @@ private void ConvertInstructionStatement(Instruction instruction, IsilBuilder bu
var isDec = instruction.Mnemonic == Mnemonic.Dec;
var im = InstructionSetIndependentOperand.MakeImmediate(1);
if (isDec) builder.Subtract(instruction.IP, ConvertOperand(instruction, 0), im);
else builder.Add(instruction.IP, ConvertOperand(instruction, 0), im);
else builder.Add(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), im);
break;
case Mnemonic.Call:
// We don't try and resolve which method is being called, but we do need to know how many parameters it has
Expand Down
7 changes: 6 additions & 1 deletion Cpp2IL.Core/OutputFormats/IsilDumpOutputFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public override void DoOutput(ApplicationAnalysisContext context, string outputR
{
method.Analyze();
typeDump.AppendLine("Disassembly:");
typeDump.Append('\t').AppendLine(context.InstructionSet.PrintAssembly(method).Replace("\n", "\n\t"));
typeDump.AppendLine().AppendLine("ISIL:");
if (method.ConvertedIsil == null || method.ConvertedIsil.Count == 0)
{
typeDump.AppendLine("No ISIL was generated");
Expand All @@ -65,7 +70,7 @@ public override void DoOutput(ApplicationAnalysisContext context, string outputR
}
catch (Exception e)
{
typeDump.Append("Method threw an exception while analyzing - ").AppendLine(e.Message).AppendLine();
typeDump.Append("Method threw an exception while analyzing - ").AppendLine(e.ToString()).AppendLine();
}
}
Expand Down

0 comments on commit 5b5a34d

Please sign in to comment.