Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 5 additions & 134 deletions src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,143 +2,13 @@
using Gee.External.Capstone;
using Gee.External.Capstone.Arm64;
using Microsoft.Diagnostics.Runtime;
using Microsoft.Diagnostics.Runtime.Interfaces;

namespace BenchmarkDotNet.Disassemblers
{
internal struct RegisterValueAccumulator
{
private enum State
{
LookingForPattern,
ExpectingMovk,
ExpectingAdd,
LookingForPossibleLdr
}

private State _state;
private long _value;
private int _expectedMovkShift;
private Arm64RegisterId _registerId;
private ClrRuntime _runtime;

public void Init(ClrRuntime runtime)
{
_state = State.LookingForPattern;
_expectedMovkShift = 0;
_value = 0;
_registerId = Arm64RegisterId.Invalid;
_runtime = runtime;
}

public void Feed(Arm64Instruction instruction)
{
Arm64InstructionDetail details = instruction.Details;

switch (_state)
{
case State.LookingForPattern:
if (instruction.Id == Arm64InstructionId.ARM64_INS_MOVZ)
{
_registerId = details.Operands[0].Register.Id;
_value = details.Operands[1].Immediate;
_state = State.ExpectingMovk;
_expectedMovkShift = 16;
}
else if (instruction.Id == Arm64InstructionId.ARM64_INS_ADRP)
{
_registerId = details.Operands[0].Register.Id;
_value = details.Operands[1].Immediate;
_state = State.ExpectingAdd;
}
break;
case State.ExpectingMovk:
if (instruction.Id == Arm64InstructionId.ARM64_INS_MOVK &&
details.Operands[0].Register.Id == _registerId &&
details.Operands[1].ShiftOperation == Arm64ShiftOperation.ARM64_SFT_LSL &&
details.Operands[1].ShiftValue == _expectedMovkShift)
{
_value = _value | (instruction.Details.Operands[1].Immediate << details.Operands[1].ShiftValue);
_expectedMovkShift += 16;
break;
}
_state = State.LookingForPossibleLdr;
goto case State.LookingForPossibleLdr;
case State.ExpectingAdd:
if (instruction.Id == Arm64InstructionId.ARM64_INS_ADD &&
details.Operands[0].Register.Id == _registerId &&
details.Operands[1].Register.Id == _registerId &&
details.Operands[2].Type == Arm64OperandType.Immediate)
{
_value = _value | instruction.Details.Operands[2].Immediate;
_state = State.LookingForPossibleLdr;
}
break;
case State.LookingForPossibleLdr:
if (instruction.Id == Arm64InstructionId.ARM64_INS_LDR &&
details.Operands[1].Type == Arm64OperandType.Memory &&
details.Operands[1].Memory.Base.Id == _registerId && // The source address is in the register we are tracking
details.Operands[1].Memory.Displacement == 0 && // There is no displacement
details.Operands[1].Memory.Index == null) // And there is no extra index register
{
// Simulate the LDR instruction.
long newValue = (long)_runtime.DataTarget.DataReader.ReadPointer((ulong)_value);
_value = newValue;
if (_value == 0)
{
_state = State.LookingForPattern;
}
else
{
// The LDR might have loaded the result in another register
_registerId = details.Operands[0].Register.Id;
}
}
else if (instruction.Id == Arm64InstructionId.ARM64_INS_CBZ ||
instruction.Id == Arm64InstructionId.ARM64_INS_CBNZ ||
instruction.Id == Arm64InstructionId.ARM64_INS_B && details.ConditionCode != Arm64ConditionCode.Invalid)
{
// ignore conditional branches
}
else if (details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE) ||
details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_CALL) ||
details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_JUMP))
{
// We've encountered an unconditional jump or call, the accumulated registers value is not valid anymore
_state = State.LookingForPattern;
}
else if (instruction.Id == Arm64InstructionId.ARM64_INS_MOVZ)
{
// Another constant loading is starting
_state = State.LookingForPattern;
goto case State.LookingForPattern;
}
else
{
// Finally check if the current instruction modified the register that was accumulating the constant
// and reset the state machine in case it did.
foreach (Arm64Register reg in details.AllWrittenRegisters)
{
// Some unexpected instruction overwriting the accumulated register
if (reg.Id == _registerId)
{
_state = State.LookingForPattern;
}
}
}
break;
}
}

public bool HasValue => _state == State.ExpectingMovk || _state == State.LookingForPossibleLdr;

public long Value { get { return _value; } }

public Arm64RegisterId RegisterId { get { return _registerId; } }
}

