diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
index 05584f3af8..f2d34996ea 100644
--- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj
+++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
@@ -23,6 +23,7 @@
+
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
new file mode 100644
index 0000000000..450eb7d646
--- /dev/null
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
@@ -0,0 +1,360 @@
+using AsmArm64;
+using Gee.External.Capstone.Arm64;
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using AsmArm64Instruction = AsmArm64.Arm64Instruction;
+using AsmArm64Operand = AsmArm64.Arm64Operand;
+using Arm64InstructionId = AsmArm64.Arm64InstructionId;
+using CapstoneArm64Instruction = Gee.External.Capstone.Arm64.Arm64Instruction;
+using CapstoneArm64InstructionId = Gee.External.Capstone.Arm64.Arm64InstructionId;
+using CapstoneArm64Operand = Gee.External.Capstone.Arm64.Arm64Operand;
+using CapstoneArm64OperandType = Gee.External.Capstone.Arm64.Arm64OperandType;
+using CapstoneArm64RegisterId = Gee.External.Capstone.Arm64.Arm64RegisterId;
+
+namespace BenchmarkDotNet.Disassemblers;
+
+internal static class Arm64InstructionExtensions
+{
+ #region Extension methods for Capstone
+ extension(CapstoneArm64Instruction instruction)
+ {
+ public CapstoneArm64RegisterId GetRegisterId(int index)
+ {
+ var operands = instruction.Details.Operands;
+ if ((uint)index >= (uint)operands.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ return operands[index].Register.Id;
+ }
+
+ public Arm64ShiftOperation GetShiftOperation(int index)
+ {
+ var operands = instruction.Details.Operands;
+ if ((uint)index >= (uint)operands.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ var operand = operands[index];
+ var shiftOperation = operand.ShiftOperation;
+
+ switch (instruction.Id)
+ {
+ case CapstoneArm64InstructionId.ARM64_INS_ADD:
+ case CapstoneArm64InstructionId.ARM64_INS_ADRP:
+ case CapstoneArm64InstructionId.ARM64_INS_MOVK:
+ case CapstoneArm64InstructionId.ARM64_INS_MOVZ:
+ switch (shiftOperation)
+ {
+ case Arm64ShiftOperation.ARM64_SFT_LSL:
+ return shiftOperation;
+
+ case Arm64ShiftOperation.Invalid:
+ if (operand.ShiftValue == 0)
+ return Arm64ShiftOperation.ARM64_SFT_LSL; // Capstone represents LSL #0 as ARM64_SFT_INVALID.
+ goto default;
+
+ // Currently used instructions don't accept other shift operations.
+ default:
+ throw new NotSupportedException($"Unexpected shift operation({shiftOperation}) is specified on instruction({instruction.Id})");
+ }
+
+ // Currently other instructions are not supported.
+ default:
+ throw new UnreachableException($"Unexpected instruction({instruction.Id}) is specified");
+ }
+ }
+
+ public int GetShiftAmount(int index)
+ {
+ var operands = instruction.Details.Operands;
+ if ((uint)index >= (uint)operands.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ var operand = operands[index];
+
+ // Capstone represents LSL #0 as ARM64_SFT_INVALID.
+ if (operand.ShiftOperation == Arm64ShiftOperation.Invalid)
+ return 0;
+
+ return operand.ShiftValue;
+ }
+
+ public long GetRawImmediateValue(int index)
+ {
+ var operands = instruction.Details.Operands;
+ if ((uint)index >= (uint)operands.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ return operands[index].Immediate;
+ }
+
+ public long GetShiftedImmediateValue(int index)
+ {
+ var operands = instruction.Details.Operands;
+ if ((uint)index >= (uint)operands.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ var operand = operands[index];
+
+ var shiftOperation = instruction.GetShiftOperation(index);
+ switch (shiftOperation)
+ {
+ case Arm64ShiftOperation.ARM64_SFT_LSL:
+ var shiftAmount = instruction.GetShiftAmount(index);
+ return operand.Immediate << shiftAmount;
+ default:
+ throw new NotSupportedException($"Unexpected shift operation({shiftOperation}) is specified on instruction({instruction.Id})");
+ }
+ }
+
+ public bool TryGetOperand(
+ int index,
+ CapstoneArm64OperandType type,
+ [NotNullWhen(true)] out CapstoneArm64Operand? result)
+ {
+ var operands = instruction.Details.Operands;
+ if ((uint)index >= (uint)operands.Length)
+ {
+ result = null;
+ return false;
+ }
+
+ result = operands[index];
+ if (result.Type == type)
+ return true;
+
+ result = null;
+ return false;
+ }
+
+ public bool BelongsToGroup(Arm64InstructionGroupId expectedGroup)
+ {
+ if (instruction.Bytes.Length < 4)
+ return false;
+ var rawInstruction = BinaryPrimitives.ReadUInt32LittleEndian(instruction.Bytes);
+ return AsmArm64Instruction.Decode(rawInstruction).BelongsToGroup(expectedGroup);
+ }
+
+ public bool IsLdrFromTrackedRegister(CapstoneArm64RegisterId trackedRegister)
+ {
+ if (instruction.Id != CapstoneArm64InstructionId.ARM64_INS_LDR)
+ return false;
+
+ if (!instruction.TryGetOperand(1, CapstoneArm64OperandType.Memory, out var memoryOperand))
+ return false;
+
+ // Check base register is the register that is currently tracked.
+ var memory = memoryOperand.Memory;
+ if (memory.Base.Id != trackedRegister)
+ return false;
+
+ // Check displacement.
+ if (memory.Displacement != 0)
+ return false;
+
+ // Check index register is specified.
+ if (memory.Index != null)
+ return false;
+
+ // Ldr from tracked register (with no displacement/index register).
+ return true;
+ }
+
+ public bool IsConditionalBranch()
+ {
+ switch (instruction.Id)
+ {
+ case Gee.External.Capstone.Arm64.Arm64InstructionId.ARM64_INS_CBZ:
+ case Gee.External.Capstone.Arm64.Arm64InstructionId.ARM64_INS_CBNZ:
+ case Gee.External.Capstone.Arm64.Arm64InstructionId.ARM64_INS_TBZ:
+ case Gee.External.Capstone.Arm64.Arm64InstructionId.ARM64_INS_TBNZ:
+ return true;
+
+ case Gee.External.Capstone.Arm64.Arm64InstructionId.ARM64_INS_B:
+ switch (instruction.Details.ConditionCode)
+ {
+ // `B` and `B.AL` instructions is unconditional branch.
+ case Gee.External.Capstone.Arm64.Arm64ConditionCode.Invalid:
+ case Gee.External.Capstone.Arm64.Arm64ConditionCode.ARM64_CC_AL:
+ return false;
+ default:
+ return true;
+ }
+
+ default:
+ return false;
+ }
+ }
+
+ public bool IsUnconditionalControlFlow()
+ {
+ return instruction.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE)
+ || instruction.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_CALL)
+ || instruction.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_JUMP);
+ }
+
+ }
+ #endregion
+
+ #region Extension methods for AsmArm64
+ extension(AsmArm64Instruction instruction)
+ {
+ public Arm64RegisterOperand GetRegisterOperand(int index)
+ => (Arm64RegisterOperand)instruction.GetOperand(index, Arm64OperandKind.Register);
+
+ public Arm64ImmediateOperand GetImmediateOperand(int index)
+ => (Arm64ImmediateOperand)instruction.GetOperand(index, Arm64OperandKind.Immediate);
+
+ private AsmArm64Operand GetOperand(int index, Arm64OperandKind expectedKind)
+ {
+ var operand = instruction.GetOperand(index);
+ if (operand.Kind != expectedKind)
+ throw new ArgumentException($"The operand at index {index} is not of the expected kind {expectedKind}. Actual kind: {operand.Kind}");
+
+ return operand;
+ }
+
+ public bool BelongsToGroup(Arm64InstructionGroupId expectedGroup)
+ {
+ // AsmArm64 don't provide mapping for instruction groups.
+ // So it need to simulate Capstone's BelongsToGroup behavior.
+ // Capstone's group mappings are defined at https://github.com/capstone-engine/capstone/blob/6.0.0-Alpha10/arch/AArch64/AArch64GenCSMappingInsn.inc
+ switch (expectedGroup)
+ {
+ case Arm64InstructionGroupId.ARM64_GRP_JUMP:
+ switch (instruction.Id)
+ {
+ case Arm64InstructionId.B_only_branch_imm:
+ case Arm64InstructionId.B_only_condbranch:
+ case Arm64InstructionId.BC_only_condbranch:
+ case Arm64InstructionId.BR_64_branch_reg:
+ case Arm64InstructionId.BRAA_64p_branch_reg:
+ case Arm64InstructionId.BRAAZ_64_branch_reg:
+ case Arm64InstructionId.BRAB_64p_branch_reg:
+ case Arm64InstructionId.BRABZ_64_branch_reg:
+ case Arm64InstructionId.CBNZ_32_compbranch:
+ case Arm64InstructionId.CBNZ_64_compbranch:
+ case Arm64InstructionId.CBZ_32_compbranch:
+ case Arm64InstructionId.CBZ_64_compbranch:
+ case Arm64InstructionId.DRPS_64e_branch_reg:
+ case Arm64InstructionId.ERET_64e_branch_reg:
+ case Arm64InstructionId.ERETAA_64e_branch_reg:
+ case Arm64InstructionId.ERETAB_64e_branch_reg:
+ case Arm64InstructionId.RET_64r_branch_reg:
+ case Arm64InstructionId.RETAA_64e_branch_reg:
+ case Arm64InstructionId.RETAASPPCR_64m_branch_reg:
+ case Arm64InstructionId.RETAASPPC_only_miscbranch:
+ case Arm64InstructionId.RETAB_64e_branch_reg:
+ case Arm64InstructionId.RETABSPPCR_64m_branch_reg:
+ case Arm64InstructionId.RETABSPPC_only_miscbranch:
+ case Arm64InstructionId.TBNZ_only_testbranch:
+ case Arm64InstructionId.TBZ_only_testbranch:
+ return true;
+
+ default:
+ return false;
+ }
+
+ case Arm64InstructionGroupId.ARM64_GRP_CALL:
+ switch (instruction.Id)
+ {
+ case Arm64InstructionId.BL_only_branch_imm:
+ case Arm64InstructionId.BLR_64_branch_reg: // BLR: Branch with link
+ case Arm64InstructionId.BLRAA_64p_branch_reg: // BLRAA: Branch with link to register with PAC
+ case Arm64InstructionId.BLRAB_64p_branch_reg: // BLRAB: Branch with link to register with PAC
+ case Arm64InstructionId.BLRAAZ_64_branch_reg: // Z: Zero modifier
+ case Arm64InstructionId.BLRABZ_64_branch_reg: // Z: Zero modifier
+ case Arm64InstructionId.HVC_ex_exception:
+ case Arm64InstructionId.SMC_ex_exception:
+ case Arm64InstructionId.SVC_ex_exception:
+ return true;
+ default:
+ return false;
+ }
+ case Arm64InstructionGroupId.ARM64_GRP_RET:
+ switch (instruction.Id)
+ {
+ case Arm64InstructionId.DRPS_64e_branch_reg:
+ case Arm64InstructionId.ERET_64e_branch_reg:
+ case Arm64InstructionId.ERETAA_64e_branch_reg:
+ case Arm64InstructionId.ERETAB_64e_branch_reg:
+ case Arm64InstructionId.RET_64r_branch_reg:
+ case Arm64InstructionId.RETAA_64e_branch_reg:
+ case Arm64InstructionId.RETAASPPC_only_miscbranch:
+ case Arm64InstructionId.RETAASPPCR_64m_branch_reg:
+ case Arm64InstructionId.RETAB_64e_branch_reg:
+ case Arm64InstructionId.RETABSPPC_only_miscbranch:
+ case Arm64InstructionId.RETABSPPCR_64m_branch_reg:
+ return true;
+ default:
+ return false;
+ }
+ case Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE:
+ switch (instruction.Id)
+ {
+ case Arm64InstructionId.B_only_branch_imm:
+ case Arm64InstructionId.B_only_condbranch:
+ case Arm64InstructionId.BC_only_condbranch:
+ case Arm64InstructionId.BL_only_branch_imm:
+ case Arm64InstructionId.CBNZ_32_compbranch:
+ case Arm64InstructionId.CBNZ_64_compbranch:
+ case Arm64InstructionId.CBZ_32_compbranch:
+ case Arm64InstructionId.CBZ_64_compbranch:
+ case Arm64InstructionId.RETAASPPC_only_miscbranch:
+ case Arm64InstructionId.RETABSPPC_only_miscbranch:
+ case Arm64InstructionId.TBNZ_only_testbranch:
+ case Arm64InstructionId.TBZ_only_testbranch:
+ return true;
+ default:
+ return false;
+ }
+
+ // Currently, other group ids are not supported.
+ case Arm64InstructionGroupId.Invalid:
+ case Arm64InstructionGroupId.ARM64_GRP_INT:
+ case Arm64InstructionGroupId.ARM64_GRP_PRIVILEGE:
+ case Arm64InstructionGroupId.ARM64_GRP_CRYPTO:
+ case Arm64InstructionGroupId.ARM64_GRP_FPARMV8:
+ case Arm64InstructionGroupId.ARM64_GRP_NEON:
+ case Arm64InstructionGroupId.ARM64_GRP_CRC:
+ default:
+ throw new NotSupportedException($"Group {expectedGroup} is not supported.");
+ }
+ }
+
+ public IEnumerable GetWrittenRegisters()
+ {
+ foreach (var operand in instruction.Operands)
+ {
+ switch (operand.Kind)
+ {
+ case Arm64OperandKind.Register:
+ var registerOperand = (Arm64RegisterOperand)operand;
+
+ if ((registerOperand.Flags & Arm64OperandFlags.Write) != 0)
+ yield return registerOperand.Value;
+
+ continue;
+
+ case Arm64OperandKind.RegisterGroup:
+ var registerGroupOperand = (Arm64RegisterGroupOperand)operand;
+
+ if ((registerGroupOperand.Flags & Arm64OperandFlags.Write) != 0)
+ {
+ var registerGroupAny = registerGroupOperand.Value;
+ var baseRegister = registerGroupAny.BaseRegister;
+ for (int i = 0; i < registerGroupAny.Count; ++i)
+ {
+ yield return Arm64RegisterAny.Create(baseRegister.Kind, baseRegister.Index + i, baseRegister.VKind, baseRegister.ElementCount, baseRegister.ElementIndex);
+ }
+ }
+ continue;
+
+ default:
+ continue;
+ }
+ }
+ }
+ }
+ #endregion
+}
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
index debc211177..b970942806 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
@@ -14,7 +14,7 @@ internal enum State
}
private State _state;
- private long _value;
+ private long _value; // TODO: Change address value type to ulong (address with offset value)
private int _expectedMovkShift;
private Arm64RegisterId _registerId;
private IClrRuntime _runtime;
@@ -39,106 +39,215 @@ internal void Reset(
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;
- }
+ if (TryHandleLookingForPattern(instruction))
+ return;
+
+ // TODO: Check register overwrite.
+ // goto default;
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;
- }
+ if (TryHandleExpectingMovk(instruction))
+ return;
+
+ // If we didn't find a expecting MOVK instruction, we might be looking for a possible LDR
_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;
- }
+ if (TryHandleExpectingAdd(instruction))
+ return;
+
+ // TODO: Check register overwrite.
+ // goto default;
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;
- }
- }
- }
+ if (TryHandleLdrFromTrackedRegister(instruction))
+ return;
+
+ if (TryHandleConditionalInstruction(instruction))
+ return;
+
+ if (TryHandleAnotherMovzInstruction(instruction))
+ return;
+
+ goto default;
+
+ default:
+ ResetIfTrackedRegisterIsOverwritten(instruction);
break;
}
}
+ // TODO: Include ExpectingAdd check. (ADRP instruction without ADD)
public bool HasValue => _state == State.ExpectingMovk || _state == State.LookingForPossibleLdr;
public long Value => _value;
public Arm64RegisterId RegisterId => _registerId;
+
+ private bool TryHandleLookingForPattern(Arm64Instruction instruction)
+ {
+ switch (instruction.Id)
+ {
+ case Arm64InstructionId.ARM64_INS_MOVZ:
+ StartMovzSequence(instruction);
+ return true;
+ case Arm64InstructionId.ARM64_INS_ADRP:
+ StartAdrpSequence(instruction);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private bool TryHandleExpectingMovk(Arm64Instruction instruction)
+ {
+ if (instruction.Id != Arm64InstructionId.ARM64_INS_MOVK)
+ return false;
+
+ if (instruction.GetRegisterId(0) != _registerId)
+ return false;
+
+ // TODO: Remove this condition because MOVK accept only LSL.
+ var details = instruction.Details;
+ if (instruction.GetShiftOperation(1) != Arm64ShiftOperation.ARM64_SFT_LSL)
+ return false;
+
+ if (instruction.GetShiftAmount(1) != _expectedMovkShift)
+ return false;
+
+ // TODO: Clear existing 16-bits values before setting MOVK immediate value.
+ _value |= instruction.GetShiftedImmediateValue(1);
+ _expectedMovkShift += 16;
+ return true;
+ }
+
+ ///
+ /// Handle following expecting ADD instruction that source/target registers match value tracked register.
+ /// ADD Xd|SP, Xn|SP, #imm, {shift}
+ /// ADD Wd|SP, Wn|SP, #imm, {shift}
+ /// If condition is not matched. skip processing. and wait succeeding ADD instruction.
+ ///
+ private bool TryHandleExpectingAdd(Arm64Instruction instruction)
+ {
+ if (instruction.Id != Arm64InstructionId.ARM64_INS_ADD)
+ return false;
+
+ if (instruction.GetRegisterId(0) != _registerId)
+ return false;
+
+ if (instruction.GetRegisterId(1) != _registerId)
+ return false;
+
+ if (!instruction.TryGetOperand(2, Arm64OperandType.Immediate, out var operand))
+ return false;
+
+ // TODO: Replace to use shifted immediate value.
+ _value += operand.Immediate;
+ _state = State.LookingForPossibleLdr;
+ return true;
+ }
+
+ private void StartMovzSequence(Arm64Instruction instruction)
+ {
+ _registerId = instruction.GetRegisterId(0);
+ _value = instruction.GetRawImmediateValue(1); // TODO: Replace to shifted immediate value.
+ _expectedMovkShift = 16;
+ _state = State.ExpectingMovk;
+ }
+
+ private void StartAdrpSequence(Arm64Instruction instruction)
+ {
+ _registerId = instruction.GetRegisterId(0);
+ _value = instruction.GetRawImmediateValue(1);// TODO: Replace to shifted immediate value.
+ _state = State.ExpectingAdd;
+ }
+
+ private bool TryHandleLdrFromTrackedRegister(Arm64Instruction instruction)
+ {
+ if (!instruction.IsLdrFromTrackedRegister(_registerId))
+ return false;
+
+ // Simulate the LDR instruction.
+ var newValue = (long)_runtime.DataTarget.DataReader.ReadPointer((ulong)_value);
+ _value = newValue;
+ if (_value == 0)
+ {
+ // TODO: Call Reset() to clear internal state.
+ _state = State.LookingForPattern;
+ return true;
+ }
+
+ // The LDR might have loaded the result in another register
+ _registerId = instruction.GetRegisterId(0);
+ return true;
+ }
+
+ private bool TryHandleConditionalInstruction(Arm64Instruction instruction)
+ {
+ // TODO: Replace to AsmArm64 based IsConditionalBranch implementation
+ if (IsConditionalBranch(instruction))
+ return true;
+
+ // TODO: Replace to AsmArm64 based IsUnconditionalControlFlow implementation
+ if (IsUnconditionalControlFlow(instruction))
+ {
+ // We've encountered an unconditional jump or call, the accumulated registers value is not valid anymore
+ Reset();
+ _state = State.LookingForPattern;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Handle MOVZ instruction that load value from tracked register to another register.
+ ///
+ private bool TryHandleAnotherMovzInstruction(Arm64Instruction instruction)
+ {
+ if (instruction.Id != Arm64InstructionId.ARM64_INS_MOVZ)
+ return false;
+
+ // Another constant loading is starting, reprocess it as a new pattern.
+ _state = State.LookingForPattern;
+ return TryHandleLookingForPattern(instruction);
+ }
+
+ // TODO: Remove this method and replace to AsmArm64 based implementation.
+ private static bool IsConditionalBranch(Arm64Instruction instruction)
+ {
+ return instruction.Id == Arm64InstructionId.ARM64_INS_CBZ
+ || instruction.Id == Arm64InstructionId.ARM64_INS_CBNZ
+ || (instruction.Id == Arm64InstructionId.ARM64_INS_B && instruction.Details.ConditionCode != Arm64ConditionCode.Invalid);
+ }
+
+ // TODO: Remove this method and replace to AsmArm64 based implementation.
+ private static bool IsUnconditionalControlFlow(Arm64Instruction instruction)
+ {
+ var details = instruction.Details;
+ return details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE)
+ || details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_CALL)
+ || details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_JUMP);
+ }
+
+ private void ResetIfTrackedRegisterIsOverwritten(Arm64Instruction instruction)
+ {
+ // 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 writtenRegister in instruction.Details.AllWrittenRegisters)
+ {
+ if (writtenRegister.Id != _registerId)
+ continue;
+
+ // Some unexpected instruction overwriting the accumulated register
+ // TODO: Use Reset() to clear internal state.
+ _state = State.LookingForPattern;
+ }
+ }
}
diff --git a/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj b/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
index 40240b4ec5..2b95d20bae 100755
--- a/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
+++ b/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
@@ -19,7 +19,6 @@
-
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
index 06eb3e68d6..30684ebdad 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
@@ -136,5 +136,28 @@ public void MovzThenLdrLiteral_ShouldNotHaveValue()
// TODO: Current accumulator don't reset registerId.
// accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
}
+
+ [Fact]
+ public void MovzThenLdr_ZeroAddress_ShouldNotHaveValue()
+ {
+ // Arrange
+ using var clrRuntime = new MockMemory()
+ .AddPointer(MovzOffset, 0)
+ .ToMockClrRuntime();
+ var accumulator = CreateValueAccumulator(clrRuntime);
+ var instructions = new[]
+ {
+ Arm64TestInstructions.Movz(X0, MovzOffset), // movz x0, #0x1000
+ Arm64TestInstructions.Ldr(X1, baseRegister: X0), // ldr x1, [x0]
+ };
+ PrintInstructions(instructions);
+
+ // Act
+ accumulator.Feed(instructions[0]);
+ accumulator.Feed(instructions[1]);
+
+ // Assert
+ accumulator.HasValue.Should().BeFalse(); // When LDR load zero. Accumulated state is expected to be resetted.
+ }
}
#endif