From aed65a2dab82cd8a90564b2696421116d7985eb0 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Wed, 23 Sep 2026 19:59:10 +0900
Subject: [PATCH 01/14] chore: move asmarm64 reference to core library
---
src/BenchmarkDotNet/BenchmarkDotNet.csproj | 1 +
tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj | 3 +--
2 files changed, 2 insertions(+), 2 deletions(-)
mode change 100755 => 100644 tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
index d21c28cbc2..e023399587 100644
--- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj
+++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
@@ -23,6 +23,7 @@
+
diff --git a/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj b/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
old mode 100755
new mode 100644
index 29926b1652..11f7eb7ae4
--- a/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
+++ b/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
@@ -19,7 +19,6 @@
-
@@ -38,7 +37,7 @@
-
+
From 9c93250b4f8dd1f18a04a712f5d60db6d82f4dd8 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Tue, 29 Sep 2026 19:41:21 +0900
Subject: [PATCH 02/14] chore: refactor ValueAccumulator logics
---
.../Arm64InstructionExtensions.cs | 360 ++++++++++++++++++
.../Arm64RegisterValueAccumulator.cs | 277 ++++++++++----
...gisterValueAccumulatorTests.MovzThenLdr.cs | 23 ++
3 files changed, 576 insertions(+), 84 deletions(-)
create mode 100644 src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
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 2fc4e23ce4..e61b072415 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/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
From 7d40afcc62672fab3da6f5bedac8efa629f36964 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Sat, 17 Oct 2026 05:00:31 +0900
Subject: [PATCH 03/14] chore: add Arm64InstructionExtensions decoding helpers
---
.../Arm64InstructionExtensions.cs | 25 +++++++++++++++----
1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
index 450eb7d646..f5e8804401 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
@@ -25,7 +25,11 @@ public CapstoneArm64RegisterId GetRegisterId(int index)
if ((uint)index >= (uint)operands.Length)
throw new ArgumentOutOfRangeException(nameof(index));
- return operands[index].Register.Id;
+ var operand = operands[index];
+ if (operand.Type != CapstoneArm64OperandType.Register)
+ throw new ArgumentException($"Operand at index {index} is not a register (actual: {operand.Type}).");
+
+ return operand.Register.Id;
}
public Arm64ShiftOperation GetShiftOperation(int index)
@@ -48,10 +52,10 @@ public Arm64ShiftOperation GetShiftOperation(int index)
case Arm64ShiftOperation.ARM64_SFT_LSL:
return shiftOperation;
+ // Capstone represents `LSL #0` as ARM64_SFT_INVALID.
+ // So handle ARM64_SFT_INVALID as `LSL` for these instructions. (Other shift operation is not used currently)
case Arm64ShiftOperation.Invalid:
- if (operand.ShiftValue == 0)
- return Arm64ShiftOperation.ARM64_SFT_LSL; // Capstone represents LSL #0 as ARM64_SFT_INVALID.
- goto default;
+ return Arm64ShiftOperation.ARM64_SFT_LSL;
// Currently used instructions don't accept other shift operations.
default:
@@ -88,7 +92,18 @@ public long GetRawImmediateValue(int index)
return operands[index].Immediate;
}
+ ///
+ /// Gets shifted immediate value as signed value (e.g. ADRP page offset can be negative).
+ ///
public long GetShiftedImmediateValue(int index)
+ => unchecked((long)instruction.GetUnsignedShiftedImmediateValue(index));
+
+ ///
+ /// Gets shifted immediate value as unsigned value (e.g. MOVZ/MOVK/ADD address construction).
+ /// Shift is performed in ulong to avoid signed overflow
+ /// (e.g. movz x0, #0xFFFF, lsl #48 exceeds long.MaxValue)
+ ///
+ public ulong GetUnsignedShiftedImmediateValue(int index)
{
var operands = instruction.Details.Operands;
if ((uint)index >= (uint)operands.Length)
@@ -101,7 +116,7 @@ public long GetShiftedImmediateValue(int index)
{
case Arm64ShiftOperation.ARM64_SFT_LSL:
var shiftAmount = instruction.GetShiftAmount(index);
- return operand.Immediate << shiftAmount;
+ return unchecked((ulong)operand.Immediate << shiftAmount);
default:
throw new NotSupportedException($"Unexpected shift operation({shiftOperation}) is specified on instruction({instruction.Id})");
}
From a9b97477fa42ecd5a0b1c1d2123c9eea147ae65c Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Sat, 17 Oct 2026 05:03:22 +0900
Subject: [PATCH 04/14] chore: refactor Arm64RegisterValueAccumulator into
TryHandle methods
---
.../Arm64RegisterValueAccumulator.cs | 261 ++++++++++--------
1 file changed, 140 insertions(+), 121 deletions(-)
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
index e61b072415..03208543a7 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
@@ -1,5 +1,6 @@
using Gee.External.Capstone.Arm64;
using Microsoft.Diagnostics.Runtime.Interfaces;
+using System.Diagnostics;
namespace BenchmarkDotNet.Disassemblers;
@@ -14,7 +15,7 @@ internal enum State
}
private State _state;
- private long _value; // TODO: Change address value type to ulong (address with offset value)
+ private long _value;
private int _expectedMovkShift;
private Arm64RegisterId _registerId;
private IClrRuntime _runtime;
@@ -44,46 +45,41 @@ public void Feed(Arm64Instruction instruction)
case State.LookingForPattern:
if (TryHandleLookingForPattern(instruction))
return;
-
- // TODO: Check register overwrite.
- // goto default;
break;
case State.ExpectingMovk:
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 (TryHandleExpectingAdd(instruction))
return;
-
- // TODO: Check register overwrite.
- // goto default;
break;
case State.LookingForPossibleLdr:
if (TryHandleLdrFromTrackedRegister(instruction))
return;
- if (TryHandleConditionalInstruction(instruction))
- return;
-
- if (TryHandleAnotherMovzInstruction(instruction))
+ if (TryHandleBranchOrRestart(instruction))
return;
- goto default;
-
- default:
- ResetIfTrackedRegisterIsOverwritten(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 reg in instruction.Details.AllWrittenRegisters)
+ {
+ // Some unexpected instruction overwriting the accumulated register
+ if (reg.Id == _registerId)
+ {
+ _state = State.LookingForPattern;
+ }
+ }
break;
}
}
- // TODO: Include ExpectingAdd check. (ADRP instruction without ADD)
public bool HasValue => _state == State.ExpectingMovk || _state == State.LookingForPossibleLdr;
public long Value => _value;
@@ -95,36 +91,80 @@ private bool TryHandleLookingForPattern(Arm64Instruction instruction)
switch (instruction.Id)
{
case Arm64InstructionId.ARM64_INS_MOVZ:
- StartMovzSequence(instruction);
- return true;
+ return TryStartMovzSequence(instruction);
case Arm64InstructionId.ARM64_INS_ADRP:
- StartAdrpSequence(instruction);
- return true;
+ return TryStartAdrpSequence(instruction);
default:
return false;
}
}
- private bool TryHandleExpectingMovk(Arm64Instruction instruction)
+ private bool TryStartMovzSequence(Arm64Instruction instruction)
{
- if (instruction.Id != Arm64InstructionId.ARM64_INS_MOVK)
- return false;
+ try
+ {
+ if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _))
+ return false;
- if (instruction.GetRegisterId(0) != _registerId)
- return false;
+ if (!instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
+ return false;
- // TODO: Remove this condition because MOVK accept only LSL.
- var details = instruction.Details;
- if (instruction.GetShiftOperation(1) != Arm64ShiftOperation.ARM64_SFT_LSL)
+ _registerId = instruction.GetRegisterId(0);
+ _value = instruction.GetRawImmediateValue(1);
+ _state = State.ExpectingMovk;
+ _expectedMovkShift = 16;
+ return true;
+ }
+ catch (Exception ex) when (IsDecodingException(ex))
+ {
return false;
+ }
+ }
+
+ private bool TryStartAdrpSequence(Arm64Instruction instruction)
+ {
+ try
+ {
+ if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _))
+ return false;
- if (instruction.GetShiftAmount(1) != _expectedMovkShift)
+ if (!instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
+ return false;
+
+ _registerId = instruction.GetRegisterId(0);
+ _value = instruction.GetRawImmediateValue(1);
+ _state = State.ExpectingAdd;
+ return true;
+ }
+ catch (Exception ex) when (IsDecodingException(ex))
+ {
return false;
+ }
+ }
+
+ private bool TryHandleExpectingMovk(Arm64Instruction instruction)
+ {
+ try
+ {
+ if (instruction.Id != Arm64InstructionId.ARM64_INS_MOVK)
+ return false;
+
+ if (instruction.GetRegisterId(0) != _registerId)
+ return false;
+
+ Arm64InstructionDetail details = instruction.Details;
+ if (details.Operands[1].ShiftOperation != Arm64ShiftOperation.ARM64_SFT_LSL
+ || details.Operands[1].ShiftValue != _expectedMovkShift)
+ return false;
- // TODO: Clear existing 16-bits values before setting MOVK immediate value.
- _value |= instruction.GetShiftedImmediateValue(1);
- _expectedMovkShift += 16;
- return true;
+ _value = _value | (instruction.GetRawImmediateValue(1) << details.Operands[1].ShiftValue);
+ _expectedMovkShift += 16;
+ return true;
+ }
+ catch (Exception ex) when (IsDecodingException(ex))
+ {
+ return false;
+ }
}
///
@@ -135,71 +175,84 @@ private bool TryHandleExpectingMovk(Arm64Instruction instruction)
///
private bool TryHandleExpectingAdd(Arm64Instruction instruction)
{
- if (instruction.Id != Arm64InstructionId.ARM64_INS_ADD)
- return false;
+ try
+ {
+ if (instruction.Id != Arm64InstructionId.ARM64_INS_ADD)
+ return false;
- if (instruction.GetRegisterId(0) != _registerId)
- return false;
+ if (instruction.GetRegisterId(0) != _registerId)
+ return false;
- if (instruction.GetRegisterId(1) != _registerId)
- return false;
+ if (instruction.GetRegisterId(1) != _registerId)
+ return false;
- if (!instruction.TryGetOperand(2, Arm64OperandType.Immediate, out var operand))
- return false;
+ if (!instruction.TryGetOperand(2, Arm64OperandType.Immediate, out _))
+ return false;
- // TODO: Replace to use shifted immediate value.
- _value += operand.Immediate;
- _state = State.LookingForPossibleLdr;
- return true;
+ _value = _value | instruction.GetRawImmediateValue(2);
+ _state = State.LookingForPossibleLdr;
+ return true;
+ }
+ catch (Exception ex) when (IsDecodingException(ex))
+ {
+ return false;
+ }
}
- private void StartMovzSequence(Arm64Instruction instruction)
+ private bool TryHandleLdrFromTrackedRegister(Arm64Instruction instruction)
{
- _registerId = instruction.GetRegisterId(0);
- _value = instruction.GetRawImmediateValue(1); // TODO: Replace to shifted immediate value.
- _expectedMovkShift = 16;
- _state = State.ExpectingMovk;
- }
+ try
+ {
+ if (!instruction.IsLdrFromTrackedRegister(_registerId))
+ return false;
- private void StartAdrpSequence(Arm64Instruction instruction)
- {
- _registerId = instruction.GetRegisterId(0);
- _value = instruction.GetRawImmediateValue(1);// TODO: Replace to shifted immediate value.
- _state = State.ExpectingAdd;
+ // 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 = instruction.GetRegisterId(0);
+ }
+
+ return true;
+ }
+ catch (Exception ex) when (IsDecodingException(ex))
+ {
+ return false;
+ }
}
- private bool TryHandleLdrFromTrackedRegister(Arm64Instruction instruction)
+ private bool TryHandleBranchOrRestart(Arm64Instruction instruction)
{
- if (!instruction.IsLdrFromTrackedRegister(_registerId))
- return false;
+ Arm64InstructionDetail details = instruction.Details;
- // Simulate the LDR instruction.
- var newValue = (long)_runtime.DataTarget.DataReader.ReadPointer((ulong)_value);
- _value = newValue;
- if (_value == 0)
+ if (instruction.Id == Arm64InstructionId.ARM64_INS_CBZ ||
+ instruction.Id == Arm64InstructionId.ARM64_INS_CBNZ ||
+ instruction.Id == Arm64InstructionId.ARM64_INS_B && details.ConditionCode != Arm64ConditionCode.Invalid)
{
- // TODO: Call Reset() to clear internal state.
- _state = State.LookingForPattern;
+ // ignore conditional branches
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))
+ 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;
return true;
+ }
- // TODO: Replace to AsmArm64 based IsUnconditionalControlFlow implementation
- if (IsUnconditionalControlFlow(instruction))
+ if (instruction.Id == Arm64InstructionId.ARM64_INS_MOVZ)
{
- // We've encountered an unconditional jump or call, the accumulated registers value is not valid anymore
- Reset();
+ // Another constant loading is starting: restart matching from LookingForPattern.
_state = State.LookingForPattern;
+ TryStartMovzSequence(instruction);
return true;
}
@@ -207,47 +260,13 @@ private bool TryHandleConditionalInstruction(Arm64Instruction instruction)
}
///
- /// Handle MOVZ instruction that load value from tracked register to another register.
+ /// Only decoding failures (missing/mistyped operands, unexpected shift/instruction)
+ /// are treated as "not matched". Anything else (e.g. OOM) must propagate.
///
- 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;
- }
- }
+ private static bool IsDecodingException(Exception ex)
+ => ex is ArgumentOutOfRangeException
+ or ArgumentException
+ or NotSupportedException
+ or InvalidOperationException
+ or UnreachableException;
}
From d58b4826caa1b35a6f73df917ebffea038b176d1 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Sat, 17 Oct 2026 05:04:44 +0900
Subject: [PATCH 05/14] chore: change Arm64RegisterValueAccumulator address
model to ulong with offset
---
.../Arm64RegisterValueAccumulator.cs | 72 +++++++++++++++----
...sassemblerTests.TryGetReferencedAddress.cs | 2 +-
...gisterValueAccumulatorTests.AdrpThenAdd.cs | 7 +-
3 files changed, 65 insertions(+), 16 deletions(-)
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
index 03208543a7..89408a7532 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
@@ -15,7 +15,8 @@ internal enum State
}
private State _state;
- private long _value;
+ private ulong _addressValue;
+ private long _addressOffset;
private int _expectedMovkShift;
private Arm64RegisterId _registerId;
private IClrRuntime _runtime;
@@ -29,12 +30,13 @@ public void Init(IClrRuntime runtime)
internal void Reset(
State state = State.LookingForPattern,
int expectedMovkShift = 0,
- long value = 0,
+ ulong value = 0,
Arm64RegisterId registerId = Arm64RegisterId.Invalid)
{
_state = state;
_expectedMovkShift = expectedMovkShift;
- _value = value;
+ _addressValue = value;
+ _addressOffset = 0;
_registerId = registerId;
}
@@ -80,9 +82,39 @@ public void Feed(Arm64Instruction instruction)
}
}
- public bool HasValue => _state == State.ExpectingMovk || _state == State.LookingForPossibleLdr;
+ public bool HasValue
+ {
+ get
+ {
+ // Value == 0 means invalid (negative combined clamps to 0, LDR resets on 0),
+ // So it is excluded to avoid a useless address lookup in the disassembler.
+ if (Value == 0)
+ return false;
+
+ return _state == State.ExpectingMovk || _state == State.LookingForPossibleLdr;
+ }
+ }
- public long Value => _value;
+ ///
+ /// Gets accumulated address value.
+ /// Address arithmetic wraps modulo 2^64, like hardware.
+ /// A negative combined value is invalid as a real address, so 0 is returned
+ /// (0 is already treated as invalid: LDR resets on 0, disassembler filters it).
+ ///
+ public ulong Value
+ {
+ get
+ {
+ if (_addressOffset < 0)
+ {
+ // Magnitude of the negative offset, safe even for long.MinValue.
+ ulong magnitude = unchecked((ulong)(-(_addressOffset + 1)) + 1);
+ if (_addressValue < magnitude)
+ return 0;
+ }
+ return unchecked(_addressValue + (ulong)_addressOffset);
+ }
+ }
public Arm64RegisterId RegisterId => _registerId;
@@ -110,7 +142,8 @@ private bool TryStartMovzSequence(Arm64Instruction instruction)
return false;
_registerId = instruction.GetRegisterId(0);
- _value = instruction.GetRawImmediateValue(1);
+ _addressValue = (ulong)instruction.GetRawImmediateValue(1);
+ _addressOffset = 0;
_state = State.ExpectingMovk;
_expectedMovkShift = 16;
return true;
@@ -132,7 +165,8 @@ private bool TryStartAdrpSequence(Arm64Instruction instruction)
return false;
_registerId = instruction.GetRegisterId(0);
- _value = instruction.GetRawImmediateValue(1);
+ _addressOffset = instruction.GetShiftedImmediateValue(1);
+ _addressValue = 0;
_state = State.ExpectingAdd;
return true;
}
@@ -157,7 +191,7 @@ private bool TryHandleExpectingMovk(Arm64Instruction instruction)
|| details.Operands[1].ShiftValue != _expectedMovkShift)
return false;
- _value = _value | (instruction.GetRawImmediateValue(1) << details.Operands[1].ShiftValue);
+ _addressValue = _addressValue | (ulong)instruction.GetRawImmediateValue(1) << details.Operands[1].ShiftValue;
_expectedMovkShift += 16;
return true;
}
@@ -189,7 +223,7 @@ private bool TryHandleExpectingAdd(Arm64Instruction instruction)
if (!instruction.TryGetOperand(2, Arm64OperandType.Immediate, out _))
return false;
- _value = _value | instruction.GetRawImmediateValue(2);
+ _addressValue = _addressValue | (ulong)instruction.GetRawImmediateValue(2);
_state = State.LookingForPossibleLdr;
return true;
}
@@ -207,9 +241,23 @@ private bool TryHandleLdrFromTrackedRegister(Arm64Instruction instruction)
return false;
// Simulate the LDR instruction.
- long newValue = (long)_runtime.DataTarget.DataReader.ReadPointer((ulong)_value);
- _value = newValue;
- if (_value == 0)
+ // Use combined Value: after ADRP+ADD the effective address is _addressValue + _addressOffset.
+ // Use the Try-pattern so an unreadable address drops tracking instead of aborting disassembly.
+ if (Value == 0)
+ {
+ _state = State.LookingForPattern;
+ return true;
+ }
+
+ if (!_runtime.DataTarget.DataReader.ReadPointer(Value, out ulong dereferenced))
+ {
+ _state = State.LookingForPattern;
+ return true;
+ }
+
+ _addressValue = dereferenced;
+ _addressOffset = 0;
+ if (_addressValue == 0)
{
_state = State.LookingForPattern;
}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
index 19e5553353..8ffa5cfe93 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
@@ -13,7 +13,7 @@ public partial class Arm64DisassemblerTests
{
private Arm64RegisterValueAccumulator CreateValueAccumulator(
IClrRuntime clrRuntime,
- long initialValue,
+ ulong initialValue,
Arm64RegisterId registerId = Arm64RegisterId.ARM64_REG_X0,
Arm64RegisterValueAccumulator.State state = Arm64RegisterValueAccumulator.State.ExpectingMovk)
{
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
index 986320cad4..9bfeb01b38 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
@@ -32,7 +32,7 @@ public void AdrpThenAdd_ShouldCalculateAddress()
}
[Fact]
- public void AdrpThenAdd_WithNegativeOffset_ShouldCalculateAddress()
+ public void AdrpThenAdd_WithNegativeOffset_ShouldClampToZero()
{
// Arrange
var accumulator = CreateValueAccumulator();
@@ -48,8 +48,9 @@ public void AdrpThenAdd_WithNegativeOffset_ShouldCalculateAddress()
accumulator.Feed(instructions[1]);
// Assert
- accumulator.HasValue.Should().BeTrue();
- accumulator.Value.Should().Be(-0x1000 + 0x100);
+ // A negative combined value is invalid as a real address, so it is clamped to 0.
+ accumulator.HasValue.Should().BeFalse();
+ accumulator.Value.Should().Be(0UL);
}
[Fact]
From fc4bfd7acbe39f48be971910a7f3b97fb3b4f2ba Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Sat, 17 Oct 2026 05:05:59 +0900
Subject: [PATCH 06/14] chore: use lane-clear MOVK accumulation without shift
order enforcement
---
.../Arm64RegisterValueAccumulator.cs | 22 +++++++++----------
...isterValueAccumulatorTests.MovzThenMovk.cs | 10 +++++----
2 files changed, 17 insertions(+), 15 deletions(-)
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
index 89408a7532..5812c1614f 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
@@ -17,7 +17,6 @@ internal enum State
private State _state;
private ulong _addressValue;
private long _addressOffset;
- private int _expectedMovkShift;
private Arm64RegisterId _registerId;
private IClrRuntime _runtime;
@@ -29,12 +28,10 @@ public void Init(IClrRuntime runtime)
internal void Reset(
State state = State.LookingForPattern,
- int expectedMovkShift = 0,
ulong value = 0,
Arm64RegisterId registerId = Arm64RegisterId.Invalid)
{
_state = state;
- _expectedMovkShift = expectedMovkShift;
_addressValue = value;
_addressOffset = 0;
_registerId = registerId;
@@ -142,10 +139,9 @@ private bool TryStartMovzSequence(Arm64Instruction instruction)
return false;
_registerId = instruction.GetRegisterId(0);
- _addressValue = (ulong)instruction.GetRawImmediateValue(1);
+ _addressValue = instruction.GetUnsignedShiftedImmediateValue(1);
_addressOffset = 0;
_state = State.ExpectingMovk;
- _expectedMovkShift = 16;
return true;
}
catch (Exception ex) when (IsDecodingException(ex))
@@ -186,13 +182,16 @@ private bool TryHandleExpectingMovk(Arm64Instruction instruction)
if (instruction.GetRegisterId(0) != _registerId)
return false;
- Arm64InstructionDetail details = instruction.Details;
- if (details.Operands[1].ShiftOperation != Arm64ShiftOperation.ARM64_SFT_LSL
- || details.Operands[1].ShiftValue != _expectedMovkShift)
+ // Note: MOVK instruction accept only LSL shift operation.
+ var shiftAmount = instruction.GetShiftAmount(1);
+ if (shiftAmount is not (0 or 16 or 32 or 48))
return false;
- _addressValue = _addressValue | (ulong)instruction.GetRawImmediateValue(1) << details.Operands[1].ShiftValue;
- _expectedMovkShift += 16;
+ var shiftedImmediateValue = instruction.GetUnsignedShiftedImmediateValue(1);
+ const ulong mask = 0xFFFF;
+ _addressValue &= ~(mask << shiftAmount); // Clear 16-bits of MOVK target
+ _addressValue |= shiftedImmediateValue; // Set shifted immediate value
+
return true;
}
catch (Exception ex) when (IsDecodingException(ex))
@@ -223,7 +222,8 @@ private bool TryHandleExpectingAdd(Arm64Instruction instruction)
if (!instruction.TryGetOperand(2, Arm64OperandType.Immediate, out _))
return false;
- _addressValue = _addressValue | (ulong)instruction.GetRawImmediateValue(2);
+ var shiftedImmediateValue = instruction.GetUnsignedShiftedImmediateValue(2);
+ _addressValue += shiftedImmediateValue;
_state = State.LookingForPossibleLdr;
return true;
}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenMovk.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenMovk.cs
index d4fc160566..aeaf75f892 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenMovk.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenMovk.cs
@@ -49,7 +49,7 @@ public void MovzThenMovk_ShouldHaveValue()
accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
}
- [Fact(Skip = "Current value accumulator expecting sequencial 0/16/32/48 shfit for MOVK.")]
+ [Fact]
public void MovzThenMovk_WithSameShiftValue_ShouldHasAccumulatedValue()
{
// Arrange
@@ -66,7 +66,7 @@ public void MovzThenMovk_WithSameShiftValue_ShouldHasAccumulatedValue()
}
[Fact]
- public void MovzThenMovk_WithUnexpectedShift_ShouldResetValue()
+ public void MovzThenMovk_WithSkippedShift_ShouldAccumulateValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
@@ -78,10 +78,12 @@ public void MovzThenMovk_WithUnexpectedShift_ShouldResetValue()
// Act
accumulator.Feed(instructions[0]);
- accumulator.Feed(instructions[1]); // Reset accumulated value, because _expectedMovkShift is not matched.
+ accumulator.Feed(instructions[1]); // Each MOVK clears and sets its own 16-bit lane, so order does not matter.
// Assert
- accumulator.HasValue.Should().BeFalse();
+ accumulator.HasValue.Should().BeTrue();
+ accumulator.Value.Should().Be(0x5678_0000_1234UL);
+ accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
}
[Fact]
From a3ffec8c3e6ec0844eef1d41e190e908b6356e5e Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Sat, 17 Oct 2026 05:07:47 +0900
Subject: [PATCH 07/14] chore: broaden Arm64RegisterValueAccumulator tracking
continuation
---
.../Arm64RegisterValueAccumulator.cs | 86 ++++++++++++-------
...gisterValueAccumulatorTests.AdrpThenAdd.cs | 7 +-
...ulatorTests.MovzThenUnConditionalBranch.cs | 11 +--
3 files changed, 66 insertions(+), 38 deletions(-)
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
index 5812c1614f..a55d8198ba 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
@@ -56,26 +56,32 @@ public void Feed(Arm64Instruction instruction)
case State.ExpectingAdd:
if (TryHandleExpectingAdd(instruction))
return;
- break;
+
+ goto default;
case State.LookingForPossibleLdr:
if (TryHandleLdrFromTrackedRegister(instruction))
return;
- if (TryHandleBranchOrRestart(instruction))
+ // Try handle MOVZ/ADRP for new constand load.
+ if (TryHandleLookingForPattern(instruction))
+ return;
+
+ goto default;
+
+ default:
+ if (TryHandleConditionalInstruction(instruction))
return;
- // 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 instruction.Details.AllWrittenRegisters)
+ // Reset accumulated state if instruction overwriting the accumulated register.
+ if (IsTrackedRegisterOverwritten(instruction))
{
- // Some unexpected instruction overwriting the accumulated register
- if (reg.Id == _registerId)
- {
- _state = State.LookingForPattern;
- }
+ Reset();
+
+ // Reprocess current instruction.
+ TryHandleLookingForPattern(instruction);
}
- break;
+ return;
}
}
@@ -88,7 +94,15 @@ public bool HasValue
if (Value == 0)
return false;
- return _state == State.ExpectingMovk || _state == State.LookingForPossibleLdr;
+ switch (_state)
+ {
+ case State.ExpectingMovk:
+ case State.LookingForPossibleLdr:
+ case State.ExpectingAdd:
+ return true;
+ default:
+ return false;
+ }
}
}
@@ -275,36 +289,46 @@ private bool TryHandleLdrFromTrackedRegister(Arm64Instruction instruction)
}
}
- private bool TryHandleBranchOrRestart(Arm64Instruction instruction)
+ private bool TryHandleConditionalInstruction(Arm64Instruction instruction)
{
- Arm64InstructionDetail details = instruction.Details;
+ if (instruction.IsConditionalBranch())
+ return true;
- if (instruction.Id == Arm64InstructionId.ARM64_INS_CBZ ||
- instruction.Id == Arm64InstructionId.ARM64_INS_CBNZ ||
- instruction.Id == Arm64InstructionId.ARM64_INS_B && details.ConditionCode != Arm64ConditionCode.Invalid)
+ if (instruction.IsUnconditionalControlFlow())
{
- // ignore conditional branches
+ Reset();
return true;
}
- if (details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE) ||
- details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_CALL) ||
- details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_JUMP))
+ return false;
+ }
+
+ private bool IsTrackedRegisterOverwritten(Arm64Instruction instruction)
+ {
+ try
{
- // We've encountered an unconditional jump or call, the accumulated registers value is not valid anymore
- _state = State.LookingForPattern;
- return true;
- }
+ // Finally check if the current instruction modified the register that was accumulating the constant
+ // and reset the state machine in case it did.
+ var details = instruction.Details;
+ if (details is null)
+ return true; // Conservative: unknown writes invalidate tracking (avoid false HasValue).
+
+ foreach (Arm64Register writtenRegister in details.AllWrittenRegisters)
+ {
+ if (writtenRegister.Id != _registerId)
+ continue;
- if (instruction.Id == Arm64InstructionId.ARM64_INS_MOVZ)
+ return true;
+ }
+
+ return false;
+ }
+ catch (Exception ex) when (IsDecodingException(ex) || ex is NullReferenceException)
{
- // Another constant loading is starting: restart matching from LookingForPattern.
- _state = State.LookingForPattern;
- TryStartMovzSequence(instruction);
+ // Conservative: on decoding failure assume overwritten so a stale value
+ // is not reported as a false-positive ReferencedAddress.
return true;
}
-
- return false;
}
///
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
index 9bfeb01b38..10a5208350 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
@@ -96,7 +96,7 @@ public void AdrpThenAdd_WithShiftedImm_ShouldCalculateAddress()
}
[Fact]
- public void AdrpThenAdd_DifferentDestinationRegister_ShouldResetValue()
+ public void AdrpThenAdd_DifferentDestinationRegister_ShouldKeepValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
@@ -112,7 +112,10 @@ public void AdrpThenAdd_DifferentDestinationRegister_ShouldResetValue()
accumulator.Feed(instructions[1]);
// Assert
- accumulator.HasValue.Should().BeFalse(); // Register is not matched
+ // The ADD writes X1, so the tracked X0 value (page address) is still valid.
+ accumulator.HasValue.Should().BeTrue();
+ accumulator.Value.Should().Be(0x2000UL);
+ accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
}
[Fact(Skip = "On current implementation, `State.ExpectingAdd` don't reset value when unexpected instruction passed.")]
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
index 06e0e47bbb..922deba92d 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
@@ -34,7 +34,7 @@ public void MovzThenUnconditionalB_ShouldResetValue()
[Fact]
- public void MovzThenTbz_ShouldResetValue()
+ public void MovzThenTbz_ShouldKeepValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
@@ -50,10 +50,11 @@ public void MovzThenTbz_ShouldResetValue()
accumulator.Feed(instructions[1]);
// Assert
- accumulator.HasValue.Should().BeFalse();
-
- // TODO: Current accumulator don't reset registerId.
- // accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
+ // TBZ is a conditional branch that does not overwrite the tracked register,
+ // so tracking continues (unlike unconditional control flow which resets).
+ accumulator.HasValue.Should().BeTrue();
+ accumulator.Value.Should().Be(0x1000UL);
+ accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
var details = instructions[1].Details;
details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_JUMP).Should().BeTrue();
From 4ca01e2691f68391deee14d93c74a23218a949b8 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Sat, 17 Oct 2026 05:11:49 +0900
Subject: [PATCH 08/14] chore: align Arm64RegisterValueAccumulator internals
with final shape
---
.../Arm64RegisterValueAccumulator.cs | 146 ++++++++++--------
...ulatorTests.MovzThenUnConditionalBranch.cs | 6 +-
2 files changed, 81 insertions(+), 71 deletions(-)
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
index a55d8198ba..de92586af5 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
@@ -50,6 +50,9 @@ public void Feed(Arm64Instruction instruction)
if (TryHandleExpectingMovk(instruction))
return;
+ // MOVK instructions are expected to be placed after MOVZ instruction.
+ // If other instruction found on ExpectingMovk state.
+ // Then state is transitioned to LookingForPossibleLdr.
_state = State.LookingForPossibleLdr;
goto case State.LookingForPossibleLdr;
@@ -89,7 +92,7 @@ public bool HasValue
{
get
{
- // Value == 0 means invalid (negative combined clamps to 0, LDR resets on 0),
+ // Value == 0 means invalid(negative combined clamps to 0, LDR resets on 0),
// So it is excluded to avoid a useless address lookup in the disassembler.
if (Value == 0)
return false;
@@ -142,50 +145,6 @@ private bool TryHandleLookingForPattern(Arm64Instruction instruction)
}
}
- private bool TryStartMovzSequence(Arm64Instruction instruction)
- {
- try
- {
- if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _))
- return false;
-
- if (!instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
- return false;
-
- _registerId = instruction.GetRegisterId(0);
- _addressValue = instruction.GetUnsignedShiftedImmediateValue(1);
- _addressOffset = 0;
- _state = State.ExpectingMovk;
- return true;
- }
- catch (Exception ex) when (IsDecodingException(ex))
- {
- return false;
- }
- }
-
- private bool TryStartAdrpSequence(Arm64Instruction instruction)
- {
- try
- {
- if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _))
- return false;
-
- if (!instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
- return false;
-
- _registerId = instruction.GetRegisterId(0);
- _addressOffset = instruction.GetShiftedImmediateValue(1);
- _addressValue = 0;
- _state = State.ExpectingAdd;
- return true;
- }
- catch (Exception ex) when (IsDecodingException(ex))
- {
- return false;
- }
- }
-
private bool TryHandleExpectingMovk(Arm64Instruction instruction)
{
try
@@ -193,6 +152,10 @@ private bool TryHandleExpectingMovk(Arm64Instruction instruction)
if (instruction.Id != Arm64InstructionId.ARM64_INS_MOVK)
return false;
+ if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _)
+ || !instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
+ return false;
+
if (instruction.GetRegisterId(0) != _registerId)
return false;
@@ -227,6 +190,10 @@ private bool TryHandleExpectingAdd(Arm64Instruction instruction)
if (instruction.Id != Arm64InstructionId.ARM64_INS_ADD)
return false;
+ if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _)
+ || !instruction.TryGetOperand(1, Arm64OperandType.Register, out _))
+ return false;
+
if (instruction.GetRegisterId(0) != _registerId)
return false;
@@ -247,6 +214,59 @@ private bool TryHandleExpectingAdd(Arm64Instruction instruction)
}
}
+ ///
+ /// Only decoding failures (missing/mistyped operands, unexpected shift/instruction)
+ /// are treated as "not matched". Anything else (e.g. OOM) must propagate.
+ ///
+ private static bool IsDecodingException(Exception ex)
+ => ex is ArgumentOutOfRangeException
+ or ArgumentException
+ or NotSupportedException
+ or InvalidOperationException
+ or UnreachableException;
+
+ private bool TryStartMovzSequence(Arm64Instruction instruction)
+ {
+ if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _))
+ return false;
+
+ if (!instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
+ return false;
+
+ try
+ {
+ _registerId = instruction.GetRegisterId(0);
+ _addressValue = instruction.GetUnsignedShiftedImmediateValue(1);
+ _addressOffset = 0;
+ _state = State.ExpectingMovk;
+ return true;
+ }
+ catch (Exception ex) when (IsDecodingException(ex))
+ {
+ return false;
+ }
+ }
+
+ private bool TryStartAdrpSequence(Arm64Instruction instruction)
+ {
+ if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _)
+ || !instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
+ return false;
+
+ try
+ {
+ _registerId = instruction.GetRegisterId(0);
+ _addressOffset = instruction.GetShiftedImmediateValue(1);
+ _addressValue = 0;
+ _state = State.ExpectingAdd;
+ return true;
+ }
+ catch (Exception ex) when (IsDecodingException(ex))
+ {
+ return false;
+ }
+ }
+
private bool TryHandleLdrFromTrackedRegister(Arm64Instruction instruction)
{
try
@@ -254,33 +274,36 @@ private bool TryHandleLdrFromTrackedRegister(Arm64Instruction instruction)
if (!instruction.IsLdrFromTrackedRegister(_registerId))
return false;
- // Simulate the LDR instruction.
- // Use combined Value: after ADRP+ADD the effective address is _addressValue + _addressOffset.
- // Use the Try-pattern so an unreadable address drops tracking instead of aborting disassembly.
+ if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _))
+ return false;
+
+ // Address 0 is never valid to dereference (e.g. `movz x0, #0` or clamped negative ADRP+ADD).
if (Value == 0)
{
- _state = State.LookingForPattern;
+ Reset();
return true;
}
+ // Simulate the LDR instruction.
+ // Use combined Value: after ADRP+ADD the effective address is _addressValue + _addressOffset.
+ // Use the Try-pattern so an unreadable address drops tracking instead of aborting disassembly.
if (!_runtime.DataTarget.DataReader.ReadPointer(Value, out ulong dereferenced))
{
- _state = State.LookingForPattern;
+ Reset();
return true;
}
_addressValue = dereferenced;
_addressOffset = 0;
+
if (_addressValue == 0)
{
- _state = State.LookingForPattern;
- }
- else
- {
- // The LDR might have loaded the result in another register
- _registerId = instruction.GetRegisterId(0);
+ Reset();
+ return true;
}
+ // The LDR might have loaded the result in another register
+ _registerId = instruction.GetRegisterId(0);
return true;
}
catch (Exception ex) when (IsDecodingException(ex))
@@ -330,15 +353,4 @@ private bool IsTrackedRegisterOverwritten(Arm64Instruction instruction)
return true;
}
}
-
- ///
- /// Only decoding failures (missing/mistyped operands, unexpected shift/instruction)
- /// are treated as "not matched". Anything else (e.g. OOM) must propagate.
- ///
- private static bool IsDecodingException(Exception ex)
- => ex is ArgumentOutOfRangeException
- or ArgumentException
- or NotSupportedException
- or InvalidOperationException
- or UnreachableException;
}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
index 922deba92d..96def412b0 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
@@ -45,15 +45,13 @@ public void MovzThenTbz_ShouldKeepValue()
};
PrintInstructions(instructions);
- // Act
+ // Act
accumulator.Feed(instructions[0]);
accumulator.Feed(instructions[1]);
// Assert
- // TBZ is a conditional branch that does not overwrite the tracked register,
- // so tracking continues (unlike unconditional control flow which resets).
accumulator.HasValue.Should().BeTrue();
- accumulator.Value.Should().Be(0x1000UL);
+ accumulator.Value.Should().Be(0x1000UL); // TBZ is a conditional branch that does not overwrite the tracked register,
accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
var details = instructions[1].Details;
From 100d68e0abf5cf6d76321386820b0af63b8de1b5 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Sat, 17 Oct 2026 06:29:23 +0900
Subject: [PATCH 09/14] chore: add/update related unit tests
---
...sassemblerTests.TryGetReferencedAddress.cs | 24 ++++++++++++++++
...gisterValueAccumulatorTests.AdrpThenAdd.cs | 4 +--
...Arm64RegisterValueAccumulatorTests.Movz.cs | 21 +++++++++++++-
...gisterValueAccumulatorTests.MovzThenLdr.cs | 28 +++++++++++++++++--
...ulatorTests.MovzThenUnConditionalBranch.cs | 4 +--
5 files changed, 73 insertions(+), 8 deletions(-)
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
index 8ffa5cfe93..006710b423 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
@@ -72,6 +72,30 @@ public void TryGetReferencedAddress_With_BLR()
isReferencedAddressIndirect.Should().BeTrue();
}
+ [Fact]
+ public void TryGetReferencedAddress_WithAdrpThenBr_ResolvesPageAddress()
+ {
+ // Arrange
+ using var clrRuntime = CreateMockClrRuntime();
+ var accumulator = new Arm64RegisterValueAccumulator();
+ accumulator.Init(clrRuntime);
+ accumulator.Feed(Arm64TestInstructions.Adrp(X0, 0x2000)); // adrp x0, #0x2000
+ var rawInstruction = Arm64InstructionFactory.BR(X0);
+
+ // Act
+ var result = Arm64DisassemblerHelper.TryGetReferencedAddress(
+ rawInstruction.ToCapstoneArm64Instruction(),
+ accumulator,
+ pointerSize: 0, // This parameter is not used.
+ out ulong referencedAddress,
+ out bool isReferencedAddressIndirect);
+
+ // Assert
+ result.Should().BeTrue();
+ referencedAddress.Should().Be(0x2000); // no ADD between ADRP and BR, so the page address itself must resolve.
+ isReferencedAddressIndirect.Should().BeTrue();
+ }
+
[Fact]
public void TryGetReferencedAddress_With_BranchRelative()
{
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
index 10a5208350..863074c770 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
@@ -74,7 +74,7 @@ public void AdrpThenAdd_ThenAdd_ShouldResetValue()
accumulator.HasValue.Should().BeFalse();
}
- [Fact(Skip = "Shifted value is not supported on current implementation.")]
+ [Fact]
public void AdrpThenAdd_WithShiftedImm_ShouldCalculateAddress()
{
// Arrange
@@ -118,7 +118,7 @@ public void AdrpThenAdd_DifferentDestinationRegister_ShouldKeepValue()
accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
}
- [Fact(Skip = "On current implementation, `State.ExpectingAdd` don't reset value when unexpected instruction passed.")]
+ [Fact]
public void AdrpThenOther_ThenAdd_ShouldResetValue()
{
// Arrange
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.Movz.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.Movz.cs
index eafb2c12bb..913c8e064c 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.Movz.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.Movz.cs
@@ -9,7 +9,7 @@ namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
// MOVZ tests.
public partial class Arm64RegisterValueAccumulatorTests
{
- [Fact(Skip = "On current implementation, shifted immediate value is not supported.")]
+ [Fact]
public void Movz_WithShiftedImmediateValue_ShouldStartNewValue()
{
// Arrange
@@ -32,5 +32,24 @@ public void Movz_WithShiftedImmediateValue_ShouldStartNewValue()
accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
accumulator.Value.Should().Be(0x3333_0000_0000);
}
+
+ [Fact]
+ public void Movz_ZeroImmediate_ShouldNotHaveValue()
+ {
+ // Arrange
+ var accumulator = CreateValueAccumulator();
+ var instructions = new[]
+ {
+ Arm64TestInstructions.Movz(X0, 0x0), // movz x0, #0x0
+ };
+ PrintInstructions(instructions);
+
+ // Act
+ accumulator.Feed(instructions[0]);
+
+ // Assert: address 0 is never valid, so it is excluded from HasValue.
+ accumulator.HasValue.Should().BeFalse();
+ accumulator.Value.Should().Be(0UL);
+ }
}
#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
index 30684ebdad..d463a18cd0 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
@@ -1,10 +1,8 @@
// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
#if NET
-using Argon;
-using AsmArm64;
using AwesomeAssertions;
using Gee.External.Capstone.Arm64;
-using System.Net;
+using System.Reflection;
using static AsmArm64.Arm64RegisterX;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -159,5 +157,29 @@ public void MovzThenLdr_ZeroAddress_ShouldNotHaveValue()
// Assert
accumulator.HasValue.Should().BeFalse(); // When LDR load zero. Accumulated state is expected to be resetted.
}
+
+ [Fact]
+ public void MovzThenLdr_UnreadableAddress_ShouldNotHaveValue()
+ {
+ // Arrange
+ using var clrRuntime = new MockMemory()
+ .AddFalsePointer(MovzOffset)
+ .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]); // No readable pointer is registered, so ReadPointer fails.
+
+ // Assert
+ accumulator.HasValue.Should().BeFalse(); // Unreadable address drops tracking instead of aborting disassembly.
+ accumulator.Value.Should().Be(0UL);
+ }
}
#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
index 96def412b0..3da52bbb4e 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
@@ -59,7 +59,7 @@ public void MovzThenTbz_ShouldKeepValue()
details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE).Should().BeTrue();
}
- [Fact(Skip = "Capstone 4.0.2 don't grouping RET instruction as JUMP group.")]
+ [Fact]
public void MovzThenRet_ShouldResetValue()
{
// Arrange
@@ -80,7 +80,7 @@ public void MovzThenRet_ShouldResetValue()
// accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
}
- [Fact(Skip = "Capstone 4.0.2 don't grouping DRPS instruction as JUMP group.")]
+ [Fact]
public void MovzThenDrps_ShouldResetValue()
{
// Arrange
From 5f4f19a801c56e9480ecd12ca292d14d284b85d3 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Wed, 4 Nov 2026 03:13:12 +0900
Subject: [PATCH 10/14] chore: refactor Arm64InstructionExtensions to use
AsmArm64
---
.../Arm64InstructionExtensions.cs | 422 +++++++++---------
.../Disassemblers/Arm64InstructionGroupId.cs | 21 +
2 files changed, 225 insertions(+), 218 deletions(-)
create mode 100644 src/BenchmarkDotNet/Disassemblers/Arm64InstructionGroupId.cs
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
index f5e8804401..0e026edfc9 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionExtensions.cs
@@ -1,174 +1,141 @@
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)
+ private static bool TryGetOperandCore(Arm64Instruction instruction, int index, Arm64OperandKind expectedKind, Func castFunc, out T result)
+ where T : struct, IArm64Operand
{
- public CapstoneArm64RegisterId GetRegisterId(int index)
+ if ((uint) index >= (uint) instruction.Operands.Count)
{
- var operands = instruction.Details.Operands;
- if ((uint)index >= (uint)operands.Length)
- throw new ArgumentOutOfRangeException(nameof(index));
-
- var operand = operands[index];
- if (operand.Type != CapstoneArm64OperandType.Register)
- throw new ArgumentException($"Operand at index {index} is not a register (actual: {operand.Type}).");
-
- return operand.Register.Id;
+ result = default;
+ return false;
}
- 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;
-
- // Capstone represents `LSL #0` as ARM64_SFT_INVALID.
- // So handle ARM64_SFT_INVALID as `LSL` for these instructions. (Other shift operation is not used currently)
- case Arm64ShiftOperation.Invalid:
- return Arm64ShiftOperation.ARM64_SFT_LSL;
-
- // Currently used instructions don't accept other shift operations.
- default:
- throw new NotSupportedException($"Unexpected shift operation({shiftOperation}) is specified on instruction({instruction.Id})");
- }
+ var operand = instruction.GetOperand(index);
- // Currently other instructions are not supported.
- default:
- throw new UnreachableException($"Unexpected instruction({instruction.Id}) is specified");
- }
+ if (operand.Kind != expectedKind)
+ {
+ result = default;
+ return false;
}
- public int GetShiftAmount(int index)
- {
- var operands = instruction.Details.Operands;
- if ((uint)index >= (uint)operands.Length)
- throw new ArgumentOutOfRangeException(nameof(index));
+ result = castFunc(operand);
+ return true;
+ }
- var operand = operands[index];
+ // Define TryGetOperand helper extension method per operand types.
+ // It's required because when using generics, it can't resolve cast (expicit operator).
+ extension(Arm64Instruction instruction)
+ {
+ public bool TryGetOperand(int index, out Arm64RegisterOperand result)
+ => TryGetOperandCore(instruction, index, Arm64OperandKind.Register, x => (Arm64RegisterOperand) x, out result);
- // Capstone represents LSL #0 as ARM64_SFT_INVALID.
- if (operand.ShiftOperation == Arm64ShiftOperation.Invalid)
- return 0;
+ public bool TryGetOperand(int index, out Arm64ImmediateOperand result)
+ => TryGetOperandCore(instruction, index, Arm64OperandKind.Immediate, x => (Arm64ImmediateOperand) x, out result);
- return operand.ShiftValue;
- }
+ public bool TryGetOperand(int index, out Arm64LabelOperand result)
+ => TryGetOperandCore(instruction, index, Arm64OperandKind.Label, x => (Arm64LabelOperand) x, out result);
- public long GetRawImmediateValue(int index)
- {
- var operands = instruction.Details.Operands;
- if ((uint)index >= (uint)operands.Length)
- throw new ArgumentOutOfRangeException(nameof(index));
+ public bool TryGetOperand(int index, out Arm64ShiftOperand result)
+ => TryGetOperandCore(instruction, index, Arm64OperandKind.Shift, x => (Arm64ShiftOperand) x, out result);
- return operands[index].Immediate;
- }
+ public bool TryGetOperand(int index, out Arm64EnumOperand result)
+ => TryGetOperandCore(instruction, index, Arm64OperandKind.Enum, x => (Arm64EnumOperand) x, out result);
- ///
- /// Gets shifted immediate value as signed value (e.g. ADRP page offset can be negative).
- ///
- public long GetShiftedImmediateValue(int index)
- => unchecked((long)instruction.GetUnsignedShiftedImmediateValue(index));
+ public bool TryGetOperand(int index, out Arm64MemoryOperand result)
+ => TryGetOperandCore(instruction, index, Arm64OperandKind.Memory, x => (Arm64MemoryOperand) x, out result);
+ }
+ extension(Arm64Instruction instruction)
+ {
///
- /// Gets shifted immediate value as unsigned value (e.g. MOVZ/MOVK/ADD address construction).
- /// Shift is performed in ulong to avoid signed overflow
- /// (e.g. movz x0, #0xFFFF, lsl #48 exceeds long.MaxValue)
+ /// Helper method to get immediate value as `ulong`.
+ /// arm64 immediate value is expected to be represented as signed long.
+ /// Note:
+ /// Some SIMD/SVE/SME instruction might have 64-bit immediate value, FP immediate value, or bitmask.
+ /// In this case, it needs to use separated API (Currently there is no code path that handles these immediate values).
///
- public ulong GetUnsignedShiftedImmediateValue(int index)
+ public bool TryGetShiftedImmediateValueUnsigned(int index, out ulong value)
{
- 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)
+ if (instruction.TryGetShiftedImmediateValue(index, out long signed))
{
- case Arm64ShiftOperation.ARM64_SFT_LSL:
- var shiftAmount = instruction.GetShiftAmount(index);
- return unchecked((ulong)operand.Immediate << shiftAmount);
- default:
- throw new NotSupportedException($"Unexpected shift operation({shiftOperation}) is specified on instruction({instruction.Id})");
+ value = (ulong) signed;
+ return true;
}
+
+ value = 0;
+ return false;
}
- public bool TryGetOperand(
- int index,
- CapstoneArm64OperandType type,
- [NotNullWhen(true)] out CapstoneArm64Operand? result)
+ public bool TryGetShiftedImmediateValue(int index, out long value)
{
- var operands = instruction.Details.Operands;
- if ((uint)index >= (uint)operands.Length)
- {
- result = null;
+ value = 0;
+ var operands = instruction.Operands;
+ if ((uint) index >= (uint) operands.Count)
return false;
- }
- result = operands[index];
- if (result.Type == type)
+ if (!instruction.TryGetOperand(index, out Arm64ImmediateOperand immediateOperand))
+ return false;
+
+ var immediateValue = immediateOperand.Value;
+
+ // Try to get shift modifier (AsmArm64 handle shift modifier as independent operand)
+ if (!instruction.TryGetOperand(index + 1, out Arm64ShiftOperand shiftOperand))
+ {
+ value = immediateValue;
return true;
+ }
- result = null;
- return false;
+ var shiftAmount = shiftOperand.Amount;
+ var shiftKind = shiftOperand.ShiftKind;
+ return TryShiftImmediateValue(immediateValue, shiftKind, shiftAmount, out value);
}
- public bool BelongsToGroup(Arm64InstructionGroupId expectedGroup)
+ ///
+ /// Gets the base address used to resolve a label operand.
+ /// ADRP encodes a page delta relative to the 4KB page containing the instruction,
+ /// while other label operands are relative to the instruction address.
+ /// (Matches AsmArm64's Arm64Disassembler.GetTargetAddress.)
+ ///
+ public ulong GetTargetAddressBase(ulong instructionAddress)
{
- if (instruction.Bytes.Length < 4)
- return false;
- var rawInstruction = BinaryPrimitives.ReadUInt32LittleEndian(instruction.Bytes);
- return AsmArm64Instruction.Decode(rawInstruction).BelongsToGroup(expectedGroup);
+ if (instruction.Mnemonic == Arm64Mnemonic.ADRP)
+ return instructionAddress & ~0xFFFUL;
+ return instructionAddress;
+ }
+
+ ///
+ /// Resolves the absolute target address of a label operand.
+ /// Address arithmetic wraps modulo 2^64, like hardware.
+ ///
+ public ulong GetTargetAddress(ulong instructionAddress, long labelOffset)
+ {
+ var targetAddress = instruction.GetTargetAddressBase(instructionAddress);
+ return unchecked(targetAddress + (ulong) labelOffset);
}
- public bool IsLdrFromTrackedRegister(CapstoneArm64RegisterId trackedRegister)
+ public bool IsLdrFromTrackedRegister(Arm64RegisterAny trackedRegister)
{
- if (instruction.Id != CapstoneArm64InstructionId.ARM64_INS_LDR)
+ if (instruction.Mnemonic != Arm64Mnemonic.LDR)
return false;
- if (!instruction.TryGetOperand(1, CapstoneArm64OperandType.Memory, out var memoryOperand))
+ if (!instruction.TryGetOperand(1, out Arm64MemoryOperand memoryOperand))
return false;
+ var memoryAccessor = memoryOperand.Accessor;
+
// Check base register is the register that is currently tracked.
- var memory = memoryOperand.Memory;
- if (memory.Base.Id != trackedRegister)
+ if (memoryAccessor.BaseRegister != trackedRegister)
return false;
// Check displacement.
- if (memory.Displacement != 0)
+ if (memoryAccessor.HasOptionalOffset && memoryAccessor.HasImmediate && memoryAccessor.Immediate != 0)
return false;
// Check index register is specified.
- if (memory.Index != null)
+ if (memoryAccessor.HasIndexRegister)
return false;
// Ldr from tracked register (with no displacement/index register).
@@ -177,20 +144,25 @@ public bool IsLdrFromTrackedRegister(CapstoneArm64RegisterId trackedRegister)
public bool IsConditionalBranch()
{
- switch (instruction.Id)
+ switch (instruction.Mnemonic)
{
- 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:
+ case Arm64Mnemonic.CBZ:
+ case Arm64Mnemonic.CBNZ:
+ case Arm64Mnemonic.TBZ:
+ case Arm64Mnemonic.TBNZ:
return true;
- case Gee.External.Capstone.Arm64.Arm64InstructionId.ARM64_INS_B:
- switch (instruction.Details.ConditionCode)
+ case Arm64Mnemonic.B:
+
+ var conditionalKind = Arm64ConditionalKind.Undefined;
+ if (instruction.TryGetOperand(0, out Arm64EnumOperand enumOperand))
+ conditionalKind = enumOperand.AsConditional;
+
+ switch (conditionalKind)
{
// `B` and `B.AL` instructions is unconditional branch.
- case Gee.External.Capstone.Arm64.Arm64ConditionCode.Invalid:
- case Gee.External.Capstone.Arm64.Arm64ConditionCode.ARM64_CC_AL:
+ case Arm64ConditionalKind.Undefined:
+ case Arm64ConditionalKind.AL:
return false;
default:
return true;
@@ -208,117 +180,89 @@ public bool IsUnconditionalControlFlow()
|| 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
+ // Capstone's group mappings are defined at https://github.com/capstone-engine/capstone/blob/6.0.0-Alpha11/arch/AArch64/AArch64GenCSMappingInsn.inc
switch (expectedGroup)
{
case Arm64InstructionGroupId.ARM64_GRP_JUMP:
- switch (instruction.Id)
+ switch (instruction.Mnemonic)
{
- 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:
+ case Arm64Mnemonic.B:
+ case Arm64Mnemonic.BC:
+ case Arm64Mnemonic.BR:
+ case Arm64Mnemonic.BRAA:
+ case Arm64Mnemonic.BRAAZ:
+ case Arm64Mnemonic.BRAB:
+ case Arm64Mnemonic.BRABZ:
+ case Arm64Mnemonic.CBNZ:
+ case Arm64Mnemonic.CBZ:
+ case Arm64Mnemonic.DRPS:
+ case Arm64Mnemonic.ERET:
+ case Arm64Mnemonic.ERETAA:
+ case Arm64Mnemonic.ERETAB:
+ case Arm64Mnemonic.RET:
+ case Arm64Mnemonic.RETAA:
+ case Arm64Mnemonic.RETAASPPC:
+ case Arm64Mnemonic.RETAASPPCR:
+ case Arm64Mnemonic.RETAB:
+ case Arm64Mnemonic.RETABSPPC:
+ case Arm64Mnemonic.RETABSPPCR:
+ case Arm64Mnemonic.TBZ:
+ case Arm64Mnemonic.TBNZ:
return true;
-
default:
return false;
}
case Arm64InstructionGroupId.ARM64_GRP_CALL:
- switch (instruction.Id)
+ switch (instruction.Mnemonic)
{
- 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:
+ case Arm64Mnemonic.BL:
+ case Arm64Mnemonic.BLR:
+ case Arm64Mnemonic.BLRAA:
+ case Arm64Mnemonic.BLRAB:
+ case Arm64Mnemonic.BLRAAZ:
+ case Arm64Mnemonic.BLRABZ:
+ case Arm64Mnemonic.HVC:
+ case Arm64Mnemonic.SMC:
+ case Arm64Mnemonic.SVC:
return true;
default:
return false;
}
case Arm64InstructionGroupId.ARM64_GRP_RET:
- switch (instruction.Id)
+ switch (instruction.Mnemonic)
{
- 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:
+ case Arm64Mnemonic.DRPS:
+ case Arm64Mnemonic.ERET:
+ case Arm64Mnemonic.ERETAA:
+ case Arm64Mnemonic.ERETAB:
+ case Arm64Mnemonic.RET:
+ case Arm64Mnemonic.RETAA:
+ case Arm64Mnemonic.RETAASPPC:
+ case Arm64Mnemonic.RETAASPPCR:
+ case Arm64Mnemonic.RETAB:
+ case Arm64Mnemonic.RETABSPPC:
+ case Arm64Mnemonic.RETABSPPCR:
return true;
default:
return false;
}
case Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE:
- switch (instruction.Id)
+ switch (instruction.Mnemonic)
{
- 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:
+ case Arm64Mnemonic.B:
+ case Arm64Mnemonic.BC:
+ case Arm64Mnemonic.BL:
+ case Arm64Mnemonic.CBNZ:
+ case Arm64Mnemonic.CBZ:
+ case Arm64Mnemonic.RETAASPPC:
+ case Arm64Mnemonic.RETABSPPC:
+ case Arm64Mnemonic.TBNZ:
+ case Arm64Mnemonic.TBZ:
return true;
default:
return false;
@@ -344,7 +288,7 @@ public IEnumerable GetWrittenRegisters()
switch (operand.Kind)
{
case Arm64OperandKind.Register:
- var registerOperand = (Arm64RegisterOperand)operand;
+ var registerOperand = (Arm64RegisterOperand) operand;
if ((registerOperand.Flags & Arm64OperandFlags.Write) != 0)
yield return registerOperand.Value;
@@ -352,7 +296,7 @@ public IEnumerable GetWrittenRegisters()
continue;
case Arm64OperandKind.RegisterGroup:
- var registerGroupOperand = (Arm64RegisterGroupOperand)operand;
+ var registerGroupOperand = (Arm64RegisterGroupOperand) operand;
if ((registerGroupOperand.Flags & Arm64OperandFlags.Write) != 0)
{
@@ -371,5 +315,47 @@ public IEnumerable GetWrittenRegisters()
}
}
}
- #endregion
+
+ ///
+ /// Applies a shift to an immediate value. Returns false for unsupported/invalid shift kinds.
+ /// Use internal modifier for unit tests.
+ ///
+ internal static bool TryShiftImmediateValue(
+ long immediateValue,
+ Arm64ShiftKind shiftKind,
+ byte shiftAmount,
+ out long result)
+ {
+ result = 0;
+ switch (shiftKind)
+ {
+ case Arm64ShiftKind.LSL:
+ result = immediateValue << shiftAmount;
+ return true;
+
+ case Arm64ShiftKind.LSR:
+ result = (long) ((ulong) immediateValue >> shiftAmount);
+ return true;
+
+ case Arm64ShiftKind.ASR:
+ result = immediateValue >> shiftAmount;
+ return true;
+
+ case Arm64ShiftKind.ROR:
+ {
+ var unsigned = (ulong) immediateValue;
+ var amount = shiftAmount & 63;
+ result = amount == 0
+ ? (long) unsigned
+ : (long) ((unsigned >> amount) | (unsigned << (64 - amount)));
+ return true;
+ }
+
+ // MSL (mask shift left) is used by bitmask immediates.
+ // Treat as unsupported to avoid producing an incorrect value.
+ case Arm64ShiftKind.MSL:
+ default:
+ return false;
+ }
+ }
}
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionGroupId.cs b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionGroupId.cs
new file mode 100644
index 0000000000..bc81c8bd1c
--- /dev/null
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionGroupId.cs
@@ -0,0 +1,21 @@
+namespace BenchmarkDotNet.Disassemblers;
+
+// Based on Capstone's instruction grouping.
+public enum Arm64InstructionGroupId
+{
+ ///
+ /// Indicates an invalid, or an uninitialized, instruction group.
+ ///
+ Invalid = 0,
+
+ ARM64_GRP_JUMP,
+ ARM64_GRP_CALL,
+ ARM64_GRP_RET,
+ ARM64_GRP_INT,
+ ARM64_GRP_PRIVILEGE = 6,
+ ARM64_GRP_BRANCH_RELATIVE,
+ ARM64_GRP_CRYPTO = 128,
+ ARM64_GRP_FPARMV8,
+ ARM64_GRP_NEON,
+ ARM64_GRP_CRC
+}
From ffceaf6a2757de6125c181e2c165fa254ec7b882 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Wed, 4 Nov 2026 03:13:15 +0900
Subject: [PATCH 11/14] chore: refactor Arm64RegisterValueAccumulator to use
AsmArm64
---
.../Arm64RegisterValueAccumulator.cs | 275 ++++++++----------
1 file changed, 123 insertions(+), 152 deletions(-)
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
index de92586af5..c8d481958d 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64RegisterValueAccumulator.cs
@@ -1,4 +1,4 @@
-using Gee.External.Capstone.Arm64;
+using AsmArm64;
using Microsoft.Diagnostics.Runtime.Interfaces;
using System.Diagnostics;
@@ -17,9 +17,11 @@ internal enum State
private State _state;
private ulong _addressValue;
private long _addressOffset;
- private Arm64RegisterId _registerId;
+ private Arm64RegisterAny _trackedRegister;
private IClrRuntime _runtime;
+ private static readonly Arm64RegisterAny InvalidRegister = Arm64RegisterAny.Create(Arm64RegisterKind.Invalid, 0, Arm64RegisterVKind.Default, 0, 0);
+
public void Init(IClrRuntime runtime)
{
_runtime = runtime;
@@ -29,20 +31,22 @@ public void Init(IClrRuntime runtime)
internal void Reset(
State state = State.LookingForPattern,
ulong value = 0,
- Arm64RegisterId registerId = Arm64RegisterId.Invalid)
+ Arm64RegisterAny register = default)
{
_state = state;
_addressValue = value;
_addressOffset = 0;
- _registerId = registerId;
+ _trackedRegister = register == default
+ ? InvalidRegister
+ : register;
}
- public void Feed(Arm64Instruction instruction)
+ public void Feed(Arm64Instruction instruction, ulong instructionAddress = 0)
{
switch (_state)
{
case State.LookingForPattern:
- if (TryHandleLookingForPattern(instruction))
+ if (TryHandleLookingForPattern(instruction, instructionAddress))
return;
break;
@@ -67,7 +71,7 @@ public void Feed(Arm64Instruction instruction)
return;
// Try handle MOVZ/ADRP for new constand load.
- if (TryHandleLookingForPattern(instruction))
+ if (TryHandleLookingForPattern(instruction, instructionAddress))
return;
goto default;
@@ -82,7 +86,7 @@ public void Feed(Arm64Instruction instruction)
Reset();
// Reprocess current instruction.
- TryHandleLookingForPattern(instruction);
+ TryHandleLookingForPattern(instruction, instructionAddress);
}
return;
}
@@ -130,16 +134,26 @@ public ulong Value
}
}
- public Arm64RegisterId RegisterId => _registerId;
+ public Arm64RegisterAny TrackedRegister => _trackedRegister;
- private bool TryHandleLookingForPattern(Arm64Instruction instruction)
+ private bool TryHandleLookingForPattern(Arm64Instruction instruction, ulong instructionAddress)
{
- switch (instruction.Id)
+ switch (instruction.Mnemonic)
{
- case Arm64InstructionId.ARM64_INS_MOVZ:
- return TryStartMovzSequence(instruction);
- case Arm64InstructionId.ARM64_INS_ADRP:
- return TryStartAdrpSequence(instruction);
+ // MOVZ is decoded as MOV instruction alias by default.
+ case Arm64Mnemonic.MOV:
+ var baseInstruction = instruction.AsBaseInstruction();
+ if (baseInstruction.Mnemonic != Arm64Mnemonic.MOVZ)
+ goto default;
+
+ return TryStartMovzSequence(baseInstruction);
+
+ case Arm64Mnemonic.MOVZ:
+ //return TryStartMovzSequence(instruction);
+ throw new UnreachableException();
+
+ case Arm64Mnemonic.ADRP:
+ return TryStartAdrpSequence(instruction, instructionAddress);
default:
return false;
}
@@ -147,34 +161,37 @@ private bool TryHandleLookingForPattern(Arm64Instruction instruction)
private bool TryHandleExpectingMovk(Arm64Instruction instruction)
{
- try
- {
- if (instruction.Id != Arm64InstructionId.ARM64_INS_MOVK)
- return false;
-
- if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _)
- || !instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
- return false;
+ if (instruction.Mnemonic != Arm64Mnemonic.MOVK)
+ return false;
- if (instruction.GetRegisterId(0) != _registerId)
- return false;
+ if (!instruction.TryGetOperand(0, out Arm64RegisterOperand registerOperand))
+ return false;
- // Note: MOVK instruction accept only LSL shift operation.
- var shiftAmount = instruction.GetShiftAmount(1);
- if (shiftAmount is not (0 or 16 or 32 or 48))
- return false;
+ if (registerOperand.Value != _trackedRegister)
+ return false;
- var shiftedImmediateValue = instruction.GetUnsignedShiftedImmediateValue(1);
- const ulong mask = 0xFFFF;
- _addressValue &= ~(mask << shiftAmount); // Clear 16-bits of MOVK target
- _addressValue |= shiftedImmediateValue; // Set shifted immediate value
+ if (!instruction.TryGetOperand(1, out Arm64ImmediateOperand _))
+ return false;
- return true;
- }
- catch (Exception ex) when (IsDecodingException(ex))
+ var shiftAmount = 0;
+ if (instruction.TryGetOperand(2, out Arm64ShiftOperand shiftOperand))
{
- return false;
+ if (shiftOperand.ShiftKind != Arm64ShiftKind.LSL)
+ return false; // MOVK only supports LSL shift kind. So this code path should never be hit.
+ shiftAmount = shiftOperand.Amount;
}
+
+ if (shiftAmount is not (0 or 16 or 32 or 48))
+ return false;
+
+ if (!instruction.TryGetShiftedImmediateValueUnsigned(1, out var shiftedImmediateValue))
+ return false;
+
+ const ulong mask = 0xFFFF;
+ _addressValue &= ~(mask << shiftAmount); // Clear 16-bits of MOVK target
+ _addressValue |= shiftedImmediateValue; // Set shifted immediate value
+
+ return true;
}
///
@@ -185,131 +202,98 @@ private bool TryHandleExpectingMovk(Arm64Instruction instruction)
///
private bool TryHandleExpectingAdd(Arm64Instruction instruction)
{
- try
- {
- if (instruction.Id != Arm64InstructionId.ARM64_INS_ADD)
- return false;
-
- if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _)
- || !instruction.TryGetOperand(1, Arm64OperandType.Register, out _))
- return false;
+ if (instruction.Mnemonic != Arm64Mnemonic.ADD)
+ return false;
- if (instruction.GetRegisterId(0) != _registerId)
- return false;
+ if (!instruction.TryGetOperand(0, out Arm64RegisterOperand registerOperand0) || registerOperand0.Value != _trackedRegister)
+ return false;
- if (instruction.GetRegisterId(1) != _registerId)
- return false;
+ if (!instruction.TryGetOperand(1, out Arm64RegisterOperand registerOperand1) || registerOperand1.Value != _trackedRegister)
+ return false;
- if (!instruction.TryGetOperand(2, Arm64OperandType.Immediate, out _))
- return false;
+ if (!instruction.TryGetOperand(2, out Arm64ImmediateOperand _))
+ return false;
- var shiftedImmediateValue = instruction.GetUnsignedShiftedImmediateValue(2);
- _addressValue += shiftedImmediateValue;
- _state = State.LookingForPossibleLdr;
- return true;
- }
- catch (Exception ex) when (IsDecodingException(ex))
- {
+ if (!instruction.TryGetShiftedImmediateValueUnsigned(2, out var shiftedImmediateValue))
return false;
- }
- }
- ///
- /// Only decoding failures (missing/mistyped operands, unexpected shift/instruction)
- /// are treated as "not matched". Anything else (e.g. OOM) must propagate.
- ///
- private static bool IsDecodingException(Exception ex)
- => ex is ArgumentOutOfRangeException
- or ArgumentException
- or NotSupportedException
- or InvalidOperationException
- or UnreachableException;
+ _addressValue += shiftedImmediateValue;
+ _state = State.LookingForPossibleLdr;
+ return true;
+ }
private bool TryStartMovzSequence(Arm64Instruction instruction)
{
- if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _))
+ if (!instruction.TryGetOperand(0, out Arm64RegisterOperand registerOperand))
return false;
- if (!instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
+ if (!instruction.TryGetOperand(1, out Arm64ImmediateOperand _))
return false;
- try
- {
- _registerId = instruction.GetRegisterId(0);
- _addressValue = instruction.GetUnsignedShiftedImmediateValue(1);
- _addressOffset = 0;
- _state = State.ExpectingMovk;
- return true;
- }
- catch (Exception ex) when (IsDecodingException(ex))
- {
+ _trackedRegister = registerOperand.Value;
+
+ if (!instruction.TryGetShiftedImmediateValueUnsigned(1, out var immediateValue))
return false;
- }
+
+ _addressValue = immediateValue;
+ _addressOffset = 0;
+ _state = State.ExpectingMovk;
+ return true;
}
- private bool TryStartAdrpSequence(Arm64Instruction instruction)
+ private bool TryStartAdrpSequence(Arm64Instruction instruction, ulong instructionAddress)
{
- if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _)
- || !instruction.TryGetOperand(1, Arm64OperandType.Immediate, out _))
+ if (!instruction.TryGetOperand(0, out Arm64RegisterOperand registerOperand)
+ || !instruction.TryGetOperand(1, out Arm64LabelOperand labelOperand))
return false;
- try
- {
- _registerId = instruction.GetRegisterId(0);
- _addressOffset = instruction.GetShiftedImmediateValue(1);
- _addressValue = 0;
- _state = State.ExpectingAdd;
- return true;
- }
- catch (Exception ex) when (IsDecodingException(ex))
- {
- return false;
- }
+ _trackedRegister = registerOperand.Value;
+
+ // ADRP label offset is a page delta relative to the page containing the instruction,
+ // so resolve it to an absolute address here.
+ _addressValue = instruction.GetTargetAddressBase(instructionAddress);
+ _addressOffset = labelOperand.Offset;
+ _state = State.ExpectingAdd;
+ return true;
}
private bool TryHandleLdrFromTrackedRegister(Arm64Instruction instruction)
{
- try
- {
- if (!instruction.IsLdrFromTrackedRegister(_registerId))
- return false;
-
- if (!instruction.TryGetOperand(0, Arm64OperandType.Register, out _))
- return false;
-
- // Address 0 is never valid to dereference (e.g. `movz x0, #0` or clamped negative ADRP+ADD).
- if (Value == 0)
- {
- Reset();
- return true;
- }
-
- // Simulate the LDR instruction.
- // Use combined Value: after ADRP+ADD the effective address is _addressValue + _addressOffset.
- // Use the Try-pattern so an unreadable address drops tracking instead of aborting disassembly.
- if (!_runtime.DataTarget.DataReader.ReadPointer(Value, out ulong dereferenced))
- {
- Reset();
- return true;
- }
+ if (!instruction.IsLdrFromTrackedRegister(_trackedRegister))
+ return false;
- _addressValue = dereferenced;
- _addressOffset = 0;
+ if (!instruction.TryGetOperand(0, out Arm64RegisterOperand registerOperand))
+ return false;
- if (_addressValue == 0)
- {
- Reset();
- return true;
- }
+ // Address 0 is never valid to dereference (e.g. `movz x0, #0` or clamped negative ADRP+ADD).
+ if (Value == 0)
+ {
+ Reset();
+ return true;
+ }
- // The LDR might have loaded the result in another register
- _registerId = instruction.GetRegisterId(0);
+ // Simulate the LDR instruction.
+ // Use combined Value: after ADRP+ADD the effective address is _addressValue + _addressOffset.
+ // Use the Try-pattern so an unreadable address drops tracking instead of aborting disassembly.
+ if (!_runtime.DataTarget.DataReader.ReadPointer(Value, out ulong dereferenced))
+ {
+ Reset();
return true;
}
- catch (Exception ex) when (IsDecodingException(ex))
+
+ _addressValue = dereferenced;
+ _addressOffset = 0;
+
+ if (_addressValue == 0)
{
- return false;
+ Reset();
+ return true;
}
+
+ // The LDR might have loaded the result in another register
+ _trackedRegister = registerOperand.Value;
+ return true;
+
}
private bool TryHandleConditionalInstruction(Arm64Instruction instruction)
@@ -328,29 +312,16 @@ private bool TryHandleConditionalInstruction(Arm64Instruction instruction)
private bool IsTrackedRegisterOverwritten(Arm64Instruction instruction)
{
- try
+ // Finally check if the current instruction modified the register that was accumulating the constant
+ // and reset the state machine in case it did.
+ foreach (var writtenRegister in instruction.GetWrittenRegisters())
{
- // Finally check if the current instruction modified the register that was accumulating the constant
- // and reset the state machine in case it did.
- var details = instruction.Details;
- if (details is null)
- return true; // Conservative: unknown writes invalidate tracking (avoid false HasValue).
-
- foreach (Arm64Register writtenRegister in details.AllWrittenRegisters)
- {
- if (writtenRegister.Id != _registerId)
- continue;
-
- return true;
- }
+ if (writtenRegister != _trackedRegister)
+ continue;
- return false;
- }
- catch (Exception ex) when (IsDecodingException(ex) || ex is NullReferenceException)
- {
- // Conservative: on decoding failure assume overwritten so a stale value
- // is not reported as a false-positive ReferencedAddress.
return true;
}
+
+ return false;
}
}
From 28519b2e4c64560160c3c3cc037216bf254c74be Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Wed, 4 Nov 2026 03:13:30 +0900
Subject: [PATCH 12/14] chore: migrate Arm64 disassembler and model to AsmArm64
---
.../Disassemblers/Arm64Disassembler.cs | 145 +++++++++-------
.../Arm64InstructionFormatter.cs | 156 +++++++++++++++---
.../Disassemblers/DataContracts.cs | 136 ++++-----------
3 files changed, 249 insertions(+), 188 deletions(-)
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
index b619389780..c7becb55bd 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
@@ -1,8 +1,8 @@
+using AsmArm64;
using BenchmarkDotNet.Diagnosers;
-using Gee.External.Capstone;
-using Gee.External.Capstone.Arm64;
using Microsoft.Diagnostics.Runtime;
using Microsoft.Diagnostics.Runtime.Interfaces;
+using System.Runtime.InteropServices;
namespace BenchmarkDotNet.Disassemblers
{
@@ -10,47 +10,43 @@ internal class Arm64Disassembler : ClrMdDisassembler
{
protected override IEnumerable Decode(byte[] code, ulong startAddress, State state, int depth, IClrMethod currentMethod, DisassemblySyntax syntax)
{
- const Arm64DisassembleMode disassembleMode = Arm64DisassembleMode.Arm;
- using (CapstoneArm64Disassembler disassembler = CapstoneFactory.CreateArm64Disassembler(disassembleMode))
- {
- // Enables disassemble details, which are disabled by default, to provide more detailed information on
- // disassembled binary code.
- disassembler.EnableInstructionDetails = true;
- disassembler.DisassembleSyntax = Map(syntax);
+ Arm64RegisterValueAccumulator accumulator = new();
+ accumulator.Init(state.Runtime);
+
+ ReadOnlySpan rawInstructions = MemoryMarshal.Cast(code);
- Arm64RegisterValueAccumulator accumulator = new();
- accumulator.Init(state.Runtime);
+ var results = new List();
+
+ for (int i = 0; i < rawInstructions.Length; ++i)
+ {
+ var rawInstruction = rawInstructions[i];
+ var instruction = Arm64Instruction.Decode(rawInstruction);
+ var instructionAddress = startAddress + (4 * (ulong) i);
- Arm64Instruction[] instructions = disassembler.Disassemble(code, (long) startAddress);
- foreach (Arm64Instruction instruction in instructions)
+ if (TryGetReferencedAddress(instruction, instructionAddress, accumulator, (uint) state.Runtime.DataTarget.DataReader.PointerSize, out var referencedAddress, out var isIndirect))
{
- bool isIndirect = false;
bool isPrestubMD = false;
-
- ulong address = 0;
- if (TryGetReferencedAddress(instruction, accumulator, (uint) state.Runtime.DataTarget.DataReader.PointerSize, out address, out isIndirect))
+ if (isIndirect && state.RuntimeVersion.Major >= 7)
{
- if (isIndirect && state.RuntimeVersion.Major >= 7)
- {
- FlushCachedDataIfNeeded(state.Runtime.DataTarget.DataReader, address, new byte[1]);
- TryResolvePrecode(state.Runtime.DataTarget.DataReader, ref address, out isPrestubMD);
- }
- TryTranslateAddressToName(address, isPrestubMD, state, depth, currentMethod);
+ FlushCachedDataIfNeeded(state.Runtime.DataTarget.DataReader, referencedAddress, new byte[1]);
+ TryResolvePrecode(state.Runtime.DataTarget.DataReader, ref referencedAddress, out isPrestubMD);
}
+ TryTranslateAddressToName(referencedAddress, isPrestubMD, state, depth, currentMethod);
+ }
- accumulator.Feed(instruction);
+ accumulator.Feed(instruction, instructionAddress);
- yield return new Arm64Asm()
- {
- InstructionPointer = (ulong) instruction.Address,
- InstructionLength = instruction.Bytes.Length,
- Instruction = instruction,
- ReferencedAddress = (address > ushort.MaxValue) ? address : null,
- IsReferencedAddressIndirect = isIndirect,
- DisassembleSyntax = disassembler.DisassembleSyntax
- };
- }
+ results.Add(new Arm64Asm()
+ {
+ InstructionPointer = instructionAddress,
+ RawInstruction = rawInstruction,
+ DisassembleSyntax = DisassembleSyntax.Gas,
+ ReferencedAddress = IsValidAddress(referencedAddress) ? referencedAddress : null,
+ IsReferencedAddressIndirect = isIndirect,
+ });
}
+
+ return results;
}
// Counterpart of IntelDisassembler.TryResolvePrecode: recognise the AArch64 precode/stub
@@ -65,7 +61,7 @@ protected override IEnumerable Decode(byte[] code, ulong startAddress, Stat
// data-section layout is also stable. What can change between versions is the offset
// between the code page and its data section, so we extract the LDR-literal displacements
// straight from the bytes instead of consulting a runtime-version-specific page-size table.
- private static bool TryResolvePrecode(IDataReader reader, ref ulong address, out bool isPrestubMD)
+ internal static bool TryResolvePrecode(IDataReader reader, ref ulong address, out bool isPrestubMD)
{
isPrestubMD = false;
if (!TryReadStubHead(reader, address, out ulong parseBase, out uint instr0, out uint instr1, out uint instr2))
@@ -145,22 +141,31 @@ private static bool TryResolvePrecode(IDataReader reader, ref ulong address, out
// Encoding: DMB ISHLD = 0xD50339BF.
private const uint DmbIshLdInstr = 0xD50339BFu;
- private static bool TryReadStubHead(IDataReader reader, ulong address, out ulong parseBase, out uint instr0, out uint instr1, out uint instr2)
+ internal static bool TryReadStubHead(IDataReader reader, ulong address, out ulong parseBase, out uint instr0, out uint instr1, out uint instr2)
{
parseBase = address;
instr0 = instr1 = instr2 = 0;
- byte[] buffer = new byte[16];
- // TODO: IDataReader.Read might return partial data. It need to call multiple times until buffer filled or readLength<=0.
- int read = reader.Read(address, buffer);
- if (read < 12)
+ // IDataReader.Read may return partial data.
+ // Call it repeatedly until the buffer is filled or no more data is available.
+ Span buffer = stackalloc byte[16];
+ int totalBytesRead = 0;
+ while (totalBytesRead < buffer.Length)
+ {
+ int bytesRead = reader.Read(address + (ulong) totalBytesRead, buffer.Slice(totalBytesRead));
+ if (bytesRead <= 0)
+ break;
+ totalBytesRead += bytesRead;
+ }
+
+ if (totalBytesRead < 12)
return false;
int offset = 0;
uint first = ReadInstr(buffer, 0);
if (first == DmbIshLdInstr)
{
- if (read < 16)
+ if (totalBytesRead < 16)
return false;
offset = 4;
parseBase = address + 4;
@@ -172,7 +177,7 @@ private static bool TryReadStubHead(IDataReader reader, ulong address, out ulong
return true;
}
- private static uint ReadInstr(byte[] buffer, int offset)
+ private static uint ReadInstr(ReadOnlySpan buffer, int offset)
=> (uint) buffer[offset]
| ((uint) buffer[offset + 1] << 8)
| ((uint) buffer[offset + 2] << 16)
@@ -181,7 +186,7 @@ private static uint ReadInstr(byte[] buffer, int offset)
// LDR (literal), 64-bit form. Encoding: bits[31:24]=0x58, bits[23:5]=imm19 (signed,
// word-scaled offset relative to the LDR's own PC), bits[4:0]=Xt. Returns the destination
// register and the byte-scaled offset from the LDR instruction's address to the loaded slot.
- private static bool IsLdrLiteral64(uint instr, out int rt, out int offsetBytes)
+ internal static bool IsLdrLiteral64(uint instr, out int rt, out int offsetBytes)
{
rt = 0;
offsetBytes = 0;
@@ -196,41 +201,57 @@ private static bool IsLdrLiteral64(uint instr, out int rt, out int offsetBytes)
return true;
}
- private static bool TryGetReferencedAddress(Arm64Instruction instruction, Arm64RegisterValueAccumulator accumulator, uint pointerSize, out ulong referencedAddress, out bool isReferencedAddressIndirect)
+ internal static bool TryGetReferencedAddress(
+ Arm64Instruction instruction,
+ ulong instructionAddress,
+ 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)
+
+ switch (instruction.Mnemonic)
{
- // Branch via register where we have extracted the value of the register by parsing the disassembly
- referencedAddress = (ulong) accumulator.Value;
- isReferencedAddressIndirect = true;
- return true;
+ // TODO: Add support for BRAA/BRAB/BRAAZ/BRABZ/BLRAA/BLRAB/BLRAAZ/BLRABZ instructions. Currently .NET JIT don't emit these instructions.
+ // See: https://github.com/dotnet/runtime/blob/v11.0.0-rc.3.26522.148/src/coreclr/jit/instrsarm64.h
+ case Arm64Mnemonic.BR:
+ case Arm64Mnemonic.BLR:
+ if (instruction.TryGetOperand(0, out Arm64RegisterOperand register) && register.Value == accumulator.TrackedRegister)
+ {
+ if (accumulator.HasValue)
+ {
+ // Branch via register where we have extracted the value of the register by parsing the disassembly.
+ // The accumulator holds an absolute address (MOVZ/MOVK constant or ADRP resolved to a page address).
+ referencedAddress = accumulator.Value;
+ isReferencedAddressIndirect = true;
+ return true;
+ }
+ }
+ break;
}
- else if (instruction.Details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE))
+
+ if (instruction.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE))
{
- // One of the operands is the address
- for (int i = 0; i < instruction.Details.Operands.Length; i++)
+ if (instruction.Flags.HasFlag(Arm64InstructionFlags.HasLabel))
{
- if (instruction.Details.Operands[i].Type == Arm64OperandType.Immediate)
+ // One of the label operands is the address
+ for (int i = 0; i < instruction.Operands.Count; i++)
{
- referencedAddress = (ulong) instruction.Details.Operands[i].Immediate;
+ if (!instruction.TryGetOperand(i, out Arm64LabelOperand labelOperand))
+ continue;
+
+ referencedAddress = instruction.GetTargetAddress(instructionAddress, labelOperand.Offset);
isReferencedAddressIndirect = false;
return true;
}
}
}
+
referencedAddress = 0;
isReferencedAddressIndirect = false;
return false;
}
- private static DisassembleSyntax Map(DisassemblySyntax syntax)
- => syntax switch
- {
- DisassemblySyntax.Att => DisassembleSyntax.Att,
- DisassemblySyntax.Intel => DisassembleSyntax.Intel,
- _ => DisassembleSyntax.Masm
- };
-
// Recognise the AArch64 jump trampoline shape the CLR JIT emits when a call's real target
// is out of rel26 range (±128 MB), plus the precode/stub shapes the runtime emits as the
// stable entry point for tiered methods (so a direct `BL imm26` landing on the precode
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionFormatter.cs b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionFormatter.cs
index 2adcdc0966..818b7e09b9 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionFormatter.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionFormatter.cs
@@ -1,52 +1,164 @@
-using Gee.External.Capstone.Arm64;
+using AsmArm64;
+using BenchmarkDotNet.Extensions;
using Iced.Intel;
+using System.Globalization;
using System.Text;
namespace BenchmarkDotNet.Disassemblers
{
internal static class Arm64InstructionFormatter
{
- // FormatterOptions is an Intel-specific concept that comes from the Iced library, but since our users can pass custom
- // Iced Formatter to DisassemblyDiagnoserConfig and it provides all the settings we need, we just reuse it here.
- internal static string Format(Arm64Asm asm, FormatterOptions formatterOptions,
- bool printInstructionAddresses, uint pointerSize, IReadOnlyDictionary symbols)
+ internal static readonly Arm64InstructionFormattingOptions DefaultFormattingOptions = new()
{
- var instruction = asm.Instruction;
- if (instruction == null)
- return "";
+ AliasMode = Arm64InstructionAliasMode.Preferred,
+ FormatProvider = CultureInfo.InvariantCulture,
+ ImmediateFormat = Arm64NumericFormat.Auto,
+ LabelOffsetFormat = Arm64NumericFormat.Auto,
+ MemoryOffsetFormat = Arm64NumericFormat.Auto,
+ UseUppercaseHex = true,
+ UseUppercaseText = false,
+ PrintDefaultOperands = false,
+ };
- StringBuilder output = new();
+ internal static readonly Arm64InstructionFormattingOptions BaseInstructionAliasModeFormattingOptions = new()
+ {
+ AliasMode = Arm64InstructionAliasMode.BaseInstruction,
+ FormatProvider = CultureInfo.InvariantCulture,
+ ImmediateFormat = Arm64NumericFormat.Auto,
+ LabelOffsetFormat = Arm64NumericFormat.Auto,
+ MemoryOffsetFormat = Arm64NumericFormat.Auto,
+ UseUppercaseHex = true,
+ UseUppercaseText = false,
+ PrintDefaultOperands = false,
+ };
- if (printInstructionAddresses)
+ internal static string Format(Arm64Instruction instruction)
+ {
+ var asm = new Arm64Asm
{
- FormatInstructionPointer(instruction, formatterOptions, pointerSize, output);
+ RawInstruction = instruction.RawInstruction,
+ DisassembleSyntax = DisassembleSyntax.Gas,
+ };
+ return Format(asm, new Iced.Intel.FormatterOptions { }, printInstructionAddresses: false, 8, new Dictionary());
+ }
+
+ internal static (string Mnemonic, string OperandsText) GetInstructionDetails(
+ Arm64Instruction instruction,
+ ulong instructionAddress,
+ IReadOnlyDictionary? symbols = null)
+ {
+ var formattingOptions = GetFormattingOptions(instruction);
+ var instructionText = FormatInstruction(instruction, formattingOptions, instructionAddress, symbols);
+ if (instructionText.IsBlank())
+ return ("", "");
+
+ var spaceIndex = instructionText.IndexOf(' ');
+ return spaceIndex < 0
+ ? (instructionText, "")
+ : (instructionText[..spaceIndex], instructionText[(spaceIndex + 1)..]);
+ }
+
+ private static string FormatInstruction(
+ Arm64Instruction instruction,
+ Arm64InstructionFormattingOptions formattingOptions,
+ ulong instructionAddress,
+ IReadOnlyDictionary? symbols)
+ {
+ // If base address is not specified, keep the raw relative label offsets.
+ if (instructionAddress == 0)
+ return instruction.ToString(formattingOptions);
+
+ var labelDidNotFit = false;
+
+ // Symbol names can be long, so grow the buffer until every resolved label fits.
+ for (int bufferLength = 256; bufferLength <= 64 * 1024; bufferLength *= 2)
+ {
+ labelDidNotFit = false;
+ var buffer = new char[bufferLength];
+
+ if (instruction.TryFormat(formattingOptions, buffer, out int written, TryResolveLabel) && !labelDidNotFit)
+ return new string(buffer, 0, written);
}
- var padRight = instruction.Details.Operands.Length > 0
- ? Math.Max(formatterOptions.FirstOperandCharIndex, instruction.Mnemonic.Length + 1)
- : 0;
- output.Append(instruction.Mnemonic.PadRight(padRight));
+ return instruction.ToString(formattingOptions);
- if (asm.ReferencedAddress.HasValue && !asm.IsReferencedAddressIndirect && symbols.TryGetValue(asm.ReferencedAddress.Value, out var name))
+ // Helper local function to resolve each label operand while formatting.
+ bool TryResolveLabel(long labelOffset, Span destination, out int charsWritten)
{
- string partToReplace = $"#0x{asm.ReferencedAddress.Value:x}";
- output.Append(instruction.Operand.Replace(partToReplace, name));
+ var address = instruction.GetTargetAddress(instructionAddress, labelOffset);
+ var text = symbols is not null && symbols.TryGetValue(address, out var name)
+ ? name
+ : $"#0x{address:X}";
+
+ if (!text.AsSpan().TryCopyTo(destination))
+ {
+ // Returning false makes AsmArm64 use the numeric fallback so that we can retry with a larger buffer.
+ labelDidNotFit = true;
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = text.Length;
+ return true;
}
- else
+ }
+
+ internal static Arm64InstructionFormattingOptions GetFormattingOptions(Arm64Instruction instruction)
+ {
+ // AsmArm64 automatically resolve instruction mnemonic to preferred alias.
+ // Too keep Gee.Extenal.Capstone disassembly results compatibility.
+ // Use `Arm64InstructionAliasMode.BaseInstruction` for MOVN and MOVZ instructions.
+ // Full list of instruction that use aliases: https://github.com/xoofx/AsmArm64/blob/main/src/AsmArm64/generated/Arm64InstructionAliasTable.gen.cs
+
+ if (instruction.Mnemonic != Arm64Mnemonic.MOV)
+ return DefaultFormattingOptions;
+
+ var baseMnemonic = instruction.AsBaseInstruction().Mnemonic;
+ switch (baseMnemonic)
+ {
+ case Arm64Mnemonic.MOVN:
+ case Arm64Mnemonic.MOVZ:
+ return BaseInstructionAliasModeFormattingOptions;
+
+ default:
+ return DefaultFormattingOptions;
+ }
+ }
+
+ // FormatterOptions is an Intel-specific concept that comes from the Iced library, but since our users can pass custom
+ // Iced Formatter to DisassemblyDiagnoserConfig and it provides all the settings we need, we just reuse it here.
+ internal static string Format(
+ Arm64Asm asm,
+ FormatterOptions formatterOptions,
+ bool printInstructionAddresses,
+ uint pointerSize,
+ IReadOnlyDictionary symbols)
+ {
+ StringBuilder output = new(256);
+
+ if (printInstructionAddresses)
{
- output.Append(instruction.Operand);
+ FormatInstructionPointer(asm.InstructionPointer, formatterOptions, pointerSize, output);
}
+ var (mnemonic, operandsText) = GetInstructionDetails(asm.Instruction, asm.InstructionPointer, symbols);
+
+ var padRight = operandsText != ""
+ ? Math.Max(formatterOptions.FirstOperandCharIndex, mnemonic.Length + 1)
+ : 0;
+ output.Append(mnemonic.PadRight(padRight));
+ output.Append(operandsText);
+
return output.ToString();
}
- private static void FormatInstructionPointer(Arm64Instruction instruction, FormatterOptions formatterOptions, uint pointerSize, StringBuilder output)
+ private static void FormatInstructionPointer(ulong address, FormatterOptions formatterOptions, uint pointerSize, StringBuilder output)
{
string ipFormat = formatterOptions.LeadingZeroes
? pointerSize == 4 ? "X8" : "X16"
: "X";
- output.Append(instruction.Address.ToString(ipFormat));
+ output.Append(address.ToString(ipFormat));
output.Append(' ');
}
}
diff --git a/src/BenchmarkDotNet/Disassemblers/DataContracts.cs b/src/BenchmarkDotNet/Disassemblers/DataContracts.cs
index 9915725abf..8b4282a4fd 100644
--- a/src/BenchmarkDotNet/Disassemblers/DataContracts.cs
+++ b/src/BenchmarkDotNet/Disassemblers/DataContracts.cs
@@ -1,9 +1,7 @@
-using Gee.External.Capstone;
-using Gee.External.Capstone.Arm64;
+using AsmArm64;
using Iced.Intel;
using Microsoft.Diagnostics.Runtime.Interfaces;
-using System.ComponentModel;
-using System.Text.Json;
+using System.Diagnostics;
using System.Text.Json.Serialization;
@@ -13,6 +11,21 @@
namespace BenchmarkDotNet.Disassemblers;
+// Based on Capstone definition..
+public enum DisassembleSyntax
+{
+ Default = Masm,
+
+ // x64/x86
+ Att = 1,
+ Intel = 2,
+ Masm = 3,
+
+ // Following enum definitions are added for arm64 platform
+ Gas = 11, // (Default) GNU assembler inspired style
+ Llvm = 12, // LLVM inspired style
+}
+
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(Sharp), typeDiscriminator: nameof(Sharp))]
[JsonDerivedType(typeof(IntelAsm), typeDiscriminator: nameof(IntelAsm))]
@@ -57,117 +70,32 @@ public sealed class IntelAsm() : Asm
public sealed class Arm64Asm : Asm
{
- [JsonInclude]
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal Arm64AsmData Data { get; set; }
-
- [JsonIgnore]
- public Arm64Instruction? Instruction
- {
- get => Data.Instruction;
- set => Data = Data with { Instruction = value };
- }
-
- [JsonIgnore]
- internal DisassembleSyntax DisassembleSyntax
+ public Arm64Asm()
{
- get => Data.DisassembleSyntax;
- set => Data = Data with { DisassembleSyntax = value };
+ InstructionLength = 4;
}
- public override string ToString() => Instruction?.ToString() ?? "";
+ public required uint RawInstruction { get; init; }
- // Wrapper class to hold Arm64 instruction and disassemble syntax.
- [JsonConverter(typeof(Arm64AsmDataConverter))]
- internal record struct Arm64AsmData
- {
- internal DisassembleSyntax DisassembleSyntax { get; set; }
+ public required DisassembleSyntax DisassembleSyntax { get; init; }
- public Arm64Instruction? Instruction { get; set; }
- }
+ private Arm64Instruction? _instruction;
- // Custom JsonConverter for Arm64AsmData.
- internal class Arm64AsmDataConverter : JsonConverter
+ [JsonIgnore]
+ public Arm64Instruction Instruction
{
- private const string Arm64AddressKey = "arm64Address";
- private const string Arm64BytesKey = "arm64Bytes";
- private const string Arm64SyntaxKey = "arm64Syntax";
-
- public override Arm64AsmData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ get
{
- if (reader.TokenType != JsonTokenType.StartObject)
- throw new JsonException();
-
- long instructionAddress = default;
- byte[] instructionBytes = [];
- DisassembleSyntax syntax = DisassembleSyntax.Masm;
-
- while (reader.Read())
- {
- if (reader.TokenType == JsonTokenType.EndObject)
- break;
-
- if (reader.TokenType != JsonTokenType.PropertyName)
- throw new JsonException();
-
- string propertyName = reader.GetString()!;
- reader.Read();
-
- switch (propertyName)
- {
- case Arm64AddressKey:
- instructionAddress = reader.GetInt64();
- break;
-
- case Arm64BytesKey:
- instructionBytes = reader.GetBytesFromBase64();
- break;
-
- case Arm64SyntaxKey:
- var syntaxValue = reader.GetString()!;
- syntax = syntaxValue switch
- {
- "Intel" => DisassembleSyntax.Intel,
- "Att" => DisassembleSyntax.Att,
- "Masm" => DisassembleSyntax.Masm,
- _ => DisassembleSyntax.Masm,
- };
- break;
-
- default:
- throw new NotSupportedException($"Unknown property({propertyName}) found.");
- }
- }
-
- if (reader.TokenType != JsonTokenType.EndObject)
- throw new JsonException("Invalid JSON");
-
- using var disassembler = CapstoneFactory.CreateArm64Disassembler(Arm64DisassembleMode.Arm);
- disassembler.EnableInstructionDetails = true;
- disassembler.DisassembleSyntax = syntax;
- var instruction = disassembler.Disassemble(instructionBytes, instructionAddress).SingleOrDefault();
-
- return new Arm64AsmData
- {
- DisassembleSyntax = syntax,
- Instruction = instruction,
- };
- }
-
- public override void Write(Utf8JsonWriter writer, Arm64AsmData value, JsonSerializerOptions options)
- {
- writer.WriteStartObject();
- writer.WriteString(Arm64SyntaxKey, value.DisassembleSyntax.ToString());
- var instruction = value.Instruction;
- if (instruction != null)
- {
- writer.WriteNumber(Arm64AddressKey, instruction.Address);
- writer.WriteBase64String(Arm64BytesKey, instruction.Bytes);
- }
- writer.WriteEndObject();
+ _instruction ??= Arm64Instruction.Decode(RawInstruction);
+ return _instruction.Value;
}
}
+ public override string ToString()
+ {
+ var options = Arm64InstructionFormatter.GetFormattingOptions(Instruction);
+ return Instruction.ToString(options);
+ }
}
public sealed class MonoCode : SourceCode
From 14c62d43441055af5af90f597ef309096ecdafd4 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Wed, 4 Nov 2026 03:13:38 +0900
Subject: [PATCH 13/14] chore: remove Capstone dependency
---
src/BenchmarkDotNet/BenchmarkDotNet.csproj | 1 -
src/BenchmarkDotNet/BenchmarkDotNet.targets | 34 -----------
.../Disassemblers/CapstoneFactory.cs | 58 -------------------
3 files changed, 93 deletions(-)
delete mode 100644 src/BenchmarkDotNet/Disassemblers/CapstoneFactory.cs
diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
index e023399587..3e6d079490 100644
--- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj
+++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
@@ -26,7 +26,6 @@
-
diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.targets b/src/BenchmarkDotNet/BenchmarkDotNet.targets
index 62c85d5b99..7703144453 100644
--- a/src/BenchmarkDotNet/BenchmarkDotNet.targets
+++ b/src/BenchmarkDotNet/BenchmarkDotNet.targets
@@ -1,40 +1,6 @@
-
-
- $(RuntimeIdentifier)
-
-
-
-
-
- linux
- win
- osx
-
- $(BenchmarkDotNetTargetPlatform)-x64
- $(BenchmarkDotNetTargetPlatform)-x86
- $(BenchmarkDotNetTargetPlatform)-arm
- $(BenchmarkDotNetTargetPlatform)-arm64
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
- /// .NET Framework does not probe runtimes/{rid}/native, where BenchmarkDotNet.targets copies the native capstone.
- /// Once it is loaded by its full path, the P/Invokes that name the module bind to the loaded library.
- /// When the file is not there, the default search applies - a build for a specific RuntimeIdentifier puts it next to the application.
- ///
- private static class FrameworkNativeLibrary
- {
- internal static readonly Lazy Handle = new(Load);
-
- private static IntPtr Load()
- {
- if (!RuntimeInformation.IsFullFramework)
- return IntPtr.Zero;
-
- string? rid = RuntimeInformation.GetCurrentPlatform() switch
- {
- Platform.X86 => "win-x86",
- Platform.X64 => "win-x64",
- Platform.Arm64 => "win-arm64",
- _ => null
- };
- var assembly = typeof(CapstoneDisassembler).Assembly;
- string location = ShadowCopyHelper.TryGetOriginalLocation(assembly, out var originalLocation) ? originalLocation : assembly.Location;
- if (rid is null || location.IsBlank())
- return IntPtr.Zero;
-
- string path = Path.Combine(Path.GetDirectoryName(location)!, "runtimes", rid, "native", "capstone.dll");
- return File.Exists(path) ? LoadLibraryW(path) : IntPtr.Zero;
- }
-
- [DllImport("kernel32", ExactSpelling = true, CharSet = CharSet.Unicode)]
- private static extern IntPtr LoadLibraryW(string lpLibFileName);
- }
-#endif
-}
From 2a103801f9fd636d19d4c27732e1d02869128275 Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Wed, 4 Nov 2026 03:13:50 +0900
Subject: [PATCH 14/14] chore: add/update related unit tests
---
.../Arm64RegisterAnyAssertion.cs | 62 +++++
.../Arm64/Arm64DisassemblerTests.Decode.cs | 28 +--
.../Arm64DisassemblerTests.IsLdrLiteral64.cs | 14 +-
...sassemblerTests.TryFollowJumpTrampoline.cs | 7 +-
...sassemblerTests.TryGetReferencedAddress.cs | 225 ++++++++++++++----
.../Arm64DisassemblerTests.TryReadStubHead.cs | 12 +-
...rm64DisassemblerTests.TryResolvePreCode.cs | 24 +-
...ssemblerTests.TryTranslateAddressToName.cs | 3 -
...64InstructionExtensionsTests.ShiftKinds.cs | 79 ++++++
...ructionExtensionsTests.ShiftedImmediate.cs | 68 ++++++
.../Arm64/Arm64InstructionExtensionsTests.cs | 12 +
...uctionFormatterTests.IntructionPatterns.cs | 60 +++++
...structionFormatterTests.LabelResolution.cs | 77 ++++++
...structionFormatterTests.MnemonicAliases.cs | 62 +++++
...Arm64InstructionFormatterTests.Scenario.cs | 80 +++++++
.../Arm64/Arm64InstructionFormatterTests.cs | 163 ++-----------
...gisterValueAccumulatorTests.AdrpThenAdd.cs | 55 +++--
...Arm64RegisterValueAccumulatorTests.Movz.cs | 21 +-
...umulatorTests.MovzThenBranchConditional.cs | 32 +--
...gisterValueAccumulatorTests.MovzThenLdr.cs | 73 +++---
...isterValueAccumulatorTests.MovzThenMovk.cs | 62 +++--
...ulatorTests.MovzThenUnConditionalBranch.cs | 71 +++---
.../Arm64/BelongsToGroupTests.cs | 173 +++++---------
.../Arm64/Helpers/Arm64DisassemblerHelper.cs | 72 ------
.../Helpers/Arm64DisassemblerTestBase.cs | 18 +-
.../Arm64/Helpers/Arm64TestInstructions.cs | 192 ---------------
.../Arm64/Helpers/ExtensionMethods.cs | 31 +++
.../DisassemblerModelSerializationTests.cs | 45 ++--
28 files changed, 1031 insertions(+), 790 deletions(-)
create mode 100644 tests/BenchmarkDotNet.Tests/AwesomeAssertions/CustomAssertions/Arm64RegisterAnyAssertion.cs
create mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.ShiftKinds.cs
create mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.ShiftedImmediate.cs
create mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.cs
create mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.IntructionPatterns.cs
create mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.LabelResolution.cs
create mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.MnemonicAliases.cs
create mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.Scenario.cs
delete mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64TestInstructions.cs
create mode 100644 tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/ExtensionMethods.cs
diff --git a/tests/BenchmarkDotNet.Tests/AwesomeAssertions/CustomAssertions/Arm64RegisterAnyAssertion.cs b/tests/BenchmarkDotNet.Tests/AwesomeAssertions/CustomAssertions/Arm64RegisterAnyAssertion.cs
new file mode 100644
index 0000000000..995694d3b9
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/AwesomeAssertions/CustomAssertions/Arm64RegisterAnyAssertion.cs
@@ -0,0 +1,62 @@
+namespace BenchmarkDotNet.Tests;
+
+using AsmArm64;
+using AwesomeAssertions;
+using AwesomeAssertions.Execution;
+
+///
+/// Custom assertions for Arm64RegisterAny.
+///
+[CustomAssertions]
+public sealed class Arm64RegisterAnyAssertions
+{
+ private readonly Arm64RegisterAny subject;
+ private readonly AssertionChain assertionChain;
+
+ public Arm64RegisterAnyAssertions(
+ Arm64RegisterAny subject,
+ AssertionChain assertionChain)
+ {
+ this.subject = subject;
+ this.assertionChain = assertionChain;
+ }
+
+ [CustomAssertion]
+ public AndConstraint Be(
+ Arm64RegisterAny expected,
+ string because = "",
+ params object[] becauseArgs)
+ {
+ assertionChain
+ .BecauseOf(because, becauseArgs)
+ .ForCondition(EqualityComparer.Default.Equals(subject, expected))
+ .FailWith("Expected register to be {0}, but found {1}.", expected.ToText(), subject.ToText());
+
+ return new AndConstraint(this);
+ }
+
+ //[CustomAssertion]
+ //public AndConstraint NotBe(
+ // Arm64RegisterAny unexpected,
+ // string because = "",
+ // params object[] becauseArgs)
+ //{
+ // assertionChain
+ // .BecauseOf(because, becauseArgs)
+ // .ForCondition(!EqualityComparer.Default.Equals(
+ // subject, unexpected))
+ // .FailWith(
+ // "Did not expect {context:Arm64 register} to be {0}{reason}.",
+ // unexpected);
+
+ // return new AndConstraint(this);
+ //}
+}
+
+public static class Arm64RegisterAnyAssertionsExtensions
+{
+ public static Arm64RegisterAnyAssertions Should(this Arm64RegisterAny subject)
+ {
+ return new Arm64RegisterAnyAssertions(subject, AssertionChain.GetOrCreate());
+ }
+}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.Decode.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.Decode.cs
index 50b4f91134..1f46a42410 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.Decode.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.Decode.cs
@@ -1,17 +1,13 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
using AwesomeAssertions.Equivalency;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Disassemblers;
-using Gee.External.Capstone;
using static AsmArm64.Arm64RegisterX;
-using Arm64Instruction = Gee.External.Capstone.Arm64.Arm64Instruction;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
-public partial class Arm64DisassemblerTests
+public partial class Arm64DisassemblerTests : Arm64DisassemblerTestBase
{
// Check Arm64Instruction equivalency by ToString output.
private static EquivalencyOptions ConfigureCustomEquivalency(EquivalencyOptions options)
@@ -33,7 +29,7 @@ public void Decode()
byte[] bytes = rawInstructions.ToLittleEndianBytes();
- const ulong MdSlotAddress = 0x56781234 + 8 + 0x20000;
+ const ulong MdSlotAddress = 0x5678_1234 + 8 + 0x20000;
using var clrRuntime = new MockMemory()
.AddInstructions(0x5678_1234,
@@ -43,8 +39,8 @@ public void Decode()
Arm64InstructionFactory.LDR(X12, 0x20000), // ldr x12, #0x20000
Arm64InstructionFactory.BR(X10), // br x10
])
- .AddPointer(MdSlotAddress, ExpectedResultAddress) // Called by TryResolvePrecode
- .AddJitHelperFunctionName(ExpectedResultAddress, "")
+ .AddPointer(MdSlotAddress, ExpectedResultAddress) // Called by TryResolvePrecode
+ .AddJitHelperFunctionName(ExpectedResultAddress, "") // Called by TryTranslateAddressToName
.AddMethodByInstructionPointer(ExpectedResultAddress, DummyTargetMethod)
.ToMockClrRuntime();
var state = new State(clrRuntime, DummyTargetFrameworkVersion);
@@ -63,9 +59,8 @@ public void Decode()
results[0].Should().BeEquivalentTo(new Arm64Asm
{
InstructionPointer = baseAddress,
- Instruction = rawInstructions[0].ToCapstoneArm64Instruction(baseAddress, 0),
- InstructionLength = 4,
- DisassembleSyntax = DisassembleSyntax.Masm,
+ RawInstruction = rawInstructions[0],
+ DisassembleSyntax = DisassembleSyntax.Gas,
ReferencedAddress = null,
IsReferencedAddressIndirect = false,
}, ConfigureCustomEquivalency);
@@ -74,9 +69,8 @@ public void Decode()
results[1].Should().BeEquivalentTo(new Arm64Asm
{
InstructionPointer = baseAddress + 4,
- Instruction = rawInstructions[1].ToCapstoneArm64Instruction(baseAddress, 1),
- InstructionLength = 4,
- DisassembleSyntax = DisassembleSyntax.Masm,
+ RawInstruction = rawInstructions[1],
+ DisassembleSyntax = DisassembleSyntax.Gas,
ReferencedAddress = null,
IsReferencedAddressIndirect = false,
}, ConfigureCustomEquivalency);
@@ -85,9 +79,8 @@ public void Decode()
results[2].Should().BeEquivalentTo(new Arm64Asm
{
InstructionPointer = baseAddress + 8,
- Instruction = rawInstructions[2].ToCapstoneArm64Instruction(baseAddress, 2),
- InstructionLength = 4,
- DisassembleSyntax = DisassembleSyntax.Masm,
+ RawInstruction = rawInstructions[2],
+ DisassembleSyntax = DisassembleSyntax.Gas,
ReferencedAddress = ExpectedResultAddress,
IsReferencedAddressIndirect = true,
}, ConfigureCustomEquivalency);
@@ -100,4 +93,3 @@ public void Decode()
});
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.IsLdrLiteral64.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.IsLdrLiteral64.cs
index f3dbae8698..1e11992a80 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.IsLdrLiteral64.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.IsLdrLiteral64.cs
@@ -1,9 +1,8 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
-using static AsmArm64.Arm64RegisterX;
using static AsmArm64.Arm64RegisterW;
+using static AsmArm64.Arm64RegisterX;
+using Arm64Disassembler = BenchmarkDotNet.Disassemblers.Arm64Disassembler;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -17,7 +16,7 @@ public void IsLdrLiteral64()
var rawInstruction = Arm64InstructionFactory.LDR(X5, label: expectedOffsetBytes);
// Act
- var result = Arm64DisassemblerHelper.IsLdrLiteral64(rawInstruction, out var rt, out var offsetBytes);
+ var result = Arm64Disassembler.IsLdrLiteral64(rawInstruction, out var rt, out var offsetBytes);
// Assert
result.Should().BeTrue();
@@ -44,7 +43,7 @@ public void IsLdrLiteral64_WithMinValue()
var rawInstruction = Arm64InstructionFactory.LDR(X0, label: new Arm64LabelOffset(expectedOffsetBytes));
// Act
- var result = Arm64DisassemblerHelper.IsLdrLiteral64(rawInstruction, out var rt, out var offsetBytes);
+ var result = Arm64Disassembler.IsLdrLiteral64(rawInstruction, out var rt, out var offsetBytes);
// Assert
result.Should().BeTrue();
@@ -71,7 +70,7 @@ public void IsLdrLiteral64_WithMaxValue()
var rawInstruction = Arm64InstructionFactory.LDR(X0, label: new Arm64LabelOffset(expectedOffsetBytes));
// Act
- var result = Arm64DisassemblerHelper.IsLdrLiteral64(rawInstruction, out var rt, out var offsetBytes);
+ var result = Arm64Disassembler.IsLdrLiteral64(rawInstruction, out var rt, out var offsetBytes);
// Assert
result.Should().BeTrue();
@@ -96,7 +95,7 @@ public void IsLdrLiteral64_With32bitRegister_ShouldBeFalse()
var rawInstruction = Arm64InstructionFactory.LDR(W5, label: 0x100);
// Act
- var result = Arm64DisassemblerHelper.IsLdrLiteral64(rawInstruction, out var rt, out var offsetBytes);
+ var result = Arm64Disassembler.IsLdrLiteral64(rawInstruction, out var rt, out var offsetBytes);
// Assert
result.Should().BeFalse();
@@ -109,4 +108,3 @@ public void IsLdrLiteral64_With32bitRegister_ShouldBeFalse()
registerOperand.Value.Should().Be((Arm64RegisterAny) W5);
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryFollowJumpTrampoline.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryFollowJumpTrampoline.cs
index ffb835c38b..7433d8ff07 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryFollowJumpTrampoline.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryFollowJumpTrampoline.cs
@@ -1,14 +1,12 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
using BenchmarkDotNet.Disassemblers;
-using static AsmArm64.Arm64RegisterX;
using static AsmArm64.Arm64RegisterW;
+using static AsmArm64.Arm64RegisterX;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
-public partial class Arm64DisassemblerTests
+public partial class Arm64DisassemblerTests : Arm64DisassemblerTestBase
{
private const int DmbIshldOffset = 4;
@@ -245,4 +243,3 @@ public void TryFollowJumpTrampoline_NonStubHead_ShouldReturnFalse()
result.Should().BeFalse();
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
index 006710b423..f5ef9f61b7 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryGetReferencedAddress.cs
@@ -1,11 +1,10 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
using BenchmarkDotNet.Disassemblers;
-using Gee.External.Capstone.Arm64;
using Microsoft.Diagnostics.Runtime.Interfaces;
using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64InstructionFactory;
+using Arm64Disassembler = BenchmarkDotNet.Disassemblers.Arm64Disassembler;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -13,17 +12,20 @@ public partial class Arm64DisassemblerTests
{
private Arm64RegisterValueAccumulator CreateValueAccumulator(
IClrRuntime clrRuntime,
- ulong initialValue,
- Arm64RegisterId registerId = Arm64RegisterId.ARM64_REG_X0,
+ ulong initialValue = 0,
+ Arm64RegisterAny trackedRegister = default,
Arm64RegisterValueAccumulator.State state = Arm64RegisterValueAccumulator.State.ExpectingMovk)
{
var valueAccumulator = new Arm64RegisterValueAccumulator();
valueAccumulator.Init(clrRuntime);
- valueAccumulator.Reset(state: state, registerId: registerId, value: initialValue);
+ valueAccumulator.Reset(state: state, value: initialValue, register: trackedRegister);
- valueAccumulator.HasValue.Should().BeTrue();
- valueAccumulator.Value.Should().Be(initialValue);
- valueAccumulator.RegisterId.Should().Be(registerId);
+ if (initialValue != 0)
+ {
+ valueAccumulator.HasValue.Should().BeTrue();
+ valueAccumulator.Value.Should().Be(initialValue);
+ }
+ valueAccumulator.TrackedRegister.Should().Be(trackedRegister);
return valueAccumulator;
}
@@ -33,16 +35,17 @@ public void TryGetReferencedAddress_With_BR()
{
// Arrange
using var clrRuntime = CreateMockClrRuntime();
- var valueAccumulator = CreateValueAccumulator(clrRuntime, initialValue: 0x100);
- var rawInstruction = Arm64InstructionFactory.BR(X0);
+ var valueAccumulator = CreateValueAccumulator(clrRuntime, trackedRegister: X0, initialValue: 0x100);
+ var instruction = BR(X0).ToArm64Instruction();
// Act
- var result = Arm64DisassemblerHelper.TryGetReferencedAddress(
- rawInstruction.ToCapstoneArm64Instruction(),
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
valueAccumulator,
pointerSize: 0, // This parameter is not used.
- out ulong referencedAddress,
- out bool isReferencedAddressIndirect);
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
// Assert
result.Should().BeTrue();
@@ -55,16 +58,17 @@ public void TryGetReferencedAddress_With_BLR()
{
// Arrange
using var clrRuntime = CreateMockClrRuntime();
- var valueAccumulator = CreateValueAccumulator(clrRuntime, initialValue: 0x100);
- var rawInstruction = Arm64InstructionFactory.BLR(X0);
+ var valueAccumulator = CreateValueAccumulator(clrRuntime, trackedRegister: X0, initialValue: 0x100);
+ var instruction = BLR(X0).ToArm64Instruction();
// Act
- var result = Arm64DisassemblerHelper.TryGetReferencedAddress(
- rawInstruction.ToCapstoneArm64Instruction(),
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
valueAccumulator,
pointerSize: 0, // This parameter is not used.
- out ulong referencedAddress,
- out bool isReferencedAddressIndirect);
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
// Assert
result.Should().BeTrue();
@@ -79,20 +83,72 @@ public void TryGetReferencedAddress_WithAdrpThenBr_ResolvesPageAddress()
using var clrRuntime = CreateMockClrRuntime();
var accumulator = new Arm64RegisterValueAccumulator();
accumulator.Init(clrRuntime);
- accumulator.Feed(Arm64TestInstructions.Adrp(X0, 0x2000)); // adrp x0, #0x2000
- var rawInstruction = Arm64InstructionFactory.BR(X0);
+ accumulator.Feed(ADRP(X0, 0x2000).ToArm64Instruction(), DummyBaseAddress); // adrp x0, #0x2000
+ var instruction = BR(X0).ToArm64Instruction();
// Act
- var result = Arm64DisassemblerHelper.TryGetReferencedAddress(
- rawInstruction.ToCapstoneArm64Instruction(),
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
accumulator,
pointerSize: 0, // This parameter is not used.
- out ulong referencedAddress,
- out bool isReferencedAddressIndirect);
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
// Assert
result.Should().BeTrue();
- referencedAddress.Should().Be(0x2000); // no ADD between ADRP and BR, so the page address itself must resolve.
+ referencedAddress.Should().Be(DummyBaseAddress + 0x2000); // There is no ADD between ADRP and BR, so the page address itself must resolve.
+ isReferencedAddressIndirect.Should().BeTrue();
+ }
+
+ [Fact]
+ public void TryGetReferencedAddress_WithAdrpAtUnalignedAddress_ResolvesPageAlignedAddress()
+ {
+ // Arrange
+ const ulong AdrpAddress = DummyBaseAddress + 0x1234; // Not page aligned.
+ using var clrRuntime = CreateMockClrRuntime();
+ var accumulator = new Arm64RegisterValueAccumulator();
+ accumulator.Init(clrRuntime);
+ accumulator.Feed(ADRP(X0, 0x2000).ToArm64Instruction(), AdrpAddress); // adrp x0, #0x2000
+ var instruction = BR(X0).ToArm64Instruction();
+
+ // Act
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
+ accumulator,
+ pointerSize: 0, // This parameter is not used.
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
+
+ // Assert
+ result.Should().BeTrue();
+ referencedAddress.Should().Be(DummyBaseAddress + 0x3000); // ADRP is relative to the page containing the instruction.
+ isReferencedAddressIndirect.Should().BeTrue();
+ }
+
+ [Fact]
+ public void TryGetReferencedAddress_WithAdrpNegativeOffset_ResolvesPageAddress()
+ {
+ // Arrange
+ using var clrRuntime = CreateMockClrRuntime();
+ var accumulator = new Arm64RegisterValueAccumulator();
+ accumulator.Init(clrRuntime);
+ accumulator.Feed(ADRP(X0, -0x1000).ToArm64Instruction(), DummyBaseAddress + 0x1234); // adrp x0, #-0x1000
+ var instruction = BR(X0).ToArm64Instruction();
+
+ // Act
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
+ accumulator,
+ pointerSize: 0, // This parameter is not used.
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
+
+ // Assert
+ result.Should().BeTrue();
+ referencedAddress.Should().Be(DummyBaseAddress); // (DummyBaseAddress + 0x1000) - 0x1000
isReferencedAddressIndirect.Should().BeTrue();
}
@@ -101,20 +157,45 @@ public void TryGetReferencedAddress_With_BranchRelative()
{
// Arrange
using var clrRuntime = CreateMockClrRuntime();
- var valueAccumulator = CreateValueAccumulator(clrRuntime, initialValue: 0x100);
+ var valueAccumulator = CreateValueAccumulator(clrRuntime, trackedRegister: X0);
var rawInstruction = Arm64InstructionFactory.B(0x200);
+ var instruction = Arm64Instruction.Decode(rawInstruction);
+
+ // Act
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
+ valueAccumulator,
+ pointerSize: 0, // This parameter is not used.
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
+
+ // Assert
+ result.Should().BeTrue();
+ referencedAddress.Should().Be(DummyBaseAddress + 0x200);
+ isReferencedAddressIndirect.Should().BeFalse();
+ }
+
+ [Fact]
+ public void TryGetReferencedAddress_With_BranchRelative_Minus()
+ {
+ // Arrange
+ using var clrRuntime = CreateMockClrRuntime();
+ var valueAccumulator = CreateValueAccumulator(clrRuntime, trackedRegister: X0);
+ var instruction = B(-0x200).ToArm64Instruction();
// Act
- var result = Arm64DisassemblerHelper.TryGetReferencedAddress(
- rawInstruction.ToCapstoneArm64Instruction(DummyBaseAddress),
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
valueAccumulator,
pointerSize: 0, // This parameter is not used.
- out ulong referencedAddress,
- out bool isReferencedAddressIndirect);
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
// Assert
result.Should().BeTrue();
- referencedAddress.Should().Be(DummyBaseAddress + 0x200); // Verify absolute address is returned.
+ referencedAddress.Should().Be(DummyBaseAddress - 0x200);
isReferencedAddressIndirect.Should().BeFalse();
}
@@ -123,16 +204,17 @@ public void TryGetReferencedAddress_DontMatchCondition_ShouldReturnFalse()
{
// Arrange
using var clrRuntime = CreateMockClrRuntime();
- var valueAccumulator = CreateValueAccumulator(clrRuntime, initialValue: 0x100);
- var rawInstruction = Arm64InstructionFactory.RET(); // `ret` instruction is not BranchRelative group
+ var valueAccumulator = CreateValueAccumulator(clrRuntime, trackedRegister: X0, initialValue: 0x0);
+ var instruction = RET().ToArm64Instruction(); // `ret` instruction is not BranchRelative group
// Act
- var result = Arm64DisassemblerHelper.TryGetReferencedAddress(
- rawInstruction.ToCapstoneArm64Instruction(),
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
valueAccumulator,
pointerSize: 0, // This parameter is not used.
- out ulong referencedAddress,
- out bool isReferencedAddressIndirect);
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
// Assert
result.Should().BeFalse();
@@ -143,19 +225,68 @@ public void TryGetReferencedAddress_With_BR_DifferentRegister_ShouldReturnFalse(
{
// Arrange
using var clrRuntime = CreateMockClrRuntime();
- var valueAccumulator = CreateValueAccumulator(clrRuntime, initialValue: 0x100);
- var rawInstruction = Arm64InstructionFactory.BR(X1);
+ var valueAccumulator = CreateValueAccumulator(clrRuntime, trackedRegister: X0, initialValue: 0x100);
+ var instruction = BR(X1).ToArm64Instruction();
// Act
- var result = Arm64DisassemblerHelper.TryGetReferencedAddress(
- rawInstruction.ToCapstoneArm64Instruction(),
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
valueAccumulator,
pointerSize: 0, // This parameter is not used.
- out ulong referencedAddress,
- out bool isReferencedAddressIndirect);
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
// Assert
result.Should().BeFalse();
}
+
+ [Fact]
+ public void TryGetReferencedAddress_With_TBZ_ResolvesLabelOffset()
+ {
+ // Arrange
+ using var clrRuntime = CreateMockClrRuntime();
+ var valueAccumulator = CreateValueAccumulator(clrRuntime, trackedRegister: X0);
+ var rawInstruction = Arm64InstructionFactory.TBZ(X0, 3, new Arm64LabelOffset(0x200)); // tbz x0, #3, #0x200
+ var instruction = Arm64Instruction.Decode(rawInstruction);
+
+ // Act
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
+ valueAccumulator,
+ pointerSize: 0, // This parameter is not used.
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
+
+ // Assert
+ result.Should().BeTrue();
+ // The bit position imm value (3) is ignored.
+ referencedAddress.Should().Be(DummyBaseAddress + 0x200);
+ isReferencedAddressIndirect.Should().BeFalse();
+ }
+
+ [Fact]
+ public void TryGetReferencedAddress_With_TBNZ_ResolvesLabelOffset()
+ {
+ // Arrange
+ using var clrRuntime = CreateMockClrRuntime();
+ var valueAccumulator = CreateValueAccumulator(clrRuntime, trackedRegister: X0);
+ var instruction = TBNZ(X0, 3, new Arm64LabelOffset(0x200)).ToArm64Instruction();
+
+ // Act
+ var result = Arm64Disassembler.TryGetReferencedAddress(
+ instruction,
+ DummyBaseAddress,
+ valueAccumulator,
+ pointerSize: 0, // This parameter is not used.
+ out var referencedAddress,
+ out var isReferencedAddressIndirect);
+
+ // Assert
+ // The bit position (3) must not be mistaken for the branch target.
+ result.Should().BeTrue();
+ referencedAddress.Should().Be(DummyBaseAddress + 0x200);
+ isReferencedAddressIndirect.Should().BeFalse();
+ }
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryReadStubHead.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryReadStubHead.cs
index 02ab988298..35722d907d 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryReadStubHead.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryReadStubHead.cs
@@ -1,7 +1,6 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
+using Arm64Disassembler = BenchmarkDotNet.Disassemblers.Arm64Disassembler;
using Arm64RegisterX = AsmArm64.Arm64RegisterX;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -28,7 +27,7 @@ public void TryReadStubHead()
var dataReader = clrRuntime.DataTarget.DataReader;
// Act
- var result = Arm64DisassemblerHelper.TryReadStubHead(
+ var result = Arm64Disassembler.TryReadStubHead(
dataReader,
BaseAddress,
out ulong parseBase,
@@ -63,7 +62,7 @@ public void TryReadStubHead_PreDotNet10()
var dataReader = clrRuntime.DataTarget.DataReader;
// Act
- var result = Arm64DisassemblerHelper.TryReadStubHead(
+ var result = Arm64Disassembler.TryReadStubHead(
dataReader,
BaseAddress,
out ulong parseBase,
@@ -99,7 +98,7 @@ public void TryReadStubHead_InsufficientInstructions_ShouldReturnFalse()
var dataReader = clrRuntime.DataTarget.DataReader;
// Act
- var result = Arm64DisassemblerHelper.TryReadStubHead(
+ var result = Arm64Disassembler.TryReadStubHead(
dataReader,
BaseAddress,
out ulong parseBase,
@@ -130,7 +129,7 @@ public void TryReadStubHead_PreDotNet10_InsufficientInstructions_ShouldReturnFal
var dataReader = clrRuntime.DataTarget.DataReader;
// Act
- var result = Arm64DisassemblerHelper.TryReadStubHead(
+ var result = Arm64Disassembler.TryReadStubHead(
dataReader,
BaseAddress,
out ulong parseBase,
@@ -142,4 +141,3 @@ public void TryReadStubHead_PreDotNet10_InsufficientInstructions_ShouldReturnFal
result.Should().BeFalse();
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryResolvePreCode.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryResolvePreCode.cs
index 22ff8b694c..1366da854f 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryResolvePreCode.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryResolvePreCode.cs
@@ -1,9 +1,8 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
using static AsmArm64.Arm64RegisterW;
using static AsmArm64.Arm64RegisterX;
+using Arm64Disassembler = BenchmarkDotNet.Disassemblers.Arm64Disassembler;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -34,7 +33,7 @@ public void TryResolvePrecode_StubPrecode()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeTrue();
@@ -68,7 +67,7 @@ public void TryResolvePrecode_FixupPrecode()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeTrue();
@@ -102,7 +101,7 @@ public void TryResolvePrecode_FixupPrecodeCode_Fixup()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeTrue();
@@ -134,7 +133,7 @@ public void TryResolvePrecode_CallCountingStub()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeTrue();
@@ -161,7 +160,7 @@ public void TryResolvePrecode_NoStubHead_ShouldBeFalse()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeFalse();
@@ -188,7 +187,7 @@ public void TryResolvePrecode_NoMatch_ShouldBeFalse()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeFalse();
@@ -220,7 +219,7 @@ public void TryResolvePrecode_StubPrecode_FailedToRead_ShouldBeFalse()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeFalse();
@@ -252,7 +251,7 @@ public void TryResolvePrecode_FixupPrecode_FailedToRead_ShouldBeFalse()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeFalse();
@@ -284,7 +283,7 @@ public void TryResolvePrecode_FixupPrecodeCode_Fixup_FailedToRead_ShouldBeFalse(
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeFalse();
@@ -314,10 +313,9 @@ public void TryResolvePrecode_CallCountingStub_FailedToRead_ShouldBeFalse()
// Act
ulong address = BaseAddress;
- var result = Arm64DisassemblerHelper.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
+ var result = Arm64Disassembler.TryResolvePrecode(dataReader, ref address, out var isPrestubMd);
// Assert
result.Should().BeFalse();
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryTranslateAddressToName.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryTranslateAddressToName.cs
index c2c33f0c5d..81a77501ad 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryTranslateAddressToName.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64DisassemblerTests.TryTranslateAddressToName.cs
@@ -1,5 +1,3 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
using BenchmarkDotNet.Disassemblers;
@@ -316,4 +314,3 @@ public void TryTranslateAddressToName_GetTypeByMethodTable()
state.Todo.Should().BeEmpty();
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.ShiftKinds.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.ShiftKinds.cs
new file mode 100644
index 0000000000..e7254ace5f
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.ShiftKinds.cs
@@ -0,0 +1,79 @@
+using AsmArm64;
+using AwesomeAssertions;
+using static AsmArm64.Arm64InstructionFactory;
+using static AsmArm64.Arm64RegisterX;
+using BenchmarkDotNet.Disassemblers;
+
+
+namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
+
+public partial class Arm64InstructionExtensionsTests
+{
+ [Theory]
+ [InlineData(Arm64ShiftKind.LSL, 0, 0x1234L, 0x1234L)]
+ [InlineData(Arm64ShiftKind.LSL, 4, 0x1234L, 0x12340L)]
+ [InlineData(Arm64ShiftKind.LSL, 12, 0x123L, 0x123000L)]
+ public void TryShiftImmediate_Lsl_ShouldShiftLeft(Arm64ShiftKind kind, byte amount, long value, long expected)
+ {
+ // Act
+ var result = Arm64InstructionExtensions.TryShiftImmediateValue(value, kind, amount, out var shifted);
+
+ // Assert
+ result.Should().BeTrue();
+ shifted.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(Arm64ShiftKind.LSR, 0, 0x1234L, 0x1234L)]
+ [InlineData(Arm64ShiftKind.LSR, 4, 0x1234L, 0x123L)]
+ [InlineData(Arm64ShiftKind.LSR, 4, -1L, 0x0FFFFFFFFFFFFFFFL)]
+ public void TryShiftImmediate_Lsr_ShouldShiftRightLogically(Arm64ShiftKind kind, byte amount, long value, long expected)
+ {
+ // Act
+ var result = Arm64InstructionExtensions.TryShiftImmediateValue(value, kind, amount, out var shifted);
+
+ // Assert
+ result.Should().BeTrue();
+ shifted.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(Arm64ShiftKind.ASR, 0, 0x1234L, 0x1234L)]
+ [InlineData(Arm64ShiftKind.ASR, 4, 0x1234L, 0x123L)]
+ [InlineData(Arm64ShiftKind.ASR, 4, -16L, -1L)]
+ public void TryShiftImmediate_Asr_ShouldShiftRightArithmetically(Arm64ShiftKind kind, byte amount, long value, long expected)
+ {
+ // Act
+ var result = Arm64InstructionExtensions.TryShiftImmediateValue(value, kind, amount, out var shifted);
+
+ // Assert
+ result.Should().BeTrue();
+ shifted.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(Arm64ShiftKind.ROR, 0, 0x1234L, 0x1234L)]
+ [InlineData(Arm64ShiftKind.ROR, 4, 0x1234L, 0x4000000000000123L)]
+ public void TryShiftImmediate_Ror_ShouldRotateRight(Arm64ShiftKind kind, byte amount, long value, long expected)
+ {
+ // Act
+ var result = Arm64InstructionExtensions.TryShiftImmediateValue(value, kind, amount, out var shifted);
+
+ // Assert
+ result.Should().BeTrue();
+ shifted.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(Arm64ShiftKind.MSL)]
+ [InlineData(Arm64ShiftKind.Invalid)]
+ public void TryShiftImmediate_UnsupportedKind_ShouldReturnFalse(Arm64ShiftKind kind)
+ {
+ // Act
+ var result = Arm64InstructionExtensions.TryShiftImmediateValue(0x1234, kind, 4, out var shifted);
+
+ // Assert
+ result.Should().BeFalse();
+ shifted.Should().Be(0);
+ }
+}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.ShiftedImmediate.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.ShiftedImmediate.cs
new file mode 100644
index 0000000000..0af94efdf7
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.ShiftedImmediate.cs
@@ -0,0 +1,68 @@
+using AsmArm64;
+using AwesomeAssertions;
+using static AsmArm64.Arm64InstructionFactory;
+using static AsmArm64.Arm64RegisterX;
+using BenchmarkDotNet.Disassemblers;
+
+
+namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
+
+public partial class Arm64InstructionExtensionsTests
+{
+
+ [Fact]
+ public void TryGetShiftedImmediateValue_WithNoShiftOperand_ShouldReturnImmediate()
+ {
+ // Arrange
+ var instruction = Arm64Instruction.Decode(ADD(X0, X0, 0x05)); // add x0, x0, #5
+
+ // Act
+ var result = instruction.TryGetShiftedImmediateValue(2, out var value);
+
+ // Assert
+ result.Should().BeTrue();
+ value.Should().Be(5);
+ }
+
+ [Fact]
+ public void TryGetShiftedImmediateValue_WithLslShift_ShouldApplyShift()
+ {
+ // Arrange
+ var instruction = Arm64Instruction.Decode(ADD(X0, X0, 0x05, amount: 12)); // add x0, x0, #5, lsl #12
+
+ /// Act
+ var result = instruction.TryGetShiftedImmediateValue(2, out var value);
+
+ // Assert
+ result.Should().BeTrue();
+ value.Should().Be(5L << 12);
+ }
+
+ [Fact]
+ public void TryGetShiftedImmediateValue_WithOutOfRangeIndex_ShouldReturnFalse()
+ {
+ // Arrange
+ var instruction = Arm64Instruction.Decode(ADD(X0, X0, 0x05)); // add x0, x0, #5
+
+ // Act
+ var result = instruction.TryGetShiftedImmediateValue(99, out var value);
+
+ // Assert
+ result.Should().BeFalse();
+ value.Should().Be(0);
+ }
+
+ [Fact]
+ public void TryGetShiftedImmediateValue_WithNonImmediateOperand_ShouldReturnFalse()
+ {
+ // Arrange
+ var instruction = Arm64Instruction.Decode(ADD(X0, X0, 0x05)); // add x0, x0, #5
+
+ // Act
+ var result = instruction.TryGetShiftedImmediateValue(0, out var value);
+
+ // Assert
+ result.Should().BeFalse();
+ value.Should().Be(0);
+ }
+}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.cs
new file mode 100644
index 0000000000..9e5e1a7f67
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionExtensionsTests.cs
@@ -0,0 +1,12 @@
+using AsmArm64;
+using AwesomeAssertions;
+using static AsmArm64.Arm64InstructionFactory;
+using static AsmArm64.Arm64RegisterX;
+using BenchmarkDotNet.Disassemblers;
+
+
+namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
+
+public partial class Arm64InstructionExtensionsTests
+{
+}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.IntructionPatterns.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.IntructionPatterns.cs
new file mode 100644
index 0000000000..428b4dea90
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.IntructionPatterns.cs
@@ -0,0 +1,60 @@
+using AsmArm64;
+using AwesomeAssertions;
+using BenchmarkDotNet.Disassemblers;
+using static AsmArm64.Arm64RegisterW;
+using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64RegisterSP;
+using static AsmArm64.Arm64InstructionFactory;
+
+namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
+
+public partial class Arm64InstructionFormatterTests : Arm64DisassemblerTestBase
+{
+ [Theory]
+ [MemberData(nameof(TestData.FormatOptionPatterns), MemberType = typeof(TestData))]
+ public void FormatInstruction(uint rawInstruction, string expected)
+ {
+ // Arrange
+ var asm = ToArm64Asm(rawInstruction, address: 0);
+
+ // Act
+ var result = Format(asm);
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ public static partial class TestData
+ {
+ public static TheoryData FormatOptionPatterns => new()
+ {
+ // Test cases to validate LabelOffsetFormat
+ { B(0x8), "b #8"}, // single digits representable value should be rendered with decimal format.
+ { B(0x100), "b #0x100"},
+ { B(0xC), "b #0xC"},
+
+ // Test cases to validate ImmediateFormat
+ { MOVZ(W0, 0x8), "movz w0, #8"},
+ { MOVZ(X0, 0x100), "movz x0, #0x100"},
+ { MOVZ(X0, 0xFFF), "movz x0, #0xFFF"},
+ { MOVZ(X0, 0x100, amount:16), "movz x0, #0x100, lsl #16"}, // Shift amount should be rendered as decimal.
+
+ // Test cases to validate MemoryOffsetFormat
+ { LDR(X0, new Arm64ImmediateMemoryAccessor(SP, 0x8)), "ldr x0, [sp, #8]" },
+ { LDR(X0, new Arm64ImmediateMemoryAccessor(SP, 0x100)), "ldr x0, [sp, #0x100]" },
+ { LDR(X0, new Arm64ImmediateMemoryAccessor(SP, 0xf00)), "ldr x0, [sp, #0xF00]" },
+
+ // Test cases to validate minus label offset.
+ { B(-0x8), "b #-8"},
+ { B(-0x100), "b #-0x100"},
+ { ADRP(X0, -0x1000), "adrp x0, #-0x1000"},
+
+ // Test case to validate instruction aliases.
+ { CMP(X0,0x00), "cmp x0, #0"},
+ { SUBS(XZR, X0, 0x00), "cmp x0, #0"}, // Resolved to CMP alias
+
+ // Others
+ { NOP(), "nop"},
+ };
+ }
+}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.LabelResolution.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.LabelResolution.cs
new file mode 100644
index 0000000000..3181b3b4d0
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.LabelResolution.cs
@@ -0,0 +1,77 @@
+using AsmArm64;
+using AwesomeAssertions;
+using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64InstructionFactory;
+
+namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
+
+public partial class Arm64InstructionFormatterTests : Arm64DisassemblerTestBase
+{
+ [Theory]
+ [InlineData(0x2000, "adrp x0, #0xF00000003000")]
+ [InlineData(-0x1000, "adrp x0, #0xF00000000000")]
+ public void FormatInstruction_WithAdrp_ResolvesLabelRelativeToContainingPage(int labelOffset, string expected)
+ {
+ // Arrange
+ const ulong InstructionAddress = DummyBaseAddress + 0x1234; // Not page aligned.
+ var asm = ToArm64Asm(ADRP(X0, labelOffset), InstructionAddress);
+
+ // Act
+ var result = Format(asm);
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Fact]
+ public void FormatInstruction_WithLdrLiteral_ReplacesAddressWithLabel()
+ {
+ // Arrange
+ var asm = ToArm64Asm(LDR(X0, 0x100), DummyBaseAddress); // ldr x0, #0x100
+ var symbols = new Dictionary
+ {
+ [DummyBaseAddress + 0x100] = "M00_L00",
+ };
+
+ // Act
+ var result = Format(asm, addressMappings: symbols);
+
+ // Assert
+ result.Should().Be("ldr x0, M00_L00");
+ }
+
+ [Theory]
+ [InlineData(-4, "b #-4")]
+ [InlineData(-8, "b #-8")]
+ [InlineData(-12, "b #-0xC")]
+ [InlineData(-16, "b #-0x10")]
+ public void FormatInstruction_WithBranchNegativeSmallOffset_AtZeroAddress_KeepsRawOffset(int labelOffset, string expected)
+ {
+ // Arrange
+ var asm = ToArm64Asm(B(new Arm64LabelOffset(labelOffset)), 0);
+
+ // Act
+ var result = Format(asm);
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Fact]
+ public void FormatInstruction_WithLongSymbolName_ResolvesLabel()
+ {
+ // Arrange
+ var symbolName = new string('x', 512);
+ var asm = ToArm64Asm(LDR(X0, 0x100), DummyBaseAddress); // ldr x0, #0x100
+ var symbols = new Dictionary
+ {
+ [DummyBaseAddress + 0x100] = symbolName,
+ };
+
+ // Act
+ var result = Format(asm, addressMappings: symbols);
+
+ // Assert
+ result.Should().Be($"ldr x0, {symbolName}");
+ }
+}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.MnemonicAliases.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.MnemonicAliases.cs
new file mode 100644
index 0000000000..b607be3008
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.MnemonicAliases.cs
@@ -0,0 +1,62 @@
+using AsmArm64;
+using AwesomeAssertions;
+using AwesomeAssertions.Execution;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Disassemblers;
+using Iced.Intel;
+using System.Security.Cryptography;
+using static AsmArm64.Arm64InstructionFactory;
+using static AsmArm64.Arm64RegisterD;
+using static AsmArm64.Arm64RegisterSP;
+using static AsmArm64.Arm64RegisterW;
+using static AsmArm64.Arm64RegisterX;
+
+namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
+
+public partial class Arm64InstructionFormatterTests : Arm64DisassemblerTestBase
+{
+ [Theory]
+ [MemberData(nameof(TestData.MnemonicPatterns), MemberType = typeof(TestData))]
+ public void VerifyMnemonicAlias(string expected, bool useBaseInstruction, uint rawInstruction)
+ {
+ // Arrange
+ var asm = ToArm64Asm(rawInstruction, address: 0);
+
+ // Act
+ var result = Arm64InstructionFormatter.GetInstructionDetails(asm.Instruction, asm.InstructionPointer).Mnemonic;
+
+ // Assert
+ result.Should().Be(expected);
+
+ var instruction = Arm64Instruction.Decode(rawInstruction);
+ if (useBaseInstruction)
+ {
+ result.Should().NotBe(instruction.Mnemonic.ToText());
+ instruction = instruction.AsBaseInstruction();
+ }
+ result.Should().Be(instruction.Mnemonic.ToText());
+ }
+
+ public static partial class TestData
+ {
+ public static TheoryData MnemonicPatterns => new()
+ {
+ { "mov", false, MOV(X0, 1) }, // `Arm64InstructionFactory.MOV` don't accept `0`. See: https://github.com/xoofx/AsmArm64/issues/16
+ { "movi", false, MOVI(D0, 0) },
+ { "movk", false, MOVK(X0, 0) },
+
+ // Folowing instructions are resolved to `mov` alias by default.
+ { "movn", true, MOVN(X0, 0) },
+ { "movn", true, MOV_MOVN(X0, new Arm64InvertedShiftedImmediate64(0, 0)) },
+ { "movz", true, MOVZ(X0, 0) },
+
+ // Folowing instructions are resolved to `subs` by default.
+ { "cmp", false, CMP(X0, X1) },
+ { "cmn", false, CMN(X0, X1) },
+
+ // Other instructions are resolved with default setting(Arm64InstructionAliasMode.Preferred)
+ { "retaa", false, RETAA() },
+ };
+ }
+}
+
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.Scenario.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.Scenario.cs
new file mode 100644
index 0000000000..d49b256bc3
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.Scenario.cs
@@ -0,0 +1,80 @@
+using AsmArm64;
+using AwesomeAssertions;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Disassemblers;
+using static AsmArm64.Arm64RegisterW;
+using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64RegisterSP;
+using static AsmArm64.Arm64InstructionFactory;
+using AwesomeAssertions.Execution;
+
+namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
+
+public partial class Arm64InstructionFormatterTests : Arm64DisassemblerTestBase
+{
+ [Fact]
+ public void FormatInstruction_IntroDisassembly_SumLocal()
+ {
+ // Arrange
+ var rawInstructions = new[]
+ {
+ STP(X29, X30, new Arm64ImmediateMemoryAccessor(SP, -0x10).Pre), // stp x29, x30, [sp, #-0x10]!
+ MOV(X29, SP), // mov x29, sp
+ LDR(X0, new Arm64ImmediateMemoryAccessor(X0, 8)), // ldr x0, [x0, #8]
+ MOV(W1, WZR), // mov w1, wzr
+ LDR(W2, new Arm64ImmediateMemoryAccessor(X0, 8)), // ldr w2, [x0, #8]
+ CMP(W2, 0), // cmp w2, #0
+ B(Arm64ConditionalKind.LE, 0x18), // b.le #0x18
+ ADD(X0,X0, 0x10), // add x0, x0, #0x10
+ // M00_L00:
+ LDR(W3, new Arm64BaseMemoryAccessor(X0), 4), // ldr w3, [x0], #4
+ ADD(W1,W3,W1), // add w1, w3, w1
+ SUB(W2,W2, 1), // sub w2, w2, #1
+ CBNZ(W2, -0x0C), // cbnz w2, #-0xC
+ // M00_L01:
+ MOV(W0, W1), // mov w0, w1
+ LDP(X29, X30, new Arm64BaseMemoryAccessor(SP), 0x10), // ldp x29, x30, [sp], #0x10
+ RET(),
+ };
+ PrintInstructions(rawInstructions);
+
+ const ulong BaseAddress = 0xFF01097000A0;
+ var symbols = new Dictionary
+ {
+ [0xFF01097000C0] = "M00_L00",
+ [0xFF01097000D0] = "M00_L01",
+ };
+ Arm64Asm[] asms = Decode(rawInstructions, BaseAddress, symbols);
+
+ // Act
+ List results = new();
+ foreach (var asm in asms)
+ {
+ var result = Arm64InstructionFormatter.Format(asm, IcedDefaultFormatterOptions, printInstructionAddresses: true, pointerSize: 8, symbols);
+ results.Add(result);
+ }
+
+ // Assert
+ using (var scope = new AssertionScope())
+ {
+ scope.FormattingOptions.StringPrintLength = 100;
+ results[00].Should().Be("FF01097000A0 stp x29, x30, [sp, #-0x10]!");
+ results[01].Should().Be("FF01097000A4 mov x29, sp");
+ results[02].Should().Be("FF01097000A8 ldr x0, [x0, #8]");
+ results[03].Should().Be("FF01097000AC mov w1, wzr");
+ results[04].Should().Be("FF01097000B0 ldr w2, [x0, #8]");
+ results[05].Should().Be("FF01097000B4 cmp w2, #0");
+ results[06].Should().Be("FF01097000B8 b.le M00_L01");
+ results[07].Should().Be("FF01097000BC add x0, x0, #0x10");
+ // M00_L00:
+ results[08].Should().Be("FF01097000C0 ldr w3, [x0], #4");
+ results[09].Should().Be("FF01097000C4 add w1, w3, w1");
+ results[10].Should().Be("FF01097000C8 sub w2, w2, #1");
+ results[11].Should().Be("FF01097000CC cbnz w2, M00_L00");
+ // M00_L01:
+ results[12].Should().Be("FF01097000D0 mov w0, w1");
+ results[13].Should().Be("FF01097000D4 ldp x29, x30, [sp], #0x10");
+ results[14].Should().Be("FF01097000D8 ret");
+ }
+ }
+}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.cs
index e2e2c1dc74..d5a876d8c9 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64InstructionFormatterTests.cs
@@ -1,122 +1,29 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
-using AwesomeAssertions;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Disassemblers;
-using Iced.Intel;
-using static AsmArm64.Arm64RegisterW;
-using static AsmArm64.Arm64RegisterX;
+
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
public partial class Arm64InstructionFormatterTests : Arm64DisassemblerTestBase
{
- private static readonly FormatterOptions DefaultFormatterOptions = new()
+ private static readonly Iced.Intel.FormatterOptions IcedDefaultFormatterOptions = new()
{
FirstOperandCharIndex = 10, // Use DisassemblyDiagnoserConfig default config value.
};
- private static readonly Arm64InstructionFormattingOptions AsmArm64FormattingOptions = new()
- {
- AliasMode = Arm64InstructionAliasMode.BaseInstruction,
- UseUppercaseText = false,
- UseUppercaseHex = false,
- ImmediateFormat = Arm64NumericFormat.Hexadecimal,
- MemoryOffsetFormat = Arm64NumericFormat.Hexadecimal,
- LabelOffsetFormat = Arm64NumericFormat.Auto,
- };
+ private static readonly Iced.Intel.FormatterOptions IcedEmptyFormatterOptions = new();
+
+ private static readonly Arm64InstructionFormattingOptions FormattingOptions = Arm64InstructionFormatter.DefaultFormattingOptions;
public Arm64InstructionFormatterTests(ITestOutputHelper output)
: base(output)
{
}
- [Theory]
- [MemberData(nameof(TestData.TestData01), MemberType = typeof(TestData))]
- public void FormatInstruction(uint rawInstruction, string expected)
- {
- // Arrange
- var asm = rawInstruction.ToArm64Asm(address: 0);
-
- // Act
- var result = asm.Format(new FormatterOptions());
-
- // Assert
- result.Should().Be(expected);
-
- // Verify AsmArm64 compatibility
- var instruction = rawInstruction.ToAsmArm64Instruction();
- var text = instruction.ToString(AsmArm64FormattingOptions);
-
- text.Should().Be(expected);
- }
-
- [Fact]
- public void FormatInstruction_IntroDisassembly_SumLocal()
- {
- // Arrange
- var rawInstructions = new[]
- {
- Arm64InstructionFactory.STP(X29, X30, new Arm64ImmediateMemoryAccessor(Arm64RegisterSP.SP, -0x10).Pre),
- Arm64InstructionFactory.MOV(X29, Arm64RegisterSP.SP),
- Arm64InstructionFactory.LDR(X0, new Arm64ImmediateMemoryAccessor(X0, 8)),
- Arm64InstructionFactory.MOV(W1, WZR),
- Arm64InstructionFactory.LDR(W2, new Arm64ImmediateMemoryAccessor(X0, 8)),
- Arm64InstructionFactory.CMP(W2, 0),
- Arm64InstructionFactory.B(Arm64ConditionalKind.LE, 24),
- Arm64InstructionFactory.ADD(X0,X0, 0x10),
- // M00_L00:
- Arm64InstructionFactory.LDR(W3, new Arm64BaseMemoryAccessor(X0), 4),
- Arm64InstructionFactory.ADD(W1,W3,W1),
- Arm64InstructionFactory.SUB(W2,W2, 1),
- Arm64InstructionFactory.CBNZ(W2, -12),
- // M00_L01:
- Arm64InstructionFactory.MOV(W0, W1),
- Arm64InstructionFactory.LDP(X29, X30, new Arm64BaseMemoryAccessor(Arm64RegisterSP.SP), 0x10),
- Arm64InstructionFactory.RET(),
- };
- PrintInstructions(rawInstructions);
-
- const ulong BaseAddress = 0xFF01097000A0;
- var symbols = new Dictionary
- {
- [0xFF01097000C0] = "M00_L00",
- [0xFF01097000D0] = "M00_L01",
- };
- Arm64Asm[] asms = GetArm64Asms(rawInstructions, BaseAddress, symbols);
-
- // Act
- List results = new();
- foreach (var asm in asms)
- {
- var result = Arm64InstructionFormatter.Format(asm, DefaultFormatterOptions, printInstructionAddresses: true, pointerSize: 8, symbols);
- results.Add(result);
- }
-
- // Assert
- results.Should().BeEquivalentTo(
- [
- "FF01097000A0 stp x29, x30, [sp, #-0x10]!",
- "FF01097000A4 mov x29, sp",
- "FF01097000A8 ldr x0, [x0, #8]",
- "FF01097000AC mov w1, wzr",
- "FF01097000B0 ldr w2, [x0, #8]",
- "FF01097000B4 cmp w2, #0",
- "FF01097000B8 b.le M00_L01",
- "FF01097000BC add x0, x0, #0x10",
- // M00_L00:
- "FF01097000C0 ldr w3, [x0], #4",
- "FF01097000C4 add w1, w3, w1",
- "FF01097000C8 sub w2, w2, #1",
- "FF01097000CC cbnz w2, M00_L00",
- // M00_L01:
- "FF01097000D0 mov w0, w1",
- "FF01097000D4 ldp x29, x30, [sp], #0x10",
- "FF01097000D8 ret",
- ], options => options.WithStrictOrdering());
- }
-
- private static Arm64Asm[] GetArm64Asms(uint[] rawInstructions, ulong baseAddress, Dictionary symbols)
+ private static Arm64Asm[] Decode(
+ uint[] rawInstructions,
+ ulong baseAddress,
+ Dictionary symbols)
{
using var clrRuntime = CreateMockClrRuntime();
var state = new State(clrRuntime, DummyTargetFrameworkVersion);
@@ -130,38 +37,11 @@ private static Arm64Asm[] GetArm64Asms(uint[] rawInstructions, ulong baseAddress
return helper.Decode(bytes, baseAddress, state, depth: 0, DummyCurrentMethod, DisassemblySyntax.Masm);
}
- public static class TestData
- {
- public static TheoryData TestData01 => new()
- {
- { Arm64InstructionFactory.B(0x8), "b #8"}, // Label value that lower than 0x10 use decimal format.
- { Arm64InstructionFactory.B(0x100), "b #0x100"},
- { Arm64InstructionFactory.MOVZ(W0, 0x8), "movz w0, #0x8"},
- { Arm64InstructionFactory.MOVZ(X0, 0x100), "movz x0, #0x100"},
- { Arm64InstructionFactory.MOVZ(X0, 0xFFF), "movz x0, #0xfff"},
- { Arm64InstructionFactory.MOVZ(X0, 0x100, amount:16), "movz x0, #0x100, lsl #16"},
-
- // TODO: `Gee.External.Capstone` seems not support negative label offset and wrong text is outputted.
- // { Arm64InstructionFactory.B(-8), "b #-8"}, // Gee.External.Capstone don't support minus label offset.
-
- { Arm64InstructionFactory.NOP(), "nop"},
- };
- }
-}
-
-file static class ExtensionMethods
-{
- public static string Format(
- this Arm64Asm asm,
- FormatterOptions options,
- bool printInstructionAddresses = false,
- Dictionary? addressMappings = null)
- {
- uint pointerSize = 8;
- return Arm64InstructionFormatter.Format(asm, options, printInstructionAddresses, pointerSize, addressMappings ?? []);
- }
-
- public static Arm64Asm ToArm64Asm(this uint rawInstruction, ulong address, ulong? referencedAddress = null, bool isReferencedAddressIndirect = false)
+ private static Arm64Asm ToArm64Asm(
+ uint rawInstruction,
+ ulong address,
+ ulong? referencedAddress = null,
+ bool isReferencedAddressIndirect = false)
{
if (referencedAddress <= ushort.MaxValue)
referencedAddress = null;
@@ -169,11 +49,20 @@ public static Arm64Asm ToArm64Asm(this uint rawInstruction, ulong address, ulong
return new Arm64Asm
{
InstructionPointer = address,
- InstructionLength = 4,
+ RawInstruction = rawInstruction,
ReferencedAddress = referencedAddress,
IsReferencedAddressIndirect = isReferencedAddressIndirect,
- Instruction = rawInstruction.ToCapstoneArm64Instruction(address),
+ DisassembleSyntax = DisassembleSyntax.Gas,
};
}
+
+ private static string Format(
+ Arm64Asm asm,
+ Iced.Intel.FormatterOptions? formatterOptions = null,
+ bool printInstructionAddresses = false,
+ Dictionary? addressMappings = null)
+ {
+ formatterOptions ??= IcedEmptyFormatterOptions;
+ return Arm64InstructionFormatter.Format(asm, formatterOptions, printInstructionAddresses, pointerSize: 8, addressMappings ?? []);
+ }
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
index 863074c770..1b403d688a 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.AdrpThenAdd.cs
@@ -1,8 +1,6 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AwesomeAssertions;
-using Gee.External.Capstone.Arm64;
using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64InstructionFactory;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -14,11 +12,12 @@ public void AdrpThenAdd_ShouldCalculateAddress()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Adrp(X0, 0x2000), // adrp x0, #0x2000
- Arm64TestInstructions.Add(X0, X0, 0x123), // add x0, x0, #0x123
+ ADRP(X0, 0x2000), // adrp x0, #0x2000
+ ADD(X0, X0, 0x123), // add x0, x0, #0x123
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -28,7 +27,7 @@ public void AdrpThenAdd_ShouldCalculateAddress()
// Assert
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x2123L);
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
}
[Fact]
@@ -36,11 +35,12 @@ public void AdrpThenAdd_WithNegativeOffset_ShouldClampToZero()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Adrp(X0, -0x1000), // adrp x0, #-0x1000
- Arm64TestInstructions.Add(X0, X0, 0x100), // add x0, x0, #0x100
+ ADRP(X0, -0x1000), // adrp x0, #-0x1000
+ ADD(X0, X0, 0x100), // add x0, x0, #0x100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions); // Note: Capstone print minus offset as `0xFFFFFFFFFFFFF000`
// Act
@@ -57,12 +57,13 @@ public void AdrpThenAdd_WithNegativeOffset_ShouldClampToZero()
public void AdrpThenAdd_ThenAdd_ShouldResetValue()
{
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Adrp(X0, 0x1000), // adrp x0, #0x1000
- Arm64TestInstructions.Add(X0, X0, 0x100), // add x0, x0, #0x100
- Arm64TestInstructions.Add(X0, X0, 0x200), // add x0, x0, #0x200
+ ADRP(X0, 0x1000), // adrp x0, #0x1000
+ ADD(X0, X0, 0x100), // add x0, x0, #0x100
+ ADD(X0, X0, 0x200), // add x0, x0, #0x200
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -79,11 +80,12 @@ public void AdrpThenAdd_WithShiftedImm_ShouldCalculateAddress()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Adrp(X0, 0x1000000), // adrp x0, #0x1000000
- Arm64TestInstructions.Add(X0, X0, 0xFFF, 12), // add x0, x0, #0xFFF, lsl #12
+ ADRP(X0, 0x1000000), // adrp x0, #0x1000000
+ ADD(X0, X0, 0xFFF, amount:12), // add x0, x0, #0xFFF, lsl #12
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -100,11 +102,12 @@ public void AdrpThenAdd_DifferentDestinationRegister_ShouldKeepValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Adrp(X0, 0x2000), // adrp x0, #0x2000
- Arm64TestInstructions.Add(X1, X0, 0x100), // add x1, x0, #0x100
+ ADRP(X0, 0x2000), // adrp x0, #0x2000
+ ADD(X1, X0, 0x100), // add x1, x0, #0x100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -115,7 +118,7 @@ public void AdrpThenAdd_DifferentDestinationRegister_ShouldKeepValue()
// The ADD writes X1, so the tracked X0 value (page address) is still valid.
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x2000UL);
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
}
[Fact]
@@ -123,12 +126,13 @@ public void AdrpThenOther_ThenAdd_ShouldResetValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Adrp(X0, 0x1000), // adrp x0, #0x1000
- Arm64TestInstructions.Movz(X0, 0x100), // movz x0, #0x100
- Arm64TestInstructions.Add(X0, X0, 0x100), // add x1, x0, #0x100
+ ADRP(X0, 0x1000), // adrp x0, #0x1000
+ MOVZ(X0, 0x100), // movz x0, #0x100
+ ADD(X0, X0, 0x100), // add x1, x0, #0x100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -140,4 +144,3 @@ public void AdrpThenOther_ThenAdd_ShouldResetValue()
accumulator.HasValue.Should().BeFalse(); // Register is not matched
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.Movz.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.Movz.cs
index 913c8e064c..840a252418 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.Movz.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.Movz.cs
@@ -1,8 +1,6 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AwesomeAssertions;
-using Gee.External.Capstone.Arm64;
using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64InstructionFactory;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -14,12 +12,13 @@ public void Movz_WithShiftedImmediateValue_ShouldStartNewValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1111, 0), // movz x0, #0x1111
- Arm64TestInstructions.Movz(X0, 0x2222, 16), // movz x0, #0x2222, lsl #16
- Arm64TestInstructions.Movz(X0, 0x3333, 32), // movz x0, #0x3333, lsl #32
+ MOVZ(X0, 0x1111, amount: 0), // movz x0, #0x1111
+ MOVZ(X0, 0x2222, amount: 16), // movz x0, #0x2222, lsl #16
+ MOVZ(X0, 0x3333, amount: 32), // movz x0, #0x3333, lsl #32
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -29,7 +28,7 @@ public void Movz_WithShiftedImmediateValue_ShouldStartNewValue()
// Assert
accumulator.HasValue.Should().BeTrue();
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
accumulator.Value.Should().Be(0x3333_0000_0000);
}
@@ -38,10 +37,11 @@ public void Movz_ZeroImmediate_ShouldNotHaveValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x0), // movz x0, #0x0
+ MOVZ(X0, 0x0), // movz x0, #0x0
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -52,4 +52,3 @@ public void Movz_ZeroImmediate_ShouldNotHaveValue()
accumulator.Value.Should().Be(0UL);
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenBranchConditional.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenBranchConditional.cs
index f10daf20c9..a3efd7169c 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenBranchConditional.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenBranchConditional.cs
@@ -1,9 +1,7 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
-using Gee.External.Capstone.Arm64;
using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64InstructionFactory;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -15,11 +13,12 @@ public void MovzThenCbz_ShouldKeepValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.Cbz(X0, 0x0100), // cbz x0, #0x0100
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ CBZ(X0, 0x0100), // cbz x0, #0x0100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -29,7 +28,7 @@ public void MovzThenCbz_ShouldKeepValue()
// Assert
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x1000);
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
}
[Fact]
@@ -37,11 +36,12 @@ public void MovzThenCbnz_ShouldKeepValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.Cbnz(X0, 0x0100), // cbnz x0, #0x0100
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ CBNZ(X0, 0x0100), // cbnz x0, #0x0100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -51,7 +51,7 @@ public void MovzThenCbnz_ShouldKeepValue()
// Assert
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x1000);
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
}
[Fact]
@@ -59,11 +59,12 @@ public void MovzThenConditionalB_ShouldKeepValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.B(Arm64ConditionalKind.EQ, 0x100), // b.eq x0, #0x0100
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ B(Arm64ConditionalKind.EQ, 0x100), // b.eq x0, #0x0100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -73,7 +74,6 @@ public void MovzThenConditionalB_ShouldKeepValue()
// Assert
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x1000); // branch instruction's immediate value is not affects accumulated value.
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
index d463a18cd0..74b832e740 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenLdr.cs
@@ -1,8 +1,6 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
+using AsmArm64;
using AwesomeAssertions;
-using Gee.External.Capstone.Arm64;
-using System.Reflection;
+using static AsmArm64.Arm64InstructionFactory;
using static AsmArm64.Arm64RegisterX;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -18,11 +16,12 @@ public void MovzThenLdr_SameRegister_ShouldHaveValue()
.AddPointer(MovzOffset, ExpectedResultAddress)
.ToMockClrRuntime();
var accumulator = CreateValueAccumulator(clrRuntime);
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, MovzOffset), // movz x0, #0x1000
- Arm64TestInstructions.Ldr(X0, baseRegister: X0), // ldr x0, [x0]
+ MOVZ(X0, MovzOffset), // movz x0, #0x1000
+ LDR(X0, new Arm64ImmediateMemoryAccessor(X0, 0)), // ldr x0, [x0]
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -31,7 +30,7 @@ public void MovzThenLdr_SameRegister_ShouldHaveValue()
// Assert
accumulator.HasValue.Should().BeTrue();
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
accumulator.Value.Should().Be(ExpectedResultAddress); // It should be value that is returned by ReadPointer.
}
@@ -43,11 +42,12 @@ public void MovzThenLdr_DifferentRegister_ShouldHaveValue()
.AddPointer(MovzOffset, ExpectedResultAddress)
.ToMockClrRuntime();
var accumulator = CreateValueAccumulator(clrRuntime);
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, MovzOffset), // movz x0, #0x1000
- Arm64TestInstructions.Ldr(X1, baseRegister: X0, immediate: 0x0), // ldr x1, [x0]
+ MOVZ(X0, MovzOffset), // movz x0, #0x1000
+ LDR(X1, new Arm64ImmediateMemoryAccessor(X0, 0)), // ldr x1, [x0]
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -56,7 +56,7 @@ public void MovzThenLdr_DifferentRegister_ShouldHaveValue()
// Assert
accumulator.HasValue.Should().BeTrue();
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X1); // Value is loaded to X1 register.
+ accumulator.TrackedRegister.Should().Be(X1); // Value is loaded to X1 register.
accumulator.Value.Should().Be(ExpectedResultAddress); // It should be value that is returned by ReadPointer.
}
@@ -68,11 +68,12 @@ public void MovzThenLdr_WithDisplacement_ShouldNotHaveValue()
.AddPointer(MovzOffset, ExpectedResultAddress)
.ToMockClrRuntime();
var accumulator = CreateValueAccumulator(clrRuntime);
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, MovzOffset), // movz x0, #0x1000
- Arm64TestInstructions.Ldr(X0, baseRegister: X0, immediate: 0x100), // ldr x0, [x0, #0x100]
+ MOVZ(X0, MovzOffset), // movz x0, #0x1000
+ LDR(X0, new Arm64ImmediateMemoryAccessor(X0, 0x100)), // ldr x0, [x0, #0x100]
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -81,9 +82,7 @@ public void MovzThenLdr_WithDisplacement_ShouldNotHaveValue()
// Assert
accumulator.HasValue.Should().BeFalse();
-
- // TODO: Current accumulator don't reset registerId.
- // accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
+ accumulator.TrackedRegister.Should().Be(default);
}
[Fact]
@@ -94,11 +93,12 @@ public void MovzThenLdr_WithIndexRegister_ShouldNotHaveValue()
.AddPointer(MovzOffset, ExpectedResultAddress)
.ToMockClrRuntime();
var accumulator = CreateValueAccumulator(clrRuntime);
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, MovzOffset), // movz x0, #0x1000
- Arm64TestInstructions.Ldr(X0, baseRegister: X0, indexRegister: X1), // ldr x0, [x0, x1]
+ MOVZ(X0, MovzOffset), // movz x0, #0x1000
+ LDR(X0, new Arm64RegisterXExtendMemoryAccessor(X0, X1, new Arm64MemoryExtend())), // ldr x0, [x0, x1]
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -107,9 +107,7 @@ public void MovzThenLdr_WithIndexRegister_ShouldNotHaveValue()
// Assert
accumulator.HasValue.Should().BeFalse();
-
- // TODO: Current accumulator don't reset registerId.
- // accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
+ accumulator.TrackedRegister.Should().Be(default);
}
[Fact]
@@ -117,11 +115,12 @@ public void MovzThenLdrLiteral_ShouldNotHaveValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, MovzOffset), // movz x0, #0x1000
- Arm64TestInstructions.Ldr(X0, 0x100), // ldr x0, #0x100
+ MOVZ(X0, MovzOffset), // movz x0, #0x1000
+ LDR(X0, new Arm64ImmediateMemoryAccessor(X0, 0x100)), // ldr x0, #0x100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -130,9 +129,7 @@ public void MovzThenLdrLiteral_ShouldNotHaveValue()
// Assert
accumulator.HasValue.Should().BeFalse();
-
- // TODO: Current accumulator don't reset registerId.
- // accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
+ accumulator.TrackedRegister.Should().Be(default);
}
[Fact]
@@ -143,11 +140,12 @@ public void MovzThenLdr_ZeroAddress_ShouldNotHaveValue()
.AddPointer(MovzOffset, 0)
.ToMockClrRuntime();
var accumulator = CreateValueAccumulator(clrRuntime);
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, MovzOffset), // movz x0, #0x1000
- Arm64TestInstructions.Ldr(X1, baseRegister: X0), // ldr x1, [x0]
+ MOVZ(X0, MovzOffset), // movz x0, #0x1000
+ LDR(X1, new Arm64ImmediateMemoryAccessor(X0,0)), // ldr x1, [x0]
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -166,11 +164,12 @@ public void MovzThenLdr_UnreadableAddress_ShouldNotHaveValue()
.AddFalsePointer(MovzOffset)
.ToMockClrRuntime();
var accumulator = CreateValueAccumulator(clrRuntime);
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, MovzOffset), // movz x0, #0x1000
- Arm64TestInstructions.Ldr(X1, baseRegister: X0), // ldr x1, [x0]
+ MOVZ(X0, MovzOffset), // movz x0, #0x1000
+ LDR(X1, new Arm64ImmediateMemoryAccessor(X0,0)), // ldr x1, [x0]
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -182,4 +181,4 @@ public void MovzThenLdr_UnreadableAddress_ShouldNotHaveValue()
accumulator.Value.Should().Be(0UL);
}
}
-#endif
+
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenMovk.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenMovk.cs
index aeaf75f892..32def75206 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenMovk.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenMovk.cs
@@ -1,8 +1,6 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AwesomeAssertions;
-using Gee.External.Capstone.Arm64;
using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64InstructionFactory;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -14,12 +12,13 @@ public void MovzThenMovz_ShouldStartNewValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1111), // movz x0, #0x1111
- Arm64TestInstructions.Movz(X1, 0x2222), // movz x1, #0x2222
- Arm64TestInstructions.Movk(X1, 0x3333, 16), // movk x1, #0x3333, lsl #16
+ MOVZ(X0, 0x1111), // movz x0, #0x1111
+ MOVZ(X1, 0x2222), // movz x1, #0x2222
+ MOVK(X1, 0x3333, amount: 16), // movk x1, #0x3333, lsl #16
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -29,7 +28,7 @@ public void MovzThenMovz_ShouldStartNewValue()
// Assert
accumulator.HasValue.Should().BeTrue();
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X1);
+ accumulator.TrackedRegister.Should().Be(X1);
accumulator.Value.Should().Be(0x3333_2222); // X1 register value is used.
}
@@ -38,15 +37,21 @@ public void MovzThenMovk_ShouldHaveValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
+ var rawInstructions = new[]
+ {
+ MOVZ(X0, 0x1234), // movz x0, #0x1234
+ MOVK(X0, 0x5678, amount: 16) // movk x0, #0x5678, lsl #16
+ };
+ var instructions = rawInstructions.ToArm64Instructions();
// Act
- accumulator.Feed(Arm64TestInstructions.Movz(X0, 0x1234)); // movz x0, #0x1234
- accumulator.Feed(Arm64TestInstructions.Movk(X0, 0x5678, 16)); // movk x0, #0x5678, lsl #16
+ accumulator.Feed(instructions[0]);
+ accumulator.Feed(instructions[1]);
// Assert
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x5678_1234L);
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
}
[Fact]
@@ -54,11 +59,18 @@ public void MovzThenMovk_WithSameShiftValue_ShouldHasAccumulatedValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
+ var rawInstructions = new[]
+ {
+ MOVZ(X0, 0x1111), // movz x0, #0x1111
+ MOVK(X0, 0x2222, amount: 16), // movk x0, #0x2222, lsl #16
+ MOVK(X0, 0x0022, amount: 16) // movk x0, #0x0022, lsl #16
+ };
+ var instructions = rawInstructions.ToArm64Instructions();
// Act
- accumulator.Feed(Arm64TestInstructions.Movz(X0, 0x1111)); // movz x0, #0x1111
- accumulator.Feed(Arm64TestInstructions.Movk(X0, 0x2222, 16)); // movk x0, #0x2222, lsl #16
- accumulator.Feed(Arm64TestInstructions.Movk(X0, 0x0022, 16)); // movk x0, #0x0022, lsl #16
+ accumulator.Feed(instructions[0]);
+ accumulator.Feed(instructions[1]);
+ accumulator.Feed(instructions[2]);
// Assert
accumulator.HasValue.Should().BeTrue();
@@ -70,11 +82,12 @@ public void MovzThenMovk_WithSkippedShift_ShouldAccumulateValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1234), // movz x0, #0x1234
- Arm64TestInstructions.Movk(X0, 0x5678, 32) // movk x0, #0x5678, lsl #32
+ MOVZ(X0, 0x1234), // movz x0, #0x1234
+ MOVK(X0, 0x5678, amount: 32) // movk x0, #0x5678, lsl #32
};
+ var instructions = rawInstructions.ToArm64Instructions();
// Act
accumulator.Feed(instructions[0]);
@@ -83,7 +96,7 @@ public void MovzThenMovk_WithSkippedShift_ShouldAccumulateValue()
// Assert
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x5678_0000_1234UL);
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
}
[Fact]
@@ -91,15 +104,20 @@ public void MovzThenMovk_DifferentRegister_ShouldHaveValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
+ var rawInstructions = new[]
+ {
+ MOVZ(X0, 0x1234), // movz x0, #0x1234
+ MOVK(X1, 0x5678, amount: 16), // movk x1, #0x5678, lsl #16
+ };
+ var instructions = rawInstructions.ToArm64Instructions();
// Act
- accumulator.Feed(Arm64TestInstructions.Movz(X0, 0x1234)); // movz x0, #0x1234
- accumulator.Feed(Arm64TestInstructions.Movk(X1, 0x5678, 16)); // movk x1, #0x5678, lsl #16
+ accumulator.Feed(instructions[0]);
+ accumulator.Feed(instructions[1]);
// Assert
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x1234);
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
index 3da52bbb4e..ed573f0625 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Arm64RegisterValueAccumulatorTests.MovzThenUnConditionalBranch.cs
@@ -1,8 +1,7 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AwesomeAssertions;
-using Gee.External.Capstone.Arm64;
+using BenchmarkDotNet.Disassemblers;
using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64InstructionFactory;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -14,11 +13,12 @@ public void MovzThenUnconditionalB_ShouldResetValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.B(0x100), // b #0x100
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ B(0x100), // b #0x100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -27,9 +27,7 @@ public void MovzThenUnconditionalB_ShouldResetValue()
// Assert
accumulator.HasValue.Should().BeFalse();
-
- // TODO: Current accumulator don't reset registerId.
- // accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
+ accumulator.TrackedRegister.Should().Be(default);
}
@@ -38,11 +36,12 @@ public void MovzThenTbz_ShouldKeepValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.Tbz(X0, 63, 0x100) // tbz x0, #63, #0x100
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ TBZ(X0, 63, 0x100) // tbz x0, #63, #0x100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -52,11 +51,10 @@ public void MovzThenTbz_ShouldKeepValue()
// Assert
accumulator.HasValue.Should().BeTrue();
accumulator.Value.Should().Be(0x1000UL); // TBZ is a conditional branch that does not overwrite the tracked register,
- accumulator.RegisterId.Should().Be(Arm64RegisterId.ARM64_REG_X0);
+ accumulator.TrackedRegister.Should().Be(X0);
- var details = instructions[1].Details;
- details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_JUMP).Should().BeTrue();
- details.BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE).Should().BeTrue();
+ instructions[1].BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_JUMP).Should().BeTrue();
+ instructions[1].BelongsToGroup(Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE).Should().BeTrue();
}
[Fact]
@@ -64,11 +62,12 @@ public void MovzThenRet_ShouldResetValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.Ret(), // ret
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ RET(), // ret
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -85,11 +84,12 @@ public void MovzThenDrps_ShouldResetValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.Drps(), // drps
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ DRPS(), // drps
};
+ var instructions = rawInstructions.ToArm64Instructions();
// Act
accumulator.Feed(instructions[0]);
@@ -97,9 +97,7 @@ public void MovzThenDrps_ShouldResetValue()
// Assert
accumulator.HasValue.Should().BeFalse();
-
- // TODO: Current implementation don't reset state.
- // accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
+ accumulator.TrackedRegister.Should().Be(default);
}
[Fact]
@@ -107,11 +105,12 @@ public void MovzThenBl_ShouldResetValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.Bl(0x100), // bl 0x100
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ BL(0x100), // bl 0x100
};
+ var instructions = rawInstructions.ToArm64Instructions();
PrintInstructions(instructions);
// Act
@@ -120,9 +119,7 @@ public void MovzThenBl_ShouldResetValue()
// Assert
accumulator.HasValue.Should().BeFalse();
-
- // TODO: Current implementation don't reset state.
- // accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
+ accumulator.TrackedRegister.Should().Be(default);
}
[Fact]
@@ -130,11 +127,12 @@ public void MovzThenBlr_ShouldResetValue()
{
// Arrange
var accumulator = CreateValueAccumulator();
- var instructions = new[]
+ var rawInstructions = new[]
{
- Arm64TestInstructions.Movz(X0, 0x1000), // movz x0, #0x1000
- Arm64TestInstructions.Blr(X0), // bl x0
+ MOVZ(X0, 0x1000), // movz x0, #0x1000
+ BLR(X0), // bl x0
};
+ var instructions = rawInstructions.ToArm64Instructions();
// Act
accumulator.Feed(instructions[0]);
@@ -142,9 +140,6 @@ public void MovzThenBlr_ShouldResetValue()
// Assert
accumulator.HasValue.Should().BeFalse();
-
- // TODO: Current implementation don't reset state.
- // accumulator.RegisterId.Should().Be(Arm64RegisterId.Invalid);
+ accumulator.TrackedRegister.Should().Be(default);
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/BelongsToGroupTests.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/BelongsToGroupTests.cs
index 5a7e352145..ab9046875d 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/BelongsToGroupTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/BelongsToGroupTests.cs
@@ -1,10 +1,10 @@
-// TODO: Remove #if directive when migrated to xunit.v3 or migrated to AsmArm64 based implementation.
-#if NET
using AsmArm64;
using AwesomeAssertions;
-using Gee.External.Capstone.Arm64;
+using BenchmarkDotNet.Disassemblers;
using static AsmArm64.Arm64RegisterW;
using static AsmArm64.Arm64RegisterX;
+using static AsmArm64.Arm64InstructionFactory;
+using static AsmArm64.Arm64ConditionalKind;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -18,33 +18,30 @@ public BelongsToGroupTests(ITestOutputHelper output)
}
[Theory]
- [MemberData(nameof(TestData.Jump), MemberType = typeof(TestData))]
- public void BelongToGroup_Jump(InstructionTestData testData)
+ [MemberData(nameof(TestData.JumpInstructions), MemberType = typeof(TestData))]
+ public void BelongToGroup_Jump(uint rawInstruction)
{
// Arrange
var groupId = Arm64InstructionGroupId.ARM64_GRP_JUMP;
- var instruction = testData.GetCapstoneInstruction();
+ var instruction = Arm64Instruction.Decode(rawInstruction);
// Act
- var result = instruction.Details.BelongsToGroup(groupId);
+ var result = instruction.BelongsToGroup(groupId);
// Assert
result.Should().BeTrue();
}
- // It looks like underlying capstone version (v4.0.2) don't contains following PR changes.
- // So It returns always false.
- // https://github.com/capstone-engine/capstone/pull/1610
- [Theory(Skip = "Incomaptible with capstone v4.0.2")]
- [MemberData(nameof(TestData.Call), MemberType = typeof(TestData))]
- public void BelongToGroup_Call(InstructionTestData testData)
+ [Theory]
+ [MemberData(nameof(TestData.CallInstructions), MemberType = typeof(TestData))]
+ public void BelongToGroup_Call(uint rawInstruction)
{
// Arrange
var groupId = Arm64InstructionGroupId.ARM64_GRP_CALL;
- var instruction = testData.GetCapstoneInstruction();
+ var instruction = Arm64Instruction.Decode(rawInstruction);
// Act
- var result = instruction.Details.BelongsToGroup(groupId);
+ var result = instruction.BelongsToGroup(groupId);
// Assert
result.Should().BeTrue();
@@ -52,123 +49,83 @@ public void BelongToGroup_Call(InstructionTestData testData)
[Theory]
- [MemberData(nameof(TestData.BranchRelative), MemberType = typeof(TestData))]
- public void BelongToGroup_BranchRelative(InstructionTestData testData)
+ [MemberData(nameof(TestData.BranchRelativeInstructions), MemberType = typeof(TestData))]
+ public void BelongToGroup_BranchRelative(uint rawInstruction)
{
// Arrange
var groupId = Arm64InstructionGroupId.ARM64_GRP_BRANCH_RELATIVE;
- var instruction = testData.GetCapstoneInstruction();
+ var instruction = Arm64Instruction.Decode(rawInstruction);
// Act
- var result = instruction.Details.BelongsToGroup(groupId);
+ var result = instruction.BelongsToGroup(groupId);
// Assert
result.Should().BeTrue();
}
-
- public record struct InstructionTestData(
- Arm64Mnemonic Nemonic,
- AsmArm64.Arm64InstructionId InstructionId,
- string Text,
- uint RawInstruction
- )
- {
- public AsmArm64.Arm64Instruction GetInstruction() => AsmArm64.Arm64Instruction.Decode(RawInstruction);
-
- public Gee.External.Capstone.Arm64.Arm64Instruction GetCapstoneInstruction() => RawInstruction.ToCapstoneArm64Instruction();
- };
-
-
public static class TestData
{
- public static TheoryData Jump
- => new(JumpInstructions.Select(ToInstructionTestData));
-
- public static TheoryData Call
- => new(CallInstructions.Select(ToInstructionTestData));
-
- public static TheoryData BranchRelative
- => new(BranchRelativeInstructions.Select(ToInstructionTestData));
-
// Following instructions is based on
// Capstone's group mapping at https://github.com/capstone-engine/capstone/blob/6.0.0-Alpha10/arch/AArch64/AArch64GenCSMappingInsn.inc
//
// Gee.External.Capstone internally use v4.0.2 mappings.
// https://github.com/capstone-engine/capstone/blob/4.0.2/arch/AArch64/AArch64MappingInsn.inc
-
- // TODO: Some instructions are commented out. Because it's not supported by current Capstone version and failed to decode.
-
- private static readonly uint[] JumpInstructions =
+ public static readonly TheoryData JumpInstructions =
[
- Arm64InstructionFactory.B(0x100), // B_only_branch_imm:
- Arm64InstructionFactory.B(Arm64ConditionalKind.EQ, 0x100), // B_only_condbranch:
- // Arm64InstructionFactory.BC(Arm64ConditionalKind.EQ, 0x100), // BC_only_condbranch: // It's introduced at Armv9.2-A
- Arm64InstructionFactory.BR(X0), // BR_64_branch_reg:
- // Arm64InstructionFactory.BRAA(X0, X1), // BRAA_64p_branch_reg:
- // Arm64InstructionFactory.BRAAZ(X0), // BRAAZ_64_branch_reg:
- // Arm64InstructionFactory.BRAB(X0, X1), // BRAB_64p_branch_reg:
- // Arm64InstructionFactory.BRABZ(X0), // BRABZ_64_branch_reg:
- Arm64InstructionFactory.CBNZ(W0, 0x100), // CBNZ_32_compbranch:
- Arm64InstructionFactory.CBNZ(X0, 0x100), // CBNZ_64_compbranch:
- Arm64InstructionFactory.CBZ(W0, 0x100), // CBZ_32_compbranch:
- Arm64InstructionFactory.CBZ(X0, 0x100), // CBZ_64_compbranch:
- // Arm64InstructionFactory.DRPS(), // DRPS_64e_branch_reg:
- // Arm64InstructionFactory.ERET(), // ERET_64e_branch_reg:
- // Arm64InstructionFactory.ERETAA(), // ERETAA_64e_branch_reg:
- // Arm64InstructionFactory.ERETAB(), // ERETAB_64e_branch_reg:
- // Arm64InstructionFactory.RET(X0), // RET_64r_branch_reg:
- // Arm64InstructionFactory.RETAA(), // RETAA_64e_branch_reg:
- // Arm64InstructionFactory.RETAASPPCR(X0), // RETAASPPCR_64m_branch_reg:
- // Arm64InstructionFactory.RETAASPPC(-0x100), // RETAASPPC_only_miscbranch:
- // Arm64InstructionFactory.RETAB(), // RETAB_64e_branch_reg:
- // Arm64InstructionFactory.RETABSPPCR(X0), // RETABSPPCR_64m_branch_reg:
- // Arm64InstructionFactory.RETABSPPC(-0x100), // RETABSPPC_only_miscbranch:
- Arm64InstructionFactory.TBNZ(X0, imm: 0x10, 0x100), // TBNZ_only_testbranch:
- Arm64InstructionFactory.TBZ(X0, imm: 0x10, 0x100), // TBZ_only_testbranch:
+ B(0x100), // B_only_branch_imm:
+ B(EQ, 0x100), // B_only_condbranch:
+ BC(EQ, 0x100), // BC_only_condbranch: // It's introduced at Armv9.2-A
+ BR(X0), // BR_64_branch_reg:
+ BRAA(X0, X1), // BRAA_64p_branch_reg:
+ BRAAZ(X0), // BRAAZ_64_branch_reg:
+ BRAB(X0, X1), // BRAB_64p_branch_reg:
+ BRABZ(X0), // BRABZ_64_branch_reg:
+ CBNZ(W0, 0x100), // CBNZ_32_compbranch:
+ CBNZ(X0, 0x100), // CBNZ_64_compbranch:
+ CBZ(W0, 0x100), // CBZ_32_compbranch:
+ CBZ(X0, 0x100), // CBZ_64_compbranch:
+ DRPS(), // DRPS_64e_branch_reg:
+ ERET(), // ERET_64e_branch_reg:
+ ERETAA(), // ERETAA_64e_branch_reg:
+ ERETAB(), // ERETAB_64e_branch_reg:
+ RET(X0), // RET_64r_branch_reg:
+ RETAA(), // RETAA_64e_branch_reg:
+ RETAASPPCR(X0), // RETAASPPCR_64m_branch_reg:
+ RETAASPPC(-0x100), // RETAASPPC_only_miscbranch:
+ RETAB(), // RETAB_64e_branch_reg:
+ RETABSPPCR(X0), // RETABSPPCR_64m_branch_reg:
+ RETABSPPC(-0x100), // RETABSPPC_only_miscbranch:
+ TBNZ(X0, imm: 0x10, 0x100), // TBNZ_only_testbranch:
+ TBZ(X0, imm: 0x10, 0x100), // TBZ_only_testbranch:
];
- private static readonly uint[] CallInstructions =
+ public static readonly TheoryData CallInstructions =
[
- Arm64InstructionFactory.BL(0x100), // BL_only_branch_imm
- Arm64InstructionFactory.BLR(X0), // BLR_64_branch_reg
- // Arm64InstructionFactory.BLRAA(X0, X1), // BLRAA_64p_branch_reg
- // Arm64InstructionFactory.BLRAB(X0, X1), // BLRAB_64p_branch_reg
- // Arm64InstructionFactory.BLRAAZ(X0), // BLRAAZ_64_branch_reg
- // Arm64InstructionFactory.BLRABZ(X0), // BLRABZ_64_branch_reg
- Arm64InstructionFactory.HVC(0x100), // HVC_ex_exception
- Arm64InstructionFactory.SMC(0x100), // HVC_ex_exception
- Arm64InstructionFactory.SVC(0x100), // SVC_ex_exception
+ BL(0x100), // BL_only_branch_imm
+ BLR(X0), // BLR_64_branch_reg
+ BLRAA(X0, X1), // BLRAA_64p_branch_reg
+ BLRAB(X0, X1), // BLRAB_64p_branch_reg
+ BLRAAZ(X0), // BLRAAZ_64_branch_reg
+ BLRABZ(X0), // BLRABZ_64_branch_reg
+ HVC(0x100), // HVC_ex_exception
+ SMC(0x100), // HVC_ex_exception
+ SVC(0x100), // SVC_ex_exception
];
- private static readonly uint[] BranchRelativeInstructions =
+ public static readonly TheoryData BranchRelativeInstructions =
[
- Arm64InstructionFactory.B(0x100), // B_only_branch_imm
- Arm64InstructionFactory.B(Arm64ConditionalKind.EQ, 0x100), // B_only_condbranch
- // Arm64InstructionFactory.BC(Arm64ConditionalKind.NE, 0x100), // BC_only_condbranch
- Arm64InstructionFactory.BL(0x100), // BL_only_branch_imm
- Arm64InstructionFactory.CBNZ(W0,0x100), // CBNZ_32_compbranch
- Arm64InstructionFactory.CBNZ(X0,0x100), // CBNZ_64_compbranch
- Arm64InstructionFactory.CBZ(W0,0x100), // CBZ_32_compbranch
- Arm64InstructionFactory.CBZ(X0,0x100), // CBZ_64_compbranch
- // Arm64InstructionFactory.RETAASPPC(0x100), // RETAASPPC_only_miscbranch
- // Arm64InstructionFactory.RETABSPPC(0x100), // RETAASPPC_only_miscbranch
- Arm64InstructionFactory.TBNZ(X0,imm:0x10, label: 0x20), // TBNZ_only_testbranch
- Arm64InstructionFactory.TBZ(X0,imm:0x10, label: 0x20), // TBZ_only_testbranch
+ B(0x100), // B_only_branch_imm
+ B(EQ, 0x100), // B_only_condbranch
+ BC(NE, 0x100), // BC_only_condbranch
+ BL(0x100), // BL_only_branch_imm
+ CBNZ(W0,0x100), // CBNZ_32_compbranch
+ CBNZ(X0,0x100), // CBNZ_64_compbranch
+ CBZ(W0,0x100), // CBZ_32_compbranch
+ CBZ(X0,0x100), // CBZ_64_compbranch
+ RETAASPPC(-0x100), // RETAASPPC_only_miscbranch
+ RETABSPPC(-0x100), // RETABSPPC_only_miscbranch
+ TBNZ(X0,imm:0x10, label: 0x20), // TBNZ_only_testbranch
+ TBZ(X0,imm:0x10, label: 0x20), // TBZ_only_testbranch
];
-
- private static InstructionTestData ToInstructionTestData(uint rawInstruction)
- {
- AsmArm64.Arm64Instruction instruction = AsmArm64.Arm64Instruction.Decode(rawInstruction);
-
- return new InstructionTestData
- {
- InstructionId = instruction.Id,
- Nemonic = instruction.Mnemonic,
- Text = instruction.ToString(),
- RawInstruction = rawInstruction,
- };
- }
}
}
-#endif
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64DisassemblerHelper.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64DisassemblerHelper.cs
index 0cdb17bb1f..573d1467da 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64DisassemblerHelper.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64DisassemblerHelper.cs
@@ -3,12 +3,6 @@
using Microsoft.Diagnostics.Runtime.Interfaces;
using System.Diagnostics;
-#if NET8_0_OR_GREATER
-using Microsoft.Diagnostics.Runtime;
-using System.Runtime.CompilerServices;
-using Arm64Instruction = Gee.External.Capstone.Arm64.Arm64Instruction;
-#endif
-
using Arm64Disassembler = BenchmarkDotNet.Disassemblers.Arm64Disassembler;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -39,70 +33,4 @@ internal class Arm64DisassemblerHelper : Arm64Disassembler
{
return base.TryFollowJumpTrampoline(state, address, out target);
}
-
- // Following methods require UnsafeAccessor to invoke private methods.
-#if NET8_0_OR_GREATER
-
- [DebuggerHidden]
- public static bool TryResolvePrecode(IDataReader reader, ref ulong address, out bool isPrestubMD)
- {
- var disassembler = new Arm64Disassembler();
- return TryResolvePrecode(disassembler, reader, ref address, out isPrestubMD);
- }
-
- [DebuggerHidden]
- public static bool TryReadStubHead(IDataReader reader, ulong address, out ulong parseBase, out uint instr0, out uint instr1, out uint instr2)
- {
- var disassembler = new Arm64Disassembler();
- return TryReadStubHead(disassembler, reader, address, out parseBase, out instr0, out instr1, out instr2);
- }
-
- [DebuggerHidden]
- public static bool IsLdrLiteral64(uint instr, out int rt, out int offsetBytes)
- {
- var disassembler = new Arm64Disassembler();
- return IsLdrLiteral64(disassembler, instr, out rt, out offsetBytes);
- }
-
- [DebuggerHidden]
- public static bool TryGetReferencedAddress(Arm64Instruction instruction, Arm64RegisterValueAccumulator accumulator, uint pointerSize, out ulong referencedAddress, out bool isReferencedAddressIndirect)
- {
- var disassembler = new Arm64Disassembler();
- return TryGetReferencedAddress(disassembler, instruction, accumulator, pointerSize, out referencedAddress, out isReferencedAddressIndirect);
- }
-
- [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(TryResolvePrecode))]
- private static extern bool TryResolvePrecode(
- Arm64Disassembler disassembler,
- IDataReader reader,
- ref ulong address,
- out bool isPrestubMD);
-
- [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(TryReadStubHead))]
- private static extern bool TryReadStubHead(
- Arm64Disassembler disassembler,
- IDataReader reader,
- ulong address,
- out ulong parseBase,
- out uint instr0,
- out uint instr1,
- out uint instr2);
-
- [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(IsLdrLiteral64))]
- private static extern bool IsLdrLiteral64(
- Arm64Disassembler disassembler,
- uint instr,
- out int rt,
- out int offsetBytes);
-
- [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(TryGetReferencedAddress))]
- private static extern bool TryGetReferencedAddress(
- Arm64Disassembler
- disassembler,
- Arm64Instruction instruction,
- Arm64RegisterValueAccumulator accumulator,
- uint pointerSize,
- out ulong referencedAddress,
- out bool isReferencedAddressIndirect);
-#endif
}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64DisassemblerTestBase.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64DisassemblerTestBase.cs
index 45e05fafd6..746e6b735a 100644
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64DisassemblerTestBase.cs
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64DisassemblerTestBase.cs
@@ -1,6 +1,5 @@
-using Microsoft.Diagnostics.Runtime;
-using Microsoft.Diagnostics.Runtime.Interfaces;
-using System.Buffers.Binary;
+using AsmArm64;
+using BenchmarkDotNet.Disassemblers;
namespace BenchmarkDotNet.Tests.Disassemblers.Arm64;
@@ -21,22 +20,17 @@ public Arm64DisassemblerTestBase(ITestOutputHelper output)
Output = output;
}
- protected void PrintInstructions(Gee.External.Capstone.Arm64.Arm64Instruction[] instructions)
+ protected void PrintInstructions(IEnumerable instructions)
{
foreach (var instruction in instructions)
{
- Output.WriteLine(instruction.ToString());
+ var instructionText = Arm64InstructionFormatter.Format(instruction);
+ Output.WriteLine(instructionText);
}
}
protected void PrintInstructions(uint[] rawInstructions)
- {
- foreach (var rawInstruction in rawInstructions)
- {
- var instruction = rawInstruction.ToCapstoneArm64Instruction();
- Output.WriteLine(instruction.ToString());
- }
- }
+ => PrintInstructions(rawInstructions.Select(Arm64Instruction.Decode));
///
/// Create MockClrRuntime with MockDataReader that returns following data.
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64TestInstructions.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64TestInstructions.cs
deleted file mode 100644
index 1371670c22..0000000000
--- a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/Arm64TestInstructions.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-using AsmArm64;
-using Gee.External.Capstone;
-using Gee.External.Capstone.Arm64;
-using System.Buffers.Binary;
-using System.Runtime.InteropServices;
-using Arm64Instruction = Gee.External.Capstone.Arm64.Arm64Instruction;
-
-namespace BenchmarkDotNet.Tests.Disassemblers;
-
-internal static class Arm64TestInstructions
-{
- public static Arm64Instruction Movz(Arm64RegisterX register, ushort value, int shift = 0)
- => Arm64InstructionFactory.MOVZ(register, value, amount: shift).ToCapstoneArm64Instruction();
-
- public static Arm64Instruction Movk(Arm64RegisterX register, ushort value, int shift = 0)
- => Arm64InstructionFactory.MOVK(register, value, amount: shift).ToCapstoneArm64Instruction();
-
- public static Arm64Instruction Add(Arm64RegisterX destination, Arm64RegisterX source, ushort immediate)
- => Arm64InstructionFactory.ADD(destination, source, immediate).ToCapstoneArm64Instruction();
-
- public static Arm64Instruction Add(Arm64RegisterX destination, Arm64RegisterX source, ushort immediate, byte shiftAmount)
- => Arm64InstructionFactory.ADD(destination, source, immediate, amount: shiftAmount).ToCapstoneArm64Instruction();
-
- public static Arm64Instruction Adrp(Arm64RegisterX register, Arm64LabelOffset offset)
- {
- if (offset.Value % 4096 != 0)
- throw new ArgumentException("ADRP label offset must be a multiple of 4096", nameof(offset));
-
- return Arm64InstructionFactory.ADRP(register, offset).ToCapstoneArm64Instruction();
- }
-
- public static Arm64Instruction Ldr(Arm64RegisterX destination, Arm64RegisterX baseRegister, short immediate = 0)
- {
- var memoryAccessor = new Arm64ImmediateMemoryAccessor(baseRegister, immediate); // Use unsigned offset
- return Arm64InstructionFactory.LDR(destination, memoryAccessor).ToCapstoneArm64Instruction(); // ldr Xt, [Xn]
- }
-
- public static Arm64Instruction Ldr(Arm64RegisterX destination, Arm64RegisterX baseRegister, Arm64RegisterX indexRegister)
- {
- var dummyMemoryExtend = new Arm64MemoryExtend(); // Use dummy IArm64MemoryExtend. Because it's not used.
- var memoryAccessor = new Arm64RegisterXExtendMemoryAccessor(baseRegister, indexRegister, dummyMemoryExtend);
- return Arm64InstructionFactory.LDR(destination, memoryAccessor).ToCapstoneArm64Instruction();
- }
-
- // LDR with PC(Program Counter) relative offset.
- public static Arm64Instruction Ldr(Arm64RegisterX destination, Arm64LabelOffset label)
- {
- label.ValidateMultipleOf8();
- return Arm64InstructionFactory.LDR(destination, label).ToCapstoneArm64Instruction(); // ldr Xd, [Xn, #label]
- }
-
- public static Arm64Instruction Cbz(Arm64RegisterX register, Arm64LabelOffset label)
- {
- label.ValidateMultipleOf4();
- return Arm64InstructionFactory.CBZ(register, label).ToCapstoneArm64Instruction(); // cbz Xn, label
- }
-
- public static Arm64Instruction Cbnz(Arm64RegisterX register, Arm64LabelOffset label)
- {
- label.ValidateMultipleOf4();
- return Arm64InstructionFactory.CBNZ(register, label).ToCapstoneArm64Instruction(); // cbnz Xn, label
- }
-
- // Unconditional branch
- public static Arm64Instruction B(Arm64LabelOffset label)
- {
- label.ValidateMultipleOf4();
- return Arm64InstructionFactory.B(label).ToCapstoneArm64Instruction();
- }
-
- // Conditional branch
- // B.EQ: Equal (Z == 1).
- // B.NE: Not equal (Z == 0).
- // B.HS: Carry set/unsigned higher or same (C == 1).
- // B.LO: Carry clear/unsigned lower (C == 0).
- // B.MI: Minus/negative (N == 1).
- // B.PL: Plus/positive or zero (N == 0).
- // B.VS: Overflow (V == 1).
- // B.VC: No overflow (V == 0).
- // B.HI: Unsigned higher (C == 1 and Z == 0).
- // B.LS: Unsigned lower or same (C == 0 or Z == 1).
- // B.GE: Signed greater than or equal (N == V).
- // B.LT: Signed less than (N != V).
- // B.GT: Signed greater than (Z == 0 and N == V).
- // B.LE: Signed less than or equal (Z == 1 or N != V).
- // B.AL: Always (unconditional execution).
- // B.NV: (this condition is deprecated or not used).
- public static Arm64Instruction B(Arm64ConditionalKind conditionalKind, Arm64LabelOffset label)
- {
- label.ValidateMultipleOf4();
- return Arm64InstructionFactory.B(conditionalKind, label).ToCapstoneArm64Instruction(); // B.EQ label
- }
-
- // RET with X30(Link Register)
- public static Arm64Instruction Ret()
- => Arm64InstructionFactory.RET().ToCapstoneArm64Instruction();
-
- public static Arm64Instruction Ret(Arm64RegisterX register)
- => Arm64InstructionFactory.RET(register).ToCapstoneArm64Instruction();
-
- public static Arm64Instruction Nop()
- => Arm64InstructionFactory.NOP().ToCapstoneArm64Instruction();
-
- public static Arm64Instruction Tbz(Arm64RegisterX register, byte imm, Arm64LabelOffset label)
- {
- label.ValidateMultipleOf4();
- return Arm64InstructionFactory.TBZ(register, imm, label).ToCapstoneArm64Instruction();
- }
-
- public static Arm64Instruction Bl(Arm64LabelOffset label)
- {
- label.ValidateMultipleOf4();
- return Arm64InstructionFactory.BL(label).ToCapstoneArm64Instruction();
- }
-
- public static Arm64Instruction Blr(Arm64RegisterX register)
- {
- return Arm64InstructionFactory.BLR(register).ToCapstoneArm64Instruction();
- }
-
- public static Arm64Instruction Drps()
- => Arm64InstructionFactory.DRPS().ToCapstoneArm64Instruction();
-
- public static Arm64Instruction RetAA()
- => Arm64InstructionFactory.RETAA().ToCapstoneArm64Instruction();
-}
-
-internal static class ExtensionMethods
-{
- public static AsmArm64.Arm64Instruction ToAsmArm64Instruction(this uint rawInstruction)
- => AsmArm64.Arm64Instruction.Decode(rawInstruction);
-
- public static Arm64Instruction ToCapstoneArm64Instruction(this uint rawInstruction, ulong baseAddress, uint i)
- {
- ulong offset = (ulong) i * 4;
- return rawInstruction.ToCapstoneArm64Instruction(baseAddress + offset);
- }
-
- public static Arm64Instruction ToCapstoneArm64Instruction(this uint rawInstruction, ulong baseAddress = 0)
- {
- Span bytes = stackalloc byte[4];
- BinaryPrimitives.WriteUInt32LittleEndian(bytes, rawInstruction);
-
- using var disassembler = CapstoneDisassembler.CreateArm64Disassembler(Arm64DisassembleMode.Arm);
- disassembler.EnableInstructionDetails = true;
-
- var instructions = disassembler.Disassemble(bytes.ToArray(), (long) baseAddress);
- if (instructions.Length == 0)
- {
- var instruction = AsmArm64.Arm64Instruction.Decode(rawInstruction);
- throw new Exception($"CapstoneDisassembler failed to deserialize instruction: {instruction.ToString()} (rawInstruction: 0x{rawInstruction:X2})");
- }
-
- return instructions.First();
- }
-
- public static byte[] ToLittleEndianBytes(this uint[] rawInstructions)
- {
- if (BitConverter.IsLittleEndian)
- return MemoryMarshal.AsBytes(rawInstructions).ToArray();
-
- byte[] bytes = new byte[rawInstructions.Length * 4];
-
- for (int i = 0; i < rawInstructions.Length; i++)
- {
- BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(i * 4, 4), rawInstructions[i]);
- }
- return bytes;
- }
-
- public static int FillBuffer(this uint[] rawInstructions, Span buffer)
- {
- var bytes = rawInstructions.ToLittleEndianBytes();
- if (bytes.Length > buffer.Length)
- throw new ArgumentException($"Buffer length must be greater than {bytes.Length}.");
-
- bytes.CopyTo(buffer);
- return bytes.Length;
- }
-
- public static void ValidateMultipleOf4(this Arm64LabelOffset label)
- {
- if (label.Value % 4 != 0)
- throw new ArgumentException("Arm64LabelOffset value must be multiple of 4");
- }
-
- public static void ValidateMultipleOf8(this Arm64LabelOffset label)
- {
- if (label.Value % 8 != 0)
- throw new ArgumentException("Arm64LabelOffset value must be multiple of 8");
- }
-}
diff --git a/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/ExtensionMethods.cs b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/ExtensionMethods.cs
new file mode 100644
index 0000000000..f1d1bc2d48
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Disassemblers/Arm64/Helpers/ExtensionMethods.cs
@@ -0,0 +1,31 @@
+using AsmArm64;
+using System.Buffers.Binary;
+using System.Runtime.InteropServices;
+
+namespace BenchmarkDotNet.Tests.Disassemblers;
+
+internal static class ExtensionMethods
+{
+ public static Arm64Instruction ToArm64Instruction(this uint rawInstruction)
+ => Arm64Instruction.Decode(rawInstruction);
+
+ public static Arm64Instruction[] ToArm64Instructions(this uint[] rawInstructions)
+ => rawInstructions.Select(Arm64Instruction.Decode).ToArray();
+
+ public static byte[] ToLittleEndianBytes(this uint[] rawInstructions)
+ {
+ if (BitConverter.IsLittleEndian)
+ return MemoryMarshal.AsBytes(rawInstructions).ToArray();
+
+ byte[] bytes = new byte[rawInstructions.Length * 4];
+
+ for (int i = 0; i < rawInstructions.Length; i++)
+ {
+ BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(i * 4, 4), rawInstructions[i]);
+ }
+ return bytes;
+ }
+
+ public static byte[] ToLittleEndianBytes(this uint rawInstruction)
+ => ToLittleEndianBytes([rawInstruction]);
+}
diff --git a/tests/BenchmarkDotNet.Tests/Serialization/DisassemblerModelSerializationTests.cs b/tests/BenchmarkDotNet.Tests/Serialization/DisassemblerModelSerializationTests.cs
index 64f94e812c..eaa02594bd 100644
--- a/tests/BenchmarkDotNet.Tests/Serialization/DisassemblerModelSerializationTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Serialization/DisassemblerModelSerializationTests.cs
@@ -1,14 +1,25 @@
+using AsmArm64;
using AwesomeAssertions;
+using AwesomeAssertions.Equivalency;
using BenchmarkDotNet.Disassemblers;
using BenchmarkDotNet.Serialization;
-using Gee.External.Capstone;
-using Gee.External.Capstone.Arm64;
+using BenchmarkDotNet.Tests.Disassemblers;
+using BenchmarkDotNet.Tests.Disassemblers.Arm64;
using Iced.Intel;
+using static AsmArm64.Arm64RegisterSP;
+using static AsmArm64.Arm64RegisterW;
+using static AsmArm64.Arm64RegisterX;
namespace BenchmarkDotNet.Tests;
-public class DisassemblerModelSerializationTests
+public class DisassemblerModelSerializationTests(ITestOutputHelper output) : Arm64DisassemblerTestBase(output)
{
+ // Check Arm64Instruction equivalency by ToString output.
+ private static EquivalencyOptions ConfigureCustomEquivalency(EquivalencyOptions options)
+ => options
+ .Using(ctx => ctx.Subject.ToString().Should().Be(ctx.Expectation.ToString()))
+ .When(info => info.Path == "Instruction");
+
[Fact]
public void ClrMdArgsSerializationTest()
{
@@ -108,25 +119,24 @@ public void IntelAsmSerializationTest()
public void Arm64AsmSerializationTest()
{
// Arrange
- byte[] instructionBytes = [0xE1, 0x0B, 0x40, 0xB9]; // ldr w1, [sp, #8]
- var disassembleSyntax = DisassembleSyntax.Intel;
+ var rawInstruction = Arm64InstructionFactory.LDR(W1, new Arm64ImmediateMemoryAccessor(SP, 8)); // ldr w1, [sp, #8]
+ byte[] instructionBytes = rawInstruction.ToLittleEndianBytes();
+
+ using var clrRuntime = new MockMemory().ToMockClrRuntime();
+ var state = new State(clrRuntime, DummyTargetFrameworkVersion);
- // Create instruction instance by using disassembler.
- using var disassembler = CapstoneFactory.CreateArm64Disassembler(Arm64DisassembleMode.Arm);
- disassembler.EnableInstructionDetails = true;
- disassembler.DisassembleSyntax = disassembleSyntax;
+ var helper = new Arm64DisassemblerHelper();
// Act
- var instructions = disassembler.Disassemble(instructionBytes);
+ var instructions = helper.Decode(instructionBytes, 0, state, 0, DummyCurrentMethod, Diagnosers.DisassemblySyntax.Att);
var instruction = instructions.Single();
var model = new Arm64Asm
{
- DisassembleSyntax = disassembleSyntax,
- Instruction = instruction,
- InstructionLength = instruction.Bytes.Length,
- InstructionPointer = (ulong) instruction.Address,
- ReferencedAddress = (instruction.Address > ushort.MaxValue) ? (ulong) instruction.Address : null,
+ InstructionPointer = instruction.InstructionPointer,
+ RawInstruction = instruction.RawInstruction,
+ DisassembleSyntax = instruction.DisassembleSyntax,
+ ReferencedAddress = (instruction.ReferencedAddress > ushort.MaxValue) ? instruction.ReferencedAddress : null,
IsReferencedAddressIndirect = true, // Test with dummy value
};
@@ -137,13 +147,12 @@ public void Arm64AsmSerializationTest()
// Assert
json.Should().NotBe("{}");
- // Compare properties (Except for`Instruction.Details.Operands` property that )
+ // Compare properties
result.Should().NotBeNull();
result.Instruction.Should().NotBeNull();
result.Instruction.ToString().Should().Be("ldr w1, [sp, #8]");
- result.Should()
- .BeEquivalentTo(model, options => options.Excluding(x => x.Instruction!.Details.Operands));
+ result.Should().BeEquivalentTo(model, ConfigureCustomEquivalency);
}
[Fact]