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