internal class Arm64Disassembler : ClrMdDisassembler
{
protected override IEnumerable<Asm> Decode(byte[] code, ulong startAddress, State state, int depth, ClrMethod currentMethod, DisassemblySyntax syntax)
protected override IEnumerable<Asm> Decode(byte[] code, ulong startAddress, State state, int depth, IClrMethod currentMethod, DisassemblySyntax syntax)
{
const Arm64DisassembleMode disassembleMode = Arm64DisassembleMode.Arm;
using (CapstoneArm64Disassembler disassembler = CapstoneDisassembler.CreateArm64Disassembler(disassembleMode))
Expand All @@ -147,7 +17,8 @@ protected override IEnumerable<Asm> Decode(byte[] code, ulong startAddress, Stat
// disassembled binary code.
disassembler.EnableInstructionDetails = true;
disassembler.DisassembleSyntax = Map(syntax);
RegisterValueAccumulator accumulator = new RegisterValueAccumulator();

Arm64RegisterValueAccumulator accumulator = new();
accumulator.Init(state.Runtime);

Arm64Instruction[] instructions = disassembler.Disassemble(code, (long)startAddress);
Expand Down Expand Up @@ -324,7 +195,7 @@ private static bool IsLdrLiteral64(uint instr, out int rt, out int offsetBytes)
return true;
}

private static bool TryGetReferencedAddress(Arm64Instruction instruction, RegisterValueAccumulator accumulator, uint pointerSize, out ulong referencedAddress, out bool isReferencedAddressIndirect)
private static bool TryGetReferencedAddress(Arm64Instruction instruction, Arm64RegisterValueAccumulator accumulator, uint pointerSize, out ulong referencedAddress, out bool isReferencedAddressIndirect)
{
if ((instruction.Id == Arm64InstructionId.ARM64_INS_BR || instruction.Id == Arm64InstructionId.ARM64_INS_BLR) && instruction.Details.Operands[0].Register.Id == accumulator.RegisterId && accumulator.HasValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ internal static string Format(Arm64Asm asm, FormatterOptions formatterOptions,
FormatInstructionPointer(instruction, formatterOptions, pointerSize, output);
}

output.Append(instruction.Mnemonic.ToString().PadRight(formatterOptions.FirstOperandCharIndex));
var padRight = Math.Max(formatterOptions.FirstOperandCharIndex, instruction.Mnemonic.Length + 1);
output.Append(instruction.Mnemonic.PadRight(padRight));

if (asm.ReferencedAddress.HasValue && !asm.IsReferencedAddressIndirect && symbols.TryGetValue(asm.ReferencedAddress.Value, out var name))
{
Expand Down
135 changes: 135 additions & 0 deletions src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using Gee.External.Capstone.Arm64;
using Microsoft.Diagnostics.Runtime.Interfaces;

namespace BenchmarkDotNet.Disassemblers;

internal struct Arm64RegisterValueAccumulator
{
private enum State
{
LookingForPattern,
ExpectingMovk,
ExpectingAdd,
LookingForPossibleLdr
}

private State _state;
private long _value;
private int _expectedMovkShift;
private Arm64RegisterId _registerId;
private IClrRuntime _runtime;

public void Init(IClrRuntime runtime)
{
_state = State.LookingForPattern;
_expectedMovkShift = 0;
_value = 0;
_registerId = Arm64RegisterId.Invalid;
_runtime = runtime;
}

public void Feed(Arm64Instruction instruction)
{
Arm64InstructionDetail details = instruction.Details;

switch (_state)
{
case State.LookingForPattern:
if (instruction.Id == Arm64InstructionId.ARM64_INS_MOVZ)
{
_registerId = details.Operands[0].Register.Id;
_value = details.Operands[1].Immediate;
_state = State.ExpectingMovk;
_expectedMovkShift = 16;
}
else if (instruction.Id == Arm64InstructionId.ARM64_INS_ADRP)
{
_registerId = details.Operands[0].Register.Id;
_value = details.Operands[1].Immediate;
_state = State.ExpectingAdd;
}
break;
case State.ExpectingMovk:
if (instruction.Id == Arm64InstructionId.ARM64_INS_MOVK &&
details.Operands[0].Register.Id == _registerId &&
details.Operands[1].ShiftOperation == Arm64ShiftOperation.ARM64_SFT_LSL &&
details.Operands[1].ShiftValue == _expectedMovkShift)
{
_value = _value | (instruction.Details.Operands[1].Immediate << details.Operands[1].ShiftValue);
_expectedMovkShift += 16;
break;
}
_state = State.LookingForPossibleLdr;
goto case State.LookingForPossibleLdr;
case State.ExpectingAdd:
if (instruction.Id == Arm64InstructionId.ARM64_INS_ADD &&
details.Operands[0].Register.Id == _registerId &&
details.Operands[1].Register.Id == _registerId &&
details.Operands[2].Type == Arm64OperandType.Immediate)
{
_value = _value | instruction.Details.Operands[2].Immediate;
_state = State.LookingForPossibleLdr;
}
break;
case State.LookingForPossibleLdr:
if (instruction.Id == Arm64InstructionId.ARM64_INS_LDR &&
details.Operands[1].Type == Arm64OperandType.Memory &&
details.Operands[1].Memory.Base.Id == _registerId && // The source address is in the register we are tracking
details.Operands[1].Memory.Displacement == 0 && // There is no displacement
details.Operands[1].Memory.Index == null) // And there is no extra index register
{
// Simulate the LDR instruction.
long newValue = (long)_runtime.DataTarget.DataReader.ReadPointer((ulong)_value);
_value = newValue;
if (_value == 0)
{
_state = State.LookingForPattern;
}
else
{
// The LDR might have loaded the result in another register
_registerId = details.Operands[0].Register.Id;
}
}
else if (instruction.Id == Arm64InstructionId.ARM64_INS_CBZ ||
instruction.Id == Arm64InstructionId.ARM64_INS_CBNZ ||
instruction.Id == Arm64InstructionId.ARM64_INS_B && details.ConditionCode != Arm64ConditionCode.Invalid)
{
// ignore conditional branches
}
else if (details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE) ||
details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_CALL) ||
details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_JUMP))
{
// We've encountered an unconditional jump or call, the accumulated registers value is not valid anymore
_state = State.LookingForPattern;
}
else if (instruction.Id == Arm64InstructionId.ARM64_INS_MOVZ)
{
// Another constant loading is starting
_state = State.LookingForPattern;
goto case State.LookingForPattern;
}
else
{
// Finally check if the current instruction modified the register that was accumulating the constant
// and reset the state machine in case it did.
foreach (Arm64Register reg in details.AllWrittenRegisters)
{
// Some unexpected instruction overwriting the accumulated register
if (reg.Id == _registerId)
{
_state = State.LookingForPattern;
}
}
}
break;
}
}

public bool HasValue => _state == State.ExpectingMovk || _state == State.LookingForPossibleLdr;

public long Value { get { return _value; } }

public Arm64RegisterId RegisterId { get { return _registerId; } }
}
13 changes: 7 additions & 6 deletions src/BenchmarkDotNet/Disassemblers/ClrMdDisassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using BenchmarkDotNet.Filters;
using BenchmarkDotNet.Portability;
using Microsoft.Diagnostics.Runtime;
using Microsoft.Diagnostics.Runtime.Interfaces;
using System.Text.RegularExpressions;

namespace BenchmarkDotNet.Disassemblers
Expand Down Expand Up @@ -157,7 +158,7 @@ private DisassembledMethod[] Disassemble(ClrMdArgs args, State state)
return result.ToArray();
}

private static bool CanBeDisassembled(ClrMethod method) => method.ILOffsetMap.Length > 0 && method.NativeCode > 0;
private static bool CanBeDisassembled(IClrMethod method) => method.ILOffsetMap.Length > 0 && method.NativeCode > 0;

private DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state, ClrMdArgs args, DisassemblySyntax syntax, SourceCodeProvider sourceCodeProvider)
{
Expand Down Expand Up @@ -206,7 +207,7 @@ private DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state,
};
}

private IEnumerable<Asm> Decode(ILToNativeMap map, State state, int depth, ClrMethod currentMethod, DisassemblySyntax syntax)
private IEnumerable<Asm> Decode(ILToNativeMap map, State state, int depth, IClrMethod currentMethod, DisassemblySyntax syntax)
{
ulong startAddress = map.StartAddress;
uint size = (uint)(map.EndAddress - map.StartAddress);
Expand All @@ -227,9 +228,9 @@ private IEnumerable<Asm> Decode(ILToNativeMap map, State state, int depth, ClrMe
return Decode(code, startAddress, state, depth, currentMethod, syntax);
}

protected abstract IEnumerable<Asm> Decode(byte[] code, ulong startAddress, State state, int depth, ClrMethod currentMethod, DisassemblySyntax syntax);
protected abstract IEnumerable<Asm> Decode(byte[] code, ulong startAddress, State state, int depth, IClrMethod currentMethod, DisassemblySyntax syntax);

private static ILToNativeMap[] GetCompleteNativeMap(ClrMethod method, ClrRuntime runtime)
private static ILToNativeMap[] GetCompleteNativeMap(IClrMethod method, IClrRuntime runtime)
{
// it's better to use one single map rather than few small ones
// it's simply easier to get next instruction when decoding ;)
Expand All @@ -251,10 +252,10 @@ private static ILToNativeMap[] GetCompleteNativeMap(ClrMethod method, ClrRuntime
.ToArray();
}

private static DisassembledMethod CreateEmpty(ClrMethod method, string reason)
private static DisassembledMethod CreateEmpty(IClrMethod method, string reason)
=> DisassembledMethod.Empty(method.Signature ?? "", method.NativeCode, reason);

protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD, State state, int depth, ClrMethod currentMethod)
protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD, State state, int depth, IClrMethod currentMethod)
{
if (!IsValidAddress(address) || state.AddressToNameMapping.ContainsKey(address))
return;
Expand Down
Loading
Loading