From d29f8a74d2a387a9a667c7082d771e3b5f018288 Mon Sep 17 00:00:00 2001 From: OBarronCS <55004530+OBarronCS@users.noreply.github.com> Date: Wed, 16 Apr 2025 05:02:25 -0700 Subject: [PATCH] Upgrade to Capstone V6 (#2766) * Upgrade to Capstone V6 * Fix final bugs related to changes in metadata/shift encoding in Capstone v6. All tests pass * rebase prep * Update to Capstone v6 alpha 4 * lint * Apply patch to fix nixos packaging --- nix/pyenv.nix | 29 +- pwndbg/aglib/disasm/__init__.py | 2 +- pwndbg/aglib/disasm/aarch64.py | 366 ++++++++++++-------- pwndbg/aglib/disasm/arch.py | 12 +- pwndbg/aglib/disasm/arm.py | 105 +++--- pwndbg/aglib/disasm/instruction.py | 45 ++- pwndbg/aglib/disasm/mips.py | 66 +++- pwndbg/aglib/disasm/riscv.py | 65 +--- pwndbg/emu/emulator.py | 13 +- pwndbg/lib/disasm/helpers.py | 1 + pwndbg/lib/regs.py | 16 +- pyproject.toml | 2 +- tests/qemu-tests/tests/user/test_aarch64.py | 304 ++++++++++++++-- tests/qemu-tests/tests/user/test_arm.py | 155 +++++++-- tests/qemu-tests/tests/user/test_mips.py | 42 +-- tests/qemu-tests/tests/user/test_riscv64.py | 8 +- tests/unit-tests/test_bit_math.py | 3 + uv.lock | 56 ++- 18 files changed, 881 insertions(+), 409 deletions(-) diff --git a/nix/pyenv.nix b/nix/pyenv.nix index 577e34851..4fd08ea18 100644 --- a/nix/pyenv.nix +++ b/nix/pyenv.nix @@ -162,37 +162,10 @@ let }: prev.capstone.overrideAttrs ( old: - lib.optionalAttrs ((isBuildSource old) && stdenv.hostPlatform.isDarwin) { + lib.optionalAttrs (isBuildSource old) { nativeBuildInputs = old.nativeBuildInputs ++ [ cmake - fixDarwinDylibNames ]; - - preBuild = '' - sed -i 's/^IS_APPLE := .*$/IS_APPLE := 1/' ./src/Makefile - - substituteInPlace ./setup.py \ - --replace-fail "import sys" "import sys; sys.argv.extend(('--plat-name', 'any'))" || true - ''; - - # See: https://github.com/capstone-engine/capstone/issues/2621 - postPatch = ( - let - gitSrc = fetchFromGitHub { - owner = "capstone-engine"; - repo = "capstone"; - rev = old.version; - hash = "sha256-VGqqrixg7LaqRWTAEBzpC+gUTchncz3Oa2pSq8GLskI="; - }; - in - '' - cp ${gitSrc}/capstone.pc.in src/ - cp ${gitSrc}/capstone-config.cmake.in src/ - cp ${gitSrc}/cmake_uninstall.cmake.in src/ - cp ${gitSrc}/CPackConfig.txt src/ - cp ${gitSrc}/CPackConfig.cmake src/ - '' - ); } ) ) { }; diff --git a/pwndbg/aglib/disasm/__init__.py b/pwndbg/aglib/disasm/__init__.py index 631757a21..0e30d1f83 100644 --- a/pwndbg/aglib/disasm/__init__.py +++ b/pwndbg/aglib/disasm/__init__.py @@ -37,7 +37,7 @@ if pwndbg.dbg.is_gdblib_available(): CapstoneArch = { "arm": CS_ARCH_ARM, "armcm": CS_ARCH_ARM, - "aarch64": CS_ARCH_ARM64, + "aarch64": CS_ARCH_AARCH64, "i386": CS_ARCH_X86, "i8086": CS_ARCH_X86, "x86-64": CS_ARCH_X86, diff --git a/pwndbg/aglib/disasm/aarch64.py b/pwndbg/aglib/disasm/aarch64.py index 04d373c3b..c86878b25 100644 --- a/pwndbg/aglib/disasm/aarch64.py +++ b/pwndbg/aglib/disasm/aarch64.py @@ -5,7 +5,7 @@ from typing import Callable from typing import Dict from capstone import * # noqa: F403 -from capstone.arm64 import * # noqa: F403 +from capstone.aarch64 import * # noqa: F403 from typing_extensions import override import pwndbg.aglib.arch @@ -28,105 +28,126 @@ if TYPE_CHECKING: # Negative size indicates signed read # None indicates the read size depends on the target register AARCH64_SINGLE_LOAD_INSTRUCTIONS: Dict[int, int | None] = { - ARM64_INS_LDRB: 1, - ARM64_INS_LDURB: 1, - ARM64_INS_LDRSB: -1, - ARM64_INS_LDURSB: -1, - ARM64_INS_LDRH: 2, - ARM64_INS_LDURH: 2, - ARM64_INS_LDRSH: -2, - ARM64_INS_LDURSH: -2, - ARM64_INS_LDURSW: -4, - ARM64_INS_LDRSW: -4, - ARM64_INS_LDUR: None, - ARM64_INS_LDR: None, - ARM64_INS_LDTRB: 1, - ARM64_INS_LDTRSB: -1, - ARM64_INS_LDTRH: 2, - ARM64_INS_LDTRSH: -2, - ARM64_INS_LDTRSW: -4, - ARM64_INS_LDTR: None, - ARM64_INS_LDXRB: 1, - ARM64_INS_LDXRH: 2, - ARM64_INS_LDXR: None, - ARM64_INS_LDARB: 1, - ARM64_INS_LDARH: 2, - ARM64_INS_LDAR: None, + AARCH64_INS_LDRB: 1, + AARCH64_INS_ALIAS_LDRB: 1, + AARCH64_INS_LDURB: 1, + AARCH64_INS_ALIAS_LDURB: 1, + AARCH64_INS_LDRSB: -1, + AARCH64_INS_ALIAS_LDRSB: -1, + AARCH64_INS_LDURSB: -1, + AARCH64_INS_ALIAS_LDURSB: -1, + AARCH64_INS_LDRH: 2, + AARCH64_INS_ALIAS_LDRH: 2, + AARCH64_INS_LDURH: 2, + AARCH64_INS_ALIAS_LDURH: 2, + AARCH64_INS_LDRSH: -2, + AARCH64_INS_ALIAS_LDRSH: -2, + AARCH64_INS_LDURSH: -2, + AARCH64_INS_ALIAS_LDURSH: -2, + AARCH64_INS_LDURSW: -4, + AARCH64_INS_ALIAS_LDURSW: -4, + AARCH64_INS_LDRSW: -4, + AARCH64_INS_ALIAS_LDRSW: -4, + AARCH64_INS_LDUR: None, + AARCH64_INS_ALIAS_LDUR: None, + AARCH64_INS_LDR: None, + AARCH64_INS_ALIAS_LDR: None, + AARCH64_INS_LDTRB: 1, + AARCH64_INS_LDTRSB: -1, + AARCH64_INS_LDTRH: 2, + AARCH64_INS_LDTRSH: -2, + AARCH64_INS_LDTRSW: -4, + AARCH64_INS_LDTR: None, + AARCH64_INS_ALIAS_LDTR: None, + AARCH64_INS_LDXRB: 1, + AARCH64_INS_LDXRH: 2, + AARCH64_INS_LDXR: None, + AARCH64_INS_LDARB: 1, + AARCH64_INS_LDARH: 2, + AARCH64_INS_LDAR: None, } # None indicates that the write size depends on the source register AARCH64_SINGLE_STORE_INSTRUCTIONS: Dict[int, int | None] = { - ARM64_INS_STRB: 1, - ARM64_INS_STURB: 1, - ARM64_INS_STRH: 2, - ARM64_INS_STURH: 2, - ARM64_INS_STUR: None, - ARM64_INS_STR: None, + AARCH64_INS_STRB: 1, + AARCH64_INS_ALIAS_STRB: 1, + AARCH64_INS_STURB: 1, + AARCH64_INS_ALIAS_STURB: 1, + AARCH64_INS_STRH: 2, + AARCH64_INS_ALIAS_STRH: 2, + AARCH64_INS_STURH: 2, + AARCH64_INS_ALIAS_STURH: 2, + AARCH64_INS_STUR: None, + AARCH64_INS_ALIAS_STUR: None, + AARCH64_INS_STR: None, + AARCH64_INS_ALIAS_STR: None, # Store Register (unprivileged) - ARM64_INS_STTRB: 1, - ARM64_INS_STTRH: 2, - ARM64_INS_STTR: None, + AARCH64_INS_STTRB: 1, + AARCH64_INS_STTRH: 2, + AARCH64_INS_STTR: None, # Store-Release - ARM64_INS_STLRB: 1, - ARM64_INS_STLRH: 2, - ARM64_INS_STLR: None, + AARCH64_INS_STLRB: 1, + AARCH64_INS_STLRH: 2, + AARCH64_INS_STLR: None, } # The first operand of these instructions gets the status result of the operation AARCH64_EXCLUSIVE_STORE_INSTRUCTIONS = { # Store Exclusive - ARM64_INS_STXRB: 1, - ARM64_INS_STXRH: 2, - ARM64_INS_STXR: None, + AARCH64_INS_STXRB: 1, + AARCH64_INS_STXRH: 2, + AARCH64_INS_STXR: None, # Store-Release Exclusive - ARM64_INS_STLXRB: 1, - ARM64_INS_STLXRH: 2, - ARM64_INS_STLXR: None, + AARCH64_INS_STLXRB: 1, + AARCH64_INS_STLXRH: 2, + AARCH64_INS_STLXR: None, } CONDITIONAL_SELECT_INSTRUCTIONS = { - ARM64_INS_CSEL, - ARM64_INS_CSINC, - ARM64_INS_CSINV, - ARM64_INS_CSNEG, - ARM64_INS_CSET, - ARM64_INS_CSETM, - ARM64_INS_CINC, - ARM64_INS_CINV, - ARM64_INS_CNEG, + AARCH64_INS_CSEL, + AARCH64_INS_CSINC, + AARCH64_INS_CSINV, + AARCH64_INS_CSNEG, + AARCH64_INS_ALIAS_CSET, + AARCH64_INS_ALIAS_CSETM, + AARCH64_INS_ALIAS_CINC, + AARCH64_INS_ALIAS_CINV, + AARCH64_INS_ALIAS_CNEG, } AARCH64_EMULATED_ANNOTATIONS = CONDITIONAL_SELECT_INSTRUCTIONS | { - ARM64_INS_SXTB, - ARM64_INS_SXTH, - ARM64_INS_SXTW, - ARM64_INS_UXTB, - ARM64_INS_UXTH, - ARM64_INS_UXTW, - ARM64_INS_RBIT, - ARM64_INS_CLS, - ARM64_INS_CLZ, - ARM64_INS_BFXIL, - ARM64_INS_UBFIZ, - ARM64_INS_UBFM, - ARM64_INS_UBFX, - ARM64_INS_SBFIZ, - ARM64_INS_SBFM, - ARM64_INS_SBFX, - ARM64_INS_BFI, - ARM64_INS_NEG, - ARM64_INS_NEGS, - ARM64_INS_REV, - ARM64_INS_BIC, - ARM64_INS_BICS, + AARCH64_INS_SXTB, + AARCH64_INS_SXTH, + AARCH64_INS_SXTW, + AARCH64_INS_UXTB, + AARCH64_INS_UXTH, + AARCH64_INS_UXTW, + AARCH64_INS_RBIT, + AARCH64_INS_CLS, + AARCH64_INS_CLZ, + AARCH64_INS_ALIAS_BFXIL, + AARCH64_INS_ALIAS_UBFIZ, + AARCH64_INS_UBFM, + AARCH64_INS_ALIAS_UBFX, + AARCH64_INS_ALIAS_SBFIZ, + AARCH64_INS_SBFM, + AARCH64_INS_ALIAS_SBFX, + AARCH64_INS_ALIAS_BFI, + AARCH64_INS_NEG, + AARCH64_INS_ALIAS_NEGS, + AARCH64_INS_REV, + AARCH64_INS_BIC, + AARCH64_INS_BICS, } +AARCH64_CONSTANT_SHIFTS = {AARCH64_SFT_LSL, AARCH64_SFT_LSR, AARCH64_SFT_ASR, AARCH64_SFT_ROR} + # Parameters to each function: (value, shift_amt, bit_width) AARCH64_BIT_SHIFT_MAP: Dict[int, Callable[[int, int, int], int]] = { - ARM64_SFT_LSL: bit_math.logical_shift_left, - ARM64_SFT_LSR: bit_math.logical_shift_right, - ARM64_SFT_ASR: bit_math.arithmetic_shift_right, - ARM64_SFT_ROR: bit_math.rotate_right, + AARCH64_SFT_LSL: bit_math.logical_shift_left, + AARCH64_SFT_LSR: bit_math.logical_shift_right, + AARCH64_SFT_ASR: bit_math.arithmetic_shift_right, + AARCH64_SFT_ROR: bit_math.rotate_right, } @@ -134,38 +155,54 @@ AARCH64_BIT_SHIFT_MAP: Dict[int, Callable[[int, int, int], int]] = { # They take in a number, extract a byte, halfword, or word, # and perform a zero- or sign-extend operation. AARCH64_EXTEND_MAP: Dict[int, Callable[[int], int]] = { - ARM64_EXT_UXTB: lambda x: x & ((1 << 8) - 1), - ARM64_EXT_UXTH: lambda x: x & ((1 << 16) - 1), - ARM64_EXT_UXTW: lambda x: x & ((1 << 32) - 1), - ARM64_EXT_UXTX: lambda x: x, # UXTX has no effect. It extracts 64-bits from a 64-bit register. - ARM64_EXT_SXTB: lambda x: bit_math.to_signed(x, 8), - ARM64_EXT_SXTH: lambda x: bit_math.to_signed(x, 16), - ARM64_EXT_SXTW: lambda x: bit_math.to_signed(x, 32), - ARM64_EXT_SXTX: lambda x: bit_math.to_signed(x, 64), + AARCH64_EXT_UXTB: lambda x: x & ((1 << 8) - 1), + AARCH64_EXT_UXTH: lambda x: x & ((1 << 16) - 1), + AARCH64_EXT_UXTW: lambda x: x & ((1 << 32) - 1), + AARCH64_EXT_UXTX: lambda x: x, # UXTX has no effect. It extracts 64-bits from a 64-bit register. + AARCH64_EXT_SXTB: lambda x: bit_math.to_signed(x, 8), + AARCH64_EXT_SXTH: lambda x: bit_math.to_signed(x, 16), + AARCH64_EXT_SXTW: lambda x: bit_math.to_signed(x, 32), + AARCH64_EXT_SXTX: lambda x: bit_math.to_signed(x, 64), } AARCH64_MATH_INSTRUCTIONS = { - ARM64_INS_ADD: "+", - ARM64_INS_ADDS: "+", - ARM64_INS_SUB: "-", - ARM64_INS_SUBS: "-", - ARM64_INS_AND: "&", - ARM64_INS_ANDS: "&", - ARM64_INS_ORR: "&", - ARM64_INS_ASR: ">>s", - ARM64_INS_ASRV: ">>s", - ARM64_INS_EOR: "^", - ARM64_INS_LSL: "<<", - ARM64_INS_LSLV: "<<", - ARM64_INS_LSR: ">>", - ARM64_INS_LSRV: ">>", - ARM64_INS_UDIV: "/", - ARM64_INS_SDIV: "/", - ARM64_INS_SMULH: "*", - ARM64_INS_SMULL: "*", - ARM64_INS_UMULH: "*", - ARM64_INS_UMULL: "*", - ARM64_INS_MUL: "*", + AARCH64_INS_ADD: "+", + AARCH64_INS_ALIAS_ADD: "+", + AARCH64_INS_ADDS: "+", + AARCH64_INS_ALIAS_ADDS: "+", + AARCH64_INS_SUB: "-", + AARCH64_INS_ALIAS_SUB: "-", + AARCH64_INS_SUBS: "-", + AARCH64_INS_ALIAS_SUBS: "-", + AARCH64_INS_AND: "&", + AARCH64_INS_ALIAS_AND: "&", + AARCH64_INS_ANDS: "&", + AARCH64_INS_ALIAS_ANDS: "&", + AARCH64_INS_ORR: "|", + AARCH64_INS_ALIAS_ORR: "|", + AARCH64_INS_EOR: "^", + AARCH64_INS_ALIAS_EOR: "^", + AARCH64_INS_UDIV: "/", + AARCH64_INS_SDIV: "/", + AARCH64_INS_SMULH: "*", + AARCH64_INS_SMULL: "*", + AARCH64_INS_ALIAS_SMULL: "*", + AARCH64_INS_UMULH: "*", + AARCH64_INS_UMULL: "*", + AARCH64_INS_ALIAS_UMULL: "*", + AARCH64_INS_MUL: "*", + AARCH64_INS_ALIAS_MUL: "*", +} + +AARCH64_SHIFT_INSTRUCTIONS = { + AARCH64_INS_LSL: "<<", + AARCH64_INS_ALIAS_LSL: "<<", + AARCH64_INS_LSR: ">>", + AARCH64_INS_ALIAS_LSR: ">>", + AARCH64_INS_ASR: ">>s", + AARCH64_INS_ALIAS_ASR: ">>s", + AARCH64_INS_ROR: ">>r", + AARCH64_INS_ALIAS_ROR: ">>r", } @@ -182,23 +219,23 @@ def resolve_condition(condition: int, cpsr: int) -> InstructionCondition: v = (cpsr >> 28) & 1 condition = { - ARM64_CC_INVALID: True, # Capstone uses this code for the 'B' instruction, the unconditional branch - ARM64_CC_EQ: z == 1, - ARM64_CC_NE: z == 0, - ARM64_CC_HS: c == 1, - ARM64_CC_LO: c == 0, - ARM64_CC_MI: n == 1, - ARM64_CC_PL: n == 0, - ARM64_CC_VS: v == 1, - ARM64_CC_VC: v == 0, - ARM64_CC_HI: c == 1 and z == 0, - ARM64_CC_LS: not (c == 1 and z == 0), - ARM64_CC_GE: n == v, - ARM64_CC_LT: n != v, - ARM64_CC_GT: z == 0 and n == v, - ARM64_CC_LE: not (z == 0 and n == v), - ARM64_CC_AL: True, - ARM64_CC_NV: True, + AArch64CC_EQ: z == 1, + AArch64CC_NE: z == 0, + AArch64CC_HS: c == 1, + AArch64CC_LO: c == 0, + AArch64CC_MI: n == 1, + AArch64CC_PL: n == 0, + AArch64CC_VS: v == 1, + AArch64CC_VC: v == 0, + AArch64CC_HI: c == 1 and z == 0, + AArch64CC_LS: not (c == 1 and z == 0), + AArch64CC_GE: n == v, + AArch64CC_LT: n != v, + AArch64CC_GT: z == 0 and n == v, + AArch64CC_LE: not (z == 0 and n == v), + AArch64CC_AL: True, + AArch64CC_NV: True, + AArch64CC_Invalid: True, }.get(condition, False) return InstructionCondition.TRUE if condition else InstructionCondition.FALSE @@ -210,23 +247,26 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): self.annotation_handlers: Dict[int, Callable[[PwndbgInstruction, Emulator], None]] = { # MOV - ARM64_INS_MOV: self._common_move_annotator, + AARCH64_INS_MOV: self._common_move_annotator, + AARCH64_INS_ALIAS_MOV: self._common_move_annotator, + # MOVZ + AARCH64_INS_MOVZ: self._common_move_annotator, # MOV WITH KEEP - ARM64_INS_MOVK: self._common_generic_register_destination, + AARCH64_INS_MOVK: self._common_generic_register_destination, # ADR - ARM64_INS_ADR: self._common_generic_register_destination, + AARCH64_INS_ADR: self._common_generic_register_destination, # ADRP - ARM64_INS_ADRP: self._handle_adrp, + AARCH64_INS_ADRP: self._handle_adrp, # CMP - ARM64_INS_CMP: self._common_cmp_annotator_builder("cpsr", "-"), + AARCH64_INS_ALIAS_CMP: self._common_cmp_annotator_builder("cpsr", "-"), # CMN - ARM64_INS_CMN: self._common_cmp_annotator_builder("cpsr", "+"), + AARCH64_INS_ALIAS_CMN: self._common_cmp_annotator_builder("cpsr", "+"), # TST (bitwise "and") - ARM64_INS_TST: self._common_cmp_annotator_builder("cpsr", "&"), + AARCH64_INS_ALIAS_TST: self._common_cmp_annotator_builder("cpsr", "&"), # CCMP (conditional compare) - ARM64_INS_CCMP: self._common_cmp_annotator_builder("cpsr", ""), + AARCH64_INS_CCMP: self._common_cmp_annotator_builder("cpsr", ""), # CCMN - ARM64_INS_CCMN: self._common_cmp_annotator_builder("cpsr", ""), + AARCH64_INS_CCMN: self._common_cmp_annotator_builder("cpsr", ""), } @override @@ -273,6 +313,20 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): instruction.operands[-1].before_value, AARCH64_MATH_INSTRUCTIONS[instruction.id], ) + elif instruction.id in AARCH64_SHIFT_INSTRUCTIONS: + # AArch64 encoding of shifts forces special attention: https://github.com/capstone-engine/capstone/issues/2631 + if len(instruction.operands) == 2: + if instruction.operands[1].cs_op.shift.type in AARCH64_CONSTANT_SHIFTS: + self._common_binary_op_annotator( + instruction, + emu, + instruction.operands[0], + instruction.operands[1].before_value_no_modifiers, + instruction.operands[1].cs_op.shift.value, + AARCH64_SHIFT_INSTRUCTIONS[instruction.id], + ) + else: + self._common_generic_register_destination(instruction, emu) elif instruction.id in AARCH64_EMULATED_ANNOTATIONS: self._common_generic_register_destination(instruction, emu) else: @@ -291,30 +345,38 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): instruction.annotation = register_assign(result_operand.str, telescope) + @override + def _prepare( + self, instruction: PwndbgInstruction, emu: pwndbg.aglib.disasm.arch.Emulator + ) -> None: + if CS_GRP_INT in instruction.groups: + # https://github.com/capstone-engine/capstone/issues/2630 + instruction.groups.remove(CS_GRP_CALL) + @override def _condition( self, instruction: PwndbgInstruction, emu: Emulator ) -> pwndbg.aglib.disasm.arch.InstructionCondition: # In ARM64, only branches have the conditional code in the instruction, # as opposed to ARM32 which allows most instructions to be conditional - if instruction.id == ARM64_INS_B: + if instruction.id == AARCH64_INS_B: # The B instruction can be made conditional by the condition codes - if instruction.cs_insn.cc in (ARM64_CC_INVALID, ARM64_CC_AL): + if instruction.cs_insn.cc in (AArch64CC_Invalid, AArch64CC_AL): instruction.declare_conditional = False else: flags = super()._read_register_name(instruction, "cpsr", emu) if flags is not None: return resolve_condition(instruction.cs_insn.cc, flags) - elif instruction.id == ARM64_INS_CBNZ: + elif instruction.id == AARCH64_INS_CBNZ: op_val = instruction.operands[0].before_value return boolean_to_instruction_condition(op_val is not None and op_val != 0) - elif instruction.id == ARM64_INS_CBZ: + elif instruction.id == AARCH64_INS_CBZ: op_val = instruction.operands[0].before_value return boolean_to_instruction_condition(op_val is not None and op_val == 0) - elif instruction.id == ARM64_INS_TBNZ: + elif instruction.id == AARCH64_INS_TBNZ: op_val, bit = ( instruction.operands[0].before_value, instruction.operands[1].before_value, @@ -323,7 +385,7 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): if op_val is not None and bit is not None: return boolean_to_instruction_condition(bool((op_val >> bit) & 1)) - elif instruction.id == ARM64_INS_TBZ: + elif instruction.id == AARCH64_INS_TBZ: op_val, bit = ( instruction.operands[0].before_value, instruction.operands[1].before_value, @@ -351,7 +413,7 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): if (val := instruction.operands[-1].before_value) is not None: return val & pwndbg.aglib.arch.ptrmask return None - elif instruction.id == ARM64_INS_RET: + elif instruction.id in (AARCH64_INS_RET, AARCH64_INS_ALIAS_RET): # If this is a ret WITHOUT an operand, it means we should read from the LR/x30 register return super()._read_register_name(instruction, "lr", emu) @@ -383,11 +445,20 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): target = 0 - # All memory operands have `base` defined + # Handle case of LDR instructions memory operands that uses immediate as memory location + # Example: ldr x1, 0x30 + if op.mem.base == 0: + return op.mem.disp + base = self._read_register(instruction, op.mem.base, emu) if base is None: return None - target = base + op.mem.disp + target = base + + if not instruction.cs_insn.post_index: + # Unlike in arm32, the sign of `disp` is encoded in the number already. + # There is no separate ".subtracted" value in Capstone + target += op.mem.disp # If there is an index register if op.mem.index != 0: @@ -448,6 +519,9 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): if target is None: return None + # We need this to retain the value of the un-shifted register in some annotations, such as shifts + op.before_value_no_modifiers = target + # The shift and sign-extend operations depend on the target bit width. # This is sometimes implicit in the target register size, which is always # the first operand. @@ -461,10 +535,12 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): target = AARCH64_EXTEND_MAP[op.cs_op.ext](target) & ((1 << target_bit_width) - 1) if op.cs_op.shift.type != 0: - print(target, op.cs_op.shift.type, op.cs_op.shift.value) - target = AARCH64_BIT_SHIFT_MAP[op.cs_op.shift.type]( + target = AARCH64_BIT_SHIFT_MAP.get(op.cs_op.shift.type, lambda *a: None)( target, op.cs_op.shift.value, target_bit_width - ) & ((1 << target_bit_width) - 1) + ) + + if target is not None: + target = target & ((1 << target_bit_width) - 1) return target diff --git a/pwndbg/aglib/disasm/arch.py b/pwndbg/aglib/disasm/arch.py index b38d4057a..d18c18fce 100644 --- a/pwndbg/aglib/disasm/arch.py +++ b/pwndbg/aglib/disasm/arch.py @@ -214,6 +214,8 @@ class DisassemblyAssistant: pwndbg.aglib.arch.name, generic_assistant ) + enhancer._prepare(instruction, emu) + # Don't disable emulation yet, as we can use it to read the syscall register enhancer._enhance_syscall(instruction, emu) @@ -259,6 +261,10 @@ class DisassemblyAssistant: print(enhancer.dump(instruction)) print("Done enhancing") + # This is run before enhancement - often used to handle edge case behavior + def _prepare(self, instruction: PwndbgInstruction, emu: Emulator) -> None: + return None + # Subclasses for specific architecture should override this def _set_annotation_string(self, instruction: PwndbgInstruction, emu: Emulator) -> None: """ @@ -317,7 +323,11 @@ class DisassemblyAssistant: op.before_value, instruction, op, emu ) - if op.symbol and op.type == CS_OP_IMM and pwndbg.config.disasm_inline_symbols: + if ( + op.symbol + and op.type in (CS_OP_IMM, CS_OP_MEM) + and pwndbg.config.disasm_inline_symbols + ): # Make an inline replacement, so `jmp 0x400122` becomes `jmp function_name` instruction.asm_string = instruction.asm_string.replace( hex(op.before_value), op.symbol diff --git a/pwndbg/aglib/disasm/arm.py b/pwndbg/aglib/disasm/arm.py index b8a4da555..1ce8ce5f5 100644 --- a/pwndbg/aglib/disasm/arm.py +++ b/pwndbg/aglib/disasm/arm.py @@ -78,31 +78,48 @@ ARM_MATH_INSTRUCTIONS = { ARM_SHIFT_INSTRUCTIONS = { ARM_INS_ASR: ">>s", + ARM_INS_ALIAS_ASR: ">>s", ARM_INS_LSR: ">>", + ARM_INS_ALIAS_LSR: ">>", ARM_INS_LSL: "<<", + ARM_INS_ALIAS_LSL: "<<", + ARM_INS_ROR: ">>r", + ARM_INS_ALIAS_ROR: ">>r", } - -def first_op_is_pc(i: PwndbgInstruction) -> bool: - return i.operands[0].reg == ARM_REG_PC - - -def ops_contain_pc(i: PwndbgInstruction) -> bool: - for op in i.operands: - if op.type == CS_OP_REG: - if op.reg == ARM_REG_PC: - return True - return False - - -ARM_CAN_WRITE_TO_PC: Dict[int, Callable[[PwndbgInstruction], bool]] = { - ARM_INS_ADD: first_op_is_pc, - ARM_INS_SUB: first_op_is_pc, - ARM_INS_SUBS: first_op_is_pc, - ARM_INS_MOV: first_op_is_pc, - ARM_INS_LDR: first_op_is_pc, - ARM_INS_POP: ops_contain_pc, - ARM_INS_LDM: ops_contain_pc, +# All of these instructions can write to the PC +# https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Application-Level-Programmers--Model/ARM-core-registers/Writing-to-the-PC?lang=en +# If they do write to PC, Capstone gives the instructions the `ARM_GRP_JUMP` group +# Note that we don't have the flag-setting variants - "ands", "subs" - because these generate an illegal instruction interrupt at runtime +ARM_CAN_WRITE_TO_PC_INSTRUCTIONS = { + ARM_INS_LDM, + ARM_INS_ALIAS_LDM, + ARM_INS_POP, + ARM_INS_ALIAS_POP, + ARM_INS_LDR, + ARM_INS_ADC, + ARM_INS_ADD, + ARM_INS_ADR, + ARM_INS_AND, + ARM_INS_ASR, + ARM_INS_ALIAS_ASR, + ARM_INS_BIC, + ARM_INS_EOR, + ARM_INS_LSL, + ARM_INS_ALIAS_LSL, + ARM_INS_LSR, + ARM_INS_ALIAS_LSR, + ARM_INS_MOV, + ARM_INS_MVN, + ARM_INS_ORR, + ARM_INS_ROR, + ARM_INS_ALIAS_ROR, + ARM_INS_RRX, + ARM_INS_ALIAS_RRX, + ARM_INS_RSB, + ARM_INS_RSC, + ARM_INS_SBC, + ARM_INS_SUB, } @@ -178,23 +195,14 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): ARM_MATH_INSTRUCTIONS[instruction.id], ) elif instruction.id in ARM_SHIFT_INSTRUCTIONS: - # If it's a constant shift - if len(instruction.operands) == 2: + # The encoding of shifts has changed between past Capstone versions: https://github.com/capstone-engine/capstone/pull/2638 + # This check avoids a crash + if len(instruction.operands) == 3: self._common_binary_op_annotator( instruction, emu, instruction.operands[0], instruction.operands[1].before_value_no_modifiers, - instruction.operands[1].cs_op.shift.value, - ARM_SHIFT_INSTRUCTIONS[instruction.id], - ) - else: - # Register shift - self._common_binary_op_annotator( - instruction, - emu, - instruction.operands[0], - instruction.operands[1].before_value, instruction.operands[2].before_value, ARM_SHIFT_INSTRUCTIONS[instruction.id], ) @@ -202,13 +210,24 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): self.annotation_handlers.get(instruction.id, lambda *a: None)(instruction, emu) @override - def _condition(self, instruction: PwndbgInstruction, emu: Emulator) -> InstructionCondition: - if instruction.id in ARM_CAN_WRITE_TO_PC: - instruction.declare_is_unconditional_jump = ARM_CAN_WRITE_TO_PC[instruction.id]( - instruction - ) + def _prepare( + self, instruction: PwndbgInstruction, emu: pwndbg.aglib.disasm.arch.Emulator + ) -> None: + if CS_GRP_INT in instruction.groups: + # https://github.com/capstone-engine/capstone/issues/2630 + instruction.groups.remove(CS_GRP_CALL) - if instruction.cs_insn.cc == ARM_CC_AL: + @override + def _condition(self, instruction: PwndbgInstruction, emu: Emulator) -> InstructionCondition: + if ARM_GRP_JUMP in instruction.groups: + if instruction.id in ARM_CAN_WRITE_TO_PC_INSTRUCTIONS: + # Since Capstone V6, instructions that write to the PC are given the jump group. + # However, in Pwndbg code, unless stated otherwise, jumps are assumed to be conditional, so we set this attribute + # to indicate that this is an unconditional branch. + instruction.declare_is_unconditional_jump = True + + # These condition codes indicate unconditionally/condition is not relevant + if instruction.cs_insn.cc in (ARM_CC_AL, ARMCC_UNDEF): if instruction.id in (ARM_INS_B, ARM_INS_BL, ARM_INS_BLX, ARM_INS_BX, ARM_INS_BXJ): instruction.declare_conditional = False return InstructionCondition.UNDETERMINED @@ -325,7 +344,11 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): # See "Operation" at the bottom of https://developer.arm.com/documentation/ddi0597/2024-03/Base-Instructions/LDR--literal---Load-Register--literal-- base = align_down(4, base) - target = base + op.mem.disp + target = base + + # On post index, the base pointer is incremented after the memory dereference + if not instruction.cs_insn.post_index: + target += op.mem.disp * (-1 if op.cs_op.subtracted else 1) # If there is an index register if op.mem.index != 0: @@ -337,7 +360,7 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): if op.cs_op.shift.type != 0: index = ARM_BIT_SHIFT_MAP[op.cs_op.shift.type](index, op.cs_op.shift.value, 32) - target += index * (-1 if op.cs_op.subtracted else 1) + target += index * op.mem.scale return target diff --git a/pwndbg/aglib/disasm/instruction.py b/pwndbg/aglib/disasm/instruction.py index db6dff074..9d0235e40 100644 --- a/pwndbg/aglib/disasm/instruction.py +++ b/pwndbg/aglib/disasm/instruction.py @@ -13,19 +13,20 @@ from capstone import CS_AC from capstone import CS_GRP from capstone import CS_OP from capstone import * # noqa: F403 +from capstone.aarch64 import AARCH64_INS_BL +from capstone.aarch64 import AARCH64_INS_BLR +from capstone.aarch64 import AARCH64_INS_BR from capstone.arm import ARM_INS_TBB from capstone.arm import ARM_INS_TBH - -# from capstone.arm64 import ARM64_INS_B -from capstone.arm64 import ARM64_INS_BL -from capstone.arm64 import ARM64_INS_BLR -from capstone.arm64 import ARM64_INS_BR +from capstone.mips import MIPS_INS_ALIAS_B +from capstone.mips import MIPS_INS_ALIAS_BAL from capstone.mips import MIPS_INS_B from capstone.mips import MIPS_INS_BAL from capstone.mips import MIPS_INS_BLTZAL from capstone.mips import MIPS_INS_J from capstone.mips import MIPS_INS_JAL from capstone.mips import MIPS_INS_JALR +from capstone.mips import MIPS_INS_JALR_HB from capstone.mips import MIPS_INS_JR from capstone.ppc import PPC_INS_B from capstone.ppc import PPC_INS_BA @@ -54,13 +55,23 @@ from pwndbg.dbg import DisassembledInstruction # so we don't need to manually specify those for each architecture UNCONDITIONAL_JUMP_INSTRUCTIONS: Dict[int, Set[int]] = { CS_ARCH_X86: {X86_INS_JMP}, - CS_ARCH_MIPS: {MIPS_INS_J, MIPS_INS_JR, MIPS_INS_JAL, MIPS_INS_JALR, MIPS_INS_BAL, MIPS_INS_B}, + CS_ARCH_MIPS: { + MIPS_INS_J, + MIPS_INS_JR, + MIPS_INS_JAL, + MIPS_INS_JALR, + MIPS_INS_JALR_HB, + MIPS_INS_BAL, + MIPS_INS_ALIAS_BAL, + MIPS_INS_B, + MIPS_INS_ALIAS_B, + }, CS_ARCH_SPARC: {SPARC_INS_JMP, SPARC_INS_JMPL}, CS_ARCH_ARM: { ARM_INS_TBB, ARM_INS_TBH, }, - CS_ARCH_ARM64: {ARM64_INS_BL, ARM64_INS_BLR, ARM64_INS_BR}, + CS_ARCH_AARCH64: {AARCH64_INS_BL, AARCH64_INS_BLR, AARCH64_INS_BR}, CS_ARCH_RISCV: { RISCV_INS_JAL, RISCV_INS_JALR, @@ -115,7 +126,7 @@ class SplitType(Enum): # Only use within the instruction.__repr__ to give a nice output CAPSTONE_ARCH_MAPPING_STRING = { CS_ARCH_ARM: "arm", - CS_ARCH_ARM64: "aarch64", + CS_ARCH_AARCH64: "aarch64", CS_ARCH_X86: "x86", CS_ARCH_PPC: "powerpc", CS_ARCH_MIPS: "mips", @@ -212,9 +223,11 @@ class PwndbgInstructionImpl(PwndbgInstruction): Groups that apply to all architectures: CS_GRP_INVALID | CS_GRP_JUMP | CS_GRP_CALL | CS_GRP_RET | CS_GRP_INT | CS_GRP_IRET | CS_GRP_PRIVILEGE | CS_GRP_BRANCH_RELATIVE """ - self.id: int = cs_insn.id + self.id: int = cs_insn.alias_id if cs_insn.is_alias else cs_insn.id """ The underlying Capstone ID for the instruction + If it's an alias, use the id of the alias + Examples: X86_INS_JMP, X86_INS_CALL, RISCV_INS_C_JAL """ @@ -293,8 +306,8 @@ class PwndbgInstructionImpl(PwndbgInstruction): In most cases, we can determine this purely based on the instruction ID, and this field is irrelevent. However, in some arches, like Arm, the same instruction can be made conditional by certain instruction attributes. Ex: - Arm, `bls` instruction. This is encoded as a `b` (Capstone ID 11) under the code, with an additional condition code field. - In this case, sometimes a `b` instruction (ID 11) is unconditional (always branches), in other cases it is conditional. + Arm, `bls` instruction. This is encoded as a `b` under the code, with an additional condition code field. + In this case, sometimes a `b` instruction is unconditional (always branches), in other cases it is conditional. We use this field to disambiguate these cases. True if we manually determine this instruction is a conditional instruction @@ -305,9 +318,9 @@ class PwndbgInstructionImpl(PwndbgInstruction): self.declare_is_unconditional_jump: bool = False """ This field is used to declare that this instruction is an unconditional jump. - Most of the type, we depend on Capstone groups to check for jump instructions, - but sometimes these are lacking, such as in the case of general-purpose instructions - where the PC is the destination register, such as Arm `add`, `sub`, `ldr`, and `pop` instructions. + Most of the time, we depend on Capstone groups to check for jump instructions. + However, some instructions become branches depending on the operands, + such as Arm `add`, `sub`, `ldr`, `pop`, where PC is the destination register In these cases, we want to forcefully state that this instruction mutates the PC, so we set this attribute to True. @@ -417,6 +430,7 @@ class PwndbgInstructionImpl(PwndbgInstruction): """ return ( self.declare_conditional is not False + and self.declare_is_unconditional_jump is False and bool(self.groups & GENERIC_JUMP_GROUPS) and self.id not in UNCONDITIONAL_JUMP_INSTRUCTIONS[self.cs_insn._cs.arch] ) @@ -482,6 +496,7 @@ class PwndbgInstructionImpl(PwndbgInstruction): info = f"""{self.mnemonic} {self.op_str} at {self.address:#x} (size={self.size}) (arch: {CAPSTONE_ARCH_MAPPING_STRING.get(self.cs_insn._cs.arch,None)}) ID: {self.id}, {self.cs_insn.insn_name()} + Capstone ID/Alias ID: {self.cs_insn.id} / {self.cs_insn.alias_id if self.cs_insn.is_alias else 'None'} Raw asm: {'%-06s %s' % (self.mnemonic, self.op_str)} New asm: {self.asm_string} Next: {self.next:#x} @@ -492,7 +507,7 @@ class PwndbgInstructionImpl(PwndbgInstruction): Operands: [{operands_str}] Conditional jump: {self.is_conditional_jump}. Taken: {self.is_conditional_jump_taken} Unconditional jump: {self.is_unconditional_jump} - Declare unconditional: {self.declare_conditional} + Declare conditional: {self.declare_conditional} Declare unconditional jump: {self.declare_is_unconditional_jump} Force jump target: {self.force_unconditional_jump_target} Can change PC: {self.has_jump_target} diff --git a/pwndbg/aglib/disasm/mips.py b/pwndbg/aglib/disasm/mips.py index ac0ea88bc..a480f0c4a 100644 --- a/pwndbg/aglib/disasm/mips.py +++ b/pwndbg/aglib/disasm/mips.py @@ -33,12 +33,57 @@ from pwndbg.aglib.disasm.instruction import PwndbgInstruction if TYPE_CHECKING: from pwndbg.emu.emulator import Emulator +# These branch instructions do not have a branch delay +BRANCH_WITHOUT_DELAY_SLOT_INSTRUCTIONS = { + MIPS_INS_BC, + MIPS_INS_BALC, + MIPS_INS_JIALC, + MIPS_INS_JIC, + MIPS_INS_BLEZALC, + MIPS_INS_BGEZALC, + MIPS_INS_BGTZALC, + MIPS_INS_BLTZALC, + MIPS_INS_BEQZALC, + MIPS_INS_BNEZALC, + MIPS_INS_BLEZC, + MIPS_INS_BGEZC, + MIPS_INS_BGEUC, + MIPS_INS_BGEIC, + MIPS_INS_BGEUC, + MIPS_INS_BGEIUC, + MIPS_INS_BGTZC, + MIPS_INS_BLTZC, + MIPS_INS_BEQZC, + MIPS_INS_ALIAS_BEQZC, + MIPS_INS_BNEZC, + MIPS_INS_ALIAS_BNEZC, + MIPS_INS_BEQC, + MIPS_INS_ALIAS_BEQC, + MIPS_INS_BEQIC, + MIPS_INS_BNEC, + MIPS_INS_ALIAS_BNEC, + MIPS_INS_BNEIC, + MIPS_INS_BLTC, + MIPS_INS_BLTIC, + MIPS_INS_BLTUC, + MIPS_INS_BLTIUC, + MIPS_INS_BGEC, + MIPS_INS_BLTUC, + MIPS_INS_BNVC, + MIPS_INS_BOVC, + MIPS_INS_BRSC, + MIPS_INS_BALRSC, + MIPS_INS_BBEQZC, + MIPS_INS_BBNEZC, +} +# Instruction in branch delay slot is ignored if the branch it NOT taken +# Currently unused BRANCH_LIKELY_INSTRUCTIONS = { - MIPS_INS_BC0TL, - MIPS_INS_BC1TL, - MIPS_INS_BC0FL, MIPS_INS_BC1FL, + MIPS_INS_ALIAS_BC1FL, + MIPS_INS_BC1TL, + MIPS_INS_ALIAS_BC1TL, MIPS_INS_BEQL, MIPS_INS_BGEZALL, MIPS_INS_BGEZL, @@ -47,9 +92,10 @@ BRANCH_LIKELY_INSTRUCTIONS = { MIPS_INS_BLTZALL, MIPS_INS_BLTZL, MIPS_INS_BNEL, + MIPS_INS_ALIAS_BNEZL, + MIPS_INS_ALIAS_BEQZL, } - CONDITION_RESOLVERS: Dict[int, Callable[[List[int]], bool]] = { MIPS_INS_BEQZ: lambda ops: ops[0] == 0, MIPS_INS_BNEZ: lambda ops: ops[0] != 0, @@ -63,6 +109,9 @@ CONDITION_RESOLVERS: Dict[int, Callable[[List[int]], bool]] = { MIPS_INS_BLTZ: lambda ops: bit_math.to_signed(ops[0], pwndbg.aglib.arch.ptrsize * 8) < 0, } +CONDITION_RESOLVERS[MIPS_INS_ALIAS_BEQZ] = CONDITION_RESOLVERS[MIPS_INS_BEQZ] +CONDITION_RESOLVERS[MIPS_INS_ALIAS_BNEZ] = CONDITION_RESOLVERS[MIPS_INS_BNEZ] + # These are instructions that have the first operand as the destination register. # They all do some computation and set the register to the result. # These were derived from "MIPS Architecture for Programmers Volume II: The MIPS64 Instruction Set Reference Manual" @@ -150,6 +199,7 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): self.annotation_handlers: Dict[int, Callable[[PwndbgInstruction, Emulator], None]] = { # MOVE MIPS_INS_MOVE: self._common_move_annotator, + MIPS_INS_ALIAS_MOVE: self._common_move_annotator, # LI MIPS_INS_LI: self._common_move_annotator, # LUI @@ -207,12 +257,6 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): @override def _condition(self, instruction: PwndbgInstruction, emu: Emulator) -> InstructionCondition: - if instruction.id == MIPS_INS_JAL: - # Capstone bug - JAL with an immediate (jal 0x1000000) has no jump group in the list, so we add it manually - # See: https://github.com/capstone-engine/capstone/issues/2448 - # And: https://github.com/capstone-engine/capstone/issues/1680 - instruction.groups.add(CS_GRP_CALL) - if len(instruction.operands) == 0: return InstructionCondition.UNDETERMINED @@ -239,7 +283,7 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): @override def _resolve_target(self, instruction: PwndbgInstruction, emu: Emulator | None): if bool(instruction.groups & FORWARD_JUMP_GROUP) and not bool( - instruction.groups & BRANCH_LIKELY_INSTRUCTIONS + instruction.groups & BRANCH_WITHOUT_DELAY_SLOT_INSTRUCTIONS ): instruction.causes_branch_delay = True diff --git a/pwndbg/aglib/disasm/riscv.py b/pwndbg/aglib/disasm/riscv.py index d3d2c3054..3558955cf 100644 --- a/pwndbg/aglib/disasm/riscv.py +++ b/pwndbg/aglib/disasm/riscv.py @@ -29,23 +29,17 @@ RISCV_LOAD_INSTRUCTIONS = { RISCV_INS_LHU: 2, RISCV_INS_LWU: 4, RISCV_INS_LD: 8, + RISCV_INS_C_LW: -4, + RISCV_INS_C_LWSP: -4, + RISCV_INS_C_LD: 8, + RISCV_INS_C_LDSP: 8, } -# Due to a bug in Capstone, these instructions have incorrect operands to represent a memory address. -# So we temporarily separate them to handle them differently -# This will be fixed in Capstone 6 - https://github.com/capstone-engine/capstone/pull/2393 -# TODO: remove this when updating to Capstone 6 -RISCV_COMPRESSED_LOAD_INSTRUCTIONS = {RISCV_INS_C_LW: -4, RISCV_INS_C_LD: 8, RISCV_INS_C_LDSP: 8} - RISCV_STORE_INSTRUCTIONS = { RISCV_INS_SB: 1, RISCV_INS_SH: 2, RISCV_INS_SW: 4, RISCV_INS_SD: 8, -} - -# TODO: remove this when updating to Capstone 6 -RISCV_COMPRESSED_STORE_INSTRUCTIONS = { RISCV_INS_C_SW: 4, RISCV_INS_C_SWSP: 4, RISCV_INS_C_SD: 8, @@ -150,26 +144,6 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): instruction.operands[0].str, instruction.operands[1].str, ) - # TODO: remove this when updating to Capstone 6 - elif instruction.id in RISCV_COMPRESSED_LOAD_INSTRUCTIONS: - # We need to manually resolve this now since Capstone doesn't properly represent - # memory operands for compressed instructions. - address = self._resolve_compressed_target_addr(instruction, emu) - if address is not None: - read_size = RISCV_COMPRESSED_LOAD_INSTRUCTIONS[instruction.id] - - dest_str = f"[{MemoryColor.get_address_or_symbol(address)}]" - - self._common_load_annotator( - instruction, - emu, - address, - abs(read_size), - read_size < 0, - pwndbg.aglib.arch.ptrsize, - instruction.operands[0].str, - dest_str, - ) elif instruction.id in RISCV_STORE_INSTRUCTIONS: self._common_store_annotator( instruction, @@ -179,21 +153,6 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): RISCV_STORE_INSTRUCTIONS[instruction.id], instruction.operands[1].str, ) - elif instruction.id in RISCV_COMPRESSED_STORE_INSTRUCTIONS: - # TODO: remove this branch when updating to Capstone 6 - address = self._resolve_compressed_target_addr(instruction, emu) - - if address is not None: - dest_str = f"[{MemoryColor.get_address_or_symbol(address)}]" - - self._common_store_annotator( - instruction, - emu, - address, - instruction.operands[0].before_value, - RISCV_COMPRESSED_STORE_INSTRUCTIONS[instruction.id], - dest_str, - ) elif instruction.id in RISCV_MATH_INSTRUCTIONS: # We need this check, because some of these instructions can encoded as aliases # Example: NOP is an alias of ADDI where target is x0. In Capstone, the ID will still be that of ADDI but with no operands @@ -233,22 +192,6 @@ class DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant): result_operand.str, MemoryColor.get_address_and_symbol(address) ) - def _resolve_compressed_target_addr( - self, instruction: PwndbgInstruction, emu: Emulator - ) -> int | None: - """ - Calculate the address used in a compressed load/store instruction. - None if address cannot be resolved. - - TODO: remove this when updating to Capstone 6 - """ - _, disp, reg = instruction.operands - - if disp.before_value is None or reg.before_value is None: - return None - - return disp.before_value + reg.before_value - def _is_condition_taken( self, instruction: PwndbgInstruction, emu: Emulator | None ) -> InstructionCondition: diff --git a/pwndbg/emu/emulator.py b/pwndbg/emu/emulator.py index 09dcd7833..2195252a4 100644 --- a/pwndbg/emu/emulator.py +++ b/pwndbg/emu/emulator.py @@ -14,7 +14,6 @@ from typing import Tuple import capstone as C import unicorn as U -import unicorn.riscv_const import pwndbg.aglib.arch import pwndbg.aglib.disasm @@ -180,7 +179,7 @@ arch_to_SYSCALL = { U.UC_ARCH_MIPS: [C.mips_const.MIPS_INS_SYSCALL], U.UC_ARCH_SPARC: [C.sparc_const.SPARC_INS_T], U.UC_ARCH_ARM: [C.arm_const.ARM_INS_SVC], - U.UC_ARCH_ARM64: [C.arm64_const.ARM64_INS_SVC], + U.UC_ARCH_ARM64: [C.aarch64_const.AARCH64_INS_SVC], U.UC_ARCH_PPC: [C.ppc_const.PPC_INS_SC], U.UC_ARCH_RISCV: [C.riscv_const.RISCV_INS_ECALL], } @@ -197,7 +196,7 @@ BANNED_INSTRUCTIONS = { "mips": {C.mips.MIPS_INS_RDHWR}, "arm": ARM_BANNED_INSTRUCTIONS, "armcm": ARM_BANNED_INSTRUCTIONS, - "aarch64": {C.arm64.ARM64_INS_MRS}, + "aarch64": {C.aarch64.AARCH64_INS_MRS}, } # https://github.com/unicorn-engine/unicorn/issues/550 @@ -254,7 +253,8 @@ class Emulator: self.last_single_step_result = InstructionExecutedResult(None, None) # Initialize the register state - for reg in self.regs.emulated_regs_order: + for emu_reg in self.regs.emulated_regs_order: + reg = emu_reg.name enum = self.get_reg_enum(reg) if not reg: @@ -270,8 +270,9 @@ class Emulator: debug(DEBUG_INIT, "# Could not set register %r", reg) continue - # All registers are initialized to zero. - if value == 0: + # Most registers are initialized to zero. + # However, some registers (CPSR on AArch64) do not default to zero, so we must explicitly set them to 0 + if not emu_reg.force_write and value == 0: continue name = f"U.x86_const.UC_X86_REG_{reg.upper()}" diff --git a/pwndbg/lib/disasm/helpers.py b/pwndbg/lib/disasm/helpers.py index eb5c75378..ccedfe4e7 100644 --- a/pwndbg/lib/disasm/helpers.py +++ b/pwndbg/lib/disasm/helpers.py @@ -5,6 +5,7 @@ def to_signed(unsigned: int, bit_width: int): """ Returns the signed number associated with the two's-complement binary representation of `unsigned` """ + unsigned = unsigned & ((1 << bit_width) - 1) extract_bit = 1 << (bit_width - 1) return unsigned - ((unsigned & extract_bit) << 1) diff --git a/pwndbg/lib/regs.py b/pwndbg/lib/regs.py index 22978babe..f89bd19fa 100644 --- a/pwndbg/lib/regs.py +++ b/pwndbg/lib/regs.py @@ -5,6 +5,7 @@ standardized interface to registers like "sp" and "pc". from __future__ import annotations +from dataclasses import dataclass from typing import Dict from typing import Iterator from typing import List @@ -18,6 +19,16 @@ from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE BitFlags = OrderedDict[str, Union[int, Tuple[int, int]]] +@dataclass +class EmulatedRegister: + """ + Represent a register to write to the Unicorn emulator. + """ + + name: str + force_write: bool + + class RegisterSet: #: Program counter register pc: str @@ -87,11 +98,12 @@ class RegisterSet: # we must write the flags register after PC, and the stack pointer after the flags register. # Otherwise, the values will be clobbered # https://github.com/pwndbg/pwndbg/pull/2337 - self.emulated_regs_order: List[str] = [] + self.emulated_regs_order: List[EmulatedRegister] = [] for reg in [pc] + list(flags) + [stack, frame] + list(retaddr) + list(misc) + list(gpr): if reg and reg not in self.emulated_regs_order: - self.emulated_regs_order.append(reg) + emu_reg = EmulatedRegister(reg, True if reg in flags else False) + self.emulated_regs_order.append(emu_reg) self.all = set(misc) | set(flags) | set(extra_flags) | set(self.retaddr) | set(self.common) self.all -= {None} diff --git a/pyproject.toml b/pyproject.toml index 87ce26c38..1d8b1b48d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [{ name = "Dominik 'disconnect3d' Czarnota", email = "dominik.b.czarno requires-python = "~=3.10" readme = "README.md" dependencies = [ - "capstone>=5.0.3,<6", + "capstone==6.0.0a4", "unicorn>=2.1.3,<3", "pwntools>=4.14.0,<5", "sortedcontainers>=2.4.0,<3", diff --git a/tests/qemu-tests/tests/user/test_aarch64.py b/tests/qemu-tests/tests/user/test_aarch64.py index 536f29eb5..58fc2689b 100644 --- a/tests/qemu-tests/tests/user/test_aarch64.py +++ b/tests/qemu-tests/tests/user/test_aarch64.py @@ -54,13 +54,13 @@ def test_aarch64_branch_enhancement(qemu_assembly_run): expected = ( "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" - " ► 0x10000000 <_start> bl #my_function \n" + " ► 0x10000000 <_start> bl my_function \n" " x0: 0\n" " x1: 0\n" " x2: 0\n" " x3: 0\n" " \n" - " 0x10000004 <_start+4> b #end \n" + " 0x10000004 <_start+4> b end \n" " ↓\n" " 0x1000000c mov x0, #0 X0 => 0\n" " 0x10000010 mov x8, #0x5d X8 => 0x5d\n" @@ -77,7 +77,7 @@ def test_aarch64_branch_enhancement(qemu_assembly_run): assert dis == expected # Now, ensure the `b` instruction is set correctly. - gdb.execute("si") + gdb.execute("ni") instruction = pwndbg.aglib.disasm.one_with_config() assert not instruction.is_conditional_jump @@ -178,19 +178,19 @@ def test_aarch64_conditional_jump_output(qemu_assembly_run): "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" " ► 0x10000000 <_start> mov x2, #0xa X2 => 0xa\n" " 0x10000004 <_start+4> mov x3, #0 X3 => 0\n" - " 0x10000008 <_start+8> ✔ cbz x3, #A \n" + " 0x10000008 <_start+8> ✔ cbz x3, A \n" " ↓\n" - " 0x10000010 ✔ cbnz x2, #B \n" + " 0x10000010 ✔ cbnz x2, B \n" " ↓\n" - " 0x10000018 ✔ tbz w2, #0, #C \n" + " 0x10000018 ✔ tbz w2, #0, C \n" " ↓\n" - " 0x10000020 ✔ tbnz w2, #3, #D \n" + " 0x10000020 ✔ tbnz w2, #3, D \n" " ↓\n" " 0x10000028 cmp x2, x3 0xa - 0x0 CPSR => 0x20000000 [ n z C v q pan il d a i f el:0 sp ]\n" - " 0x1000002c b.eq #E \n" + " 0x1000002c b.eq E \n" " \n" " 0x10000030 nop \n" - " 0x10000034 ✔ b.ne #F \n" + " 0x10000034 ✔ b.ne F \n" " ↓\n" " 0x1000003c mov x0, #0 X0 => 0\n" "────────────────────────────────────────────────────────────────────────────────\n" @@ -246,25 +246,23 @@ def test_conditional_jumps_no_emulate(qemu_assembly_run): test_aarch64_conditional_jumps(qemu_assembly_run) -AARCH64_ASSEMBLY = """ +AARCH64_BINARY_OPERATIONS = """ mov x0, 7 mov x1, 563 - add x2, x0, x1 sub x3, x1, x0 +adds x2, x0, x1 +subs x3, x1, x0 and x4, x0, x1 orr x5, x0, x1 eor x6, x0, x1 -lsl x7, x0, 2 -lsr x8, x1, 2 mul x10, x0, x1 udiv x11, x1, x0 - """ def test_aarch64_binary_operations(qemu_assembly_run): - qemu_assembly_run(AARCH64_ASSEMBLY, "aarch64") + qemu_assembly_run(AARCH64_BINARY_OPERATIONS, "aarch64") dis = gdb.execute("context disasm", to_string=True) dis = pwndbg.color.strip(dis) @@ -276,11 +274,11 @@ def test_aarch64_binary_operations(qemu_assembly_run): " 0x10000004 <_start+4> mov x1, #0x233 X1 => 0x233\n" " 0x10000008 <_start+8> add x2, x0, x1 X2 => 0x23a (0x7 + 0x233)\n" " 0x1000000c <_start+12> sub x3, x1, x0 X3 => 0x22c (0x233 - 0x7)\n" - " 0x10000010 <_start+16> and x4, x0, x1 X4 => 3 (0x7 & 0x233)\n" - " 0x10000014 <_start+20> orr x5, x0, x1 X5 => 0x237 (0x7 & 0x233)\n" - " 0x10000018 <_start+24> eor x6, x0, x1 X6 => 0x234 (0x7 ^ 0x233)\n" - " 0x1000001c <_start+28> lsl x7, x0, #2 X7 => 28 (7 << 2)\n" - " 0x10000020 <_start+32> lsr x8, x1, #2 X8 => 140 (0x233 >> 0x2)\n" + " 0x10000010 <_start+16> adds x2, x0, x1 X2 => 0x23a (0x7 + 0x233)\n" + " 0x10000014 <_start+20> subs x3, x1, x0 X3 => 0x22c (0x233 - 0x7)\n" + " 0x10000018 <_start+24> and x4, x0, x1 X4 => 3 (0x7 & 0x233)\n" + " 0x1000001c <_start+28> orr x5, x0, x1 X5 => 0x237 (0x7 | 0x233)\n" + " 0x10000020 <_start+32> eor x6, x0, x1 X6 => 0x234 (0x7 ^ 0x233)\n" " 0x10000024 <_start+36> mul x10, x0, x1 X10 => 0xf65 (0x7 * 0x233)\n" " 0x10000028 <_start+40> udiv x11, x1, x0 X11 => 80 (0x233 / 0x7)\n" "────────────────────────────────────────────────────────────────────────────────\n" @@ -342,16 +340,16 @@ def test_aarch64_store_instructions(qemu_assembly_run): expected = ( "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" - " ► 0x10000028 ldr x4, #stores+56 X4, 0x10000060 => 0x4010e8 (value1) ◂— 0\n" - " 0x1000002c strb w0, [x4] [value1] <= 0xf0\n" - " 0x10000030 ldr x5, #stores+64 X5, 0x10000068 => 0x4010e9 (value2) ◂— 0\n" - " 0x10000034 strh w0, [x5] [value2] <= 0xdef0\n" - " 0x10000038 ldr x6, #stores+72 X6, 0x10000070 => 0x4010eb (value4) ◂— 0\n" - " 0x1000003c str w0, [x6] [value4] <= 0x9abcdef0\n" - " 0x10000040 ldr x7, #stores+80 X7, 0x10000078 => 0x4010ef (value8) ◂— 0\n" - " 0x10000044 str x0, [x7] [value8] <= 0x123456789abcdef0\n" - " 0x10000048 mov x8, #0x5d X8 => 0x5d\n" - " 0x1000004c mov x0, #0 X0 => 0\n" + " ► 0x10000028 ldr x4, stores+56 X4, [stores+56] => 0x4010e8 (value1) ◂— 0\n" + " 0x1000002c strb w0, [x4] [value1] <= 0xf0\n" + " 0x10000030 ldr x5, stores+64 X5, [stores+64] => 0x4010e9 (value2) ◂— 0\n" + " 0x10000034 strh w0, [x5] [value2] <= 0xdef0\n" + " 0x10000038 ldr x6, stores+72 X6, [stores+72] => 0x4010eb (value4) ◂— 0\n" + " 0x1000003c str w0, [x6] [value4] <= 0x9abcdef0\n" + " 0x10000040 ldr x7, stores+80 X7, [stores+80] => 0x4010ef (value8) ◂— 0\n" + " 0x10000044 str x0, [x7] [value8] <= 0x123456789abcdef0\n" + " 0x10000048 mov x8, #0x5d X8 => 0x5d\n" + " 0x1000004c mov x0, #0 X0 => 0\n" " 0x10000050 svc #0 \n" "────────────────────────────────────────────────────────────────────────────────\n" ) @@ -432,6 +430,252 @@ def test_aarch64_load_instructions(qemu_assembly_run): assert dis == expected +CPSR_REGISTER_TEST = f""" +mov x19, #8 +cmn x19, #8 +b.ne exit + +nop +nop +nop +nop +nop + +end: +exit: +{AARCH64_GRACEFUL_EXIT} +""" + + +def test_aarch64_write_cpsr_when_zero(qemu_assembly_run): + """ + The purpose of this test is to ensure we writing our CPSR register to the Unicorn emulator always. + + We have an optimization to not write registers with the value zero to the emulator. This conflicts with the flags register. + The CPSR register, by default, has the Z bit enabled, so the value is not 0. In this test, we do a comparison that sets the bit off, + making CPSR have the value of zero. If we don't write 0 to this register explicitly emulator, Unicorn will take the "default" value which has the Z bit enabled. + And therefore, the branch will be mispredicted. + """ + + qemu_assembly_run(CPSR_REGISTER_TEST, "aarch64") + + # Warm up the instruction cache so when we step forward, we remember the earlier instructions + gdb.execute("context disasm") + + gdb.execute("si") + gdb.execute("si") + + dis = gdb.execute("context disasm", to_string=True) + dis = pwndbg.color.strip(dis) + + expected = ( + "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" + "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" + " 0x10000000 <_start> mov x19, #8 X19 => 8\n" + " 0x10000004 <_start+4> cmn x19, #8 8 + 8 CPSR => 0x0 [ n z c v q pan il d a i f el:0 sp ]\n" + " ► 0x10000008 <_start+8> ✔ b.ne exit \n" + " ↓\n" + " 0x10000020 mov x0, #0 X0 => 0\n" + " 0x10000024 mov x8, #0x5d X8 => 0x5d\n" + " 0x10000028 svc #0 \n" + " 0x1000002c udf #0\n" + " 0x10000030 udf #0\n" + " 0x10000034 udf #0\n" + " 0x10000038 udf #0\n" + " 0x1000003c udf #0\n" + "────────────────────────────────────────────────────────────────────────────────\n" + ) + + assert dis == expected + + +AARCH64_MEMORY_OPERAND_TEST = rf""" +LDR X1, =msg +LDR W0, [X1], #4 +LDR W0, [X1, #4] +MOV X3, #8 +LDR W4, [X1, X3] +LDR W5, [X1, #-4] +MOV X6, #-4 +LDR W7, [X1, X6] + +exit: +{AARCH64_GRACEFUL_EXIT} + +data: +.data +msg: + .asciz "ABCDEFGHIJKLMNOPQRSTUVWXYZ!" +""" + + +def test_aarch64_memory_operands(qemu_assembly_run): + """ + Testing LDR instructions with interseting operands: + - LDR a constant into a register + - Post-index offset + - Pre-index with constant/register + - Pre-index with negated constants/register + - Include shifts + """ + + qemu_assembly_run(AARCH64_MEMORY_OPERAND_TEST, "aarch64") + + dis = gdb.execute("context disasm", to_string=True) + dis = pwndbg.color.strip(dis) + + expected = ( + "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" + "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" + " ► 0x10000000 <_start> ldr x1, data+4 X1, [data+4] => 0x4010e8 (msg) ◂— 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!'\n" + " 0x10000004 <_start+4> ldr w0, [x1], #4 W0, [msg] => 0x44434241\n" + " 0x10000008 <_start+8> ldr w0, [x1, #4] W0, [msg+8] => 0x4c4b4a49\n" + " 0x1000000c <_start+12> mov x3, #8 X3 => 8\n" + " 0x10000010 <_start+16> ldr w4, [x1, x3] W4, [msg+12] => 0x504f4e4d\n" + " 0x10000014 <_start+20> ldur w5, [x1, #-4] W5, [msg] => 0x44434241\n" + " 0x10000018 <_start+24> mov x6, #-4 X6 => 0xfffffffffffffffc\n" + " 0x1000001c <_start+28> ldr w7, [x1, x6] W7, [msg] => 0x44434241\n" + " 0x10000020 mov x0, #0 X0 => 0\n" + " 0x10000024 mov x8, #0x5d X8 => 0x5d\n" + " 0x10000028 svc #0 \n" + "────────────────────────────────────────────────────────────────────────────────\n" + ) + + assert dis == expected + + +AARCH64_SHIFTS_AND_EXTENDS = r""" +mov X1, 1 +mov X3, 8 +ADD X0, X1, X1, LSL 2 +ADD X0, X1, X3, LSR 2 + +MOV W2, 0xFFFFFFFF +ADD X0, X1, W2, SXTB +ADD X0, X1, W2, UXTB + +ADD X0, X1, X2, ASR 2 +MOV X0, X1, ROR 2 + +SXTB X2, w2 +ADD X0, X1, X2, ASR 2 +""" + + +def test_aarch64_shifts_and_extends(qemu_assembly_run): + """ + Ensure our logic in register shifts + extends are working correctly + """ + qemu_assembly_run(AARCH64_SHIFTS_AND_EXTENDS, "aarch64") + + dis = gdb.execute("context disasm", to_string=True) + dis = pwndbg.color.strip(dis) + + expected = ( + "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" + "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" + " ► 0x10000000 <_start> mov x1, #1 X1 => 1\n" + " 0x10000004 <_start+4> mov x3, #8 X3 => 8\n" + " 0x10000008 <_start+8> add x0, x1, x1, lsl #2 X0 => 5 (1 + 4)\n" + " 0x1000000c <_start+12> add x0, x1, x3, lsr #2 X0 => 3 (1 + 2)\n" + " 0x10000010 <_start+16> mov w2, #-1 W2 => 0xffffffff\n" + " 0x10000014 <_start+20> add x0, x1, w2, sxtb X0 => 0 (0x1 + 0xffffffffffffffff)\n" + " 0x10000018 <_start+24> add x0, x1, w2, uxtb X0 => 0x100 (0x1 + 0xff)\n" + " 0x1000001c <_start+28> add x0, x1, x2, asr #2 X0 => 0x40000000 (0x1 + 0x3fffffff)\n" + " 0x10000020 <_start+32> orr x0, xzr, x1, ror #2 X0 => 0x4000000000000000 (0x0 | 0x4000000000000000)\n" + " 0x10000024 <_start+36> sxtb x2, w2\n" + " 0x10000028 <_start+40> add x0, x1, x2, asr #2 X0 => 0 (0x1 + 0xffffffffffffffff)\n" + "────────────────────────────────────────────────────────────────────────────────\n" + ) + + assert dis == expected + + +AARCH64_MEMORY_OPERAND_SHIFT = r""" +LDR x2, =msg +ADD x2,x2,16 +MOV w3, 0xffffffff +ldr x1, [x2, w3, sxtw] +ldr x1, [x2, w3, sxtw 3] +nop +.data +msg: + .asciz "ABCDEFGHIJKLMNOPQRSTUVWXYZ!" +""" + + +def test_aarch64_shifts_and_extends_in_memory_operands(qemu_assembly_run): + qemu_assembly_run(AARCH64_MEMORY_OPERAND_SHIFT, "aarch64") + + dis = gdb.execute("context disasm", to_string=True) + dis = pwndbg.color.strip(dis) + + expected = ( + "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" + "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" + " ► 0x10000000 <_start> ldr x2, _start+24 X2, [_start+24] => 0x4010e8 (msg) ◂— 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!'\n" + " 0x10000004 <_start+4> add x2, x2, #0x10 X2 => 0x4010f8 (msg+16) (0x4010e8 + 0x10)\n" + " 0x10000008 <_start+8> mov w3, #-1 W3 => 0xffffffff\n" + " 0x1000000c <_start+12> ldr x1, [x2, w3, sxtw] X1, [msg+15] => 0x5756555453525150 ('PQRSTUVW')\n" + " 0x10000010 <_start+16> ldr x1, [x2, w3, sxtw #3] X1, [msg+8] => 0x504f4e4d4c4b4a49 ('IJKLMNOP')\n" + " 0x10000014 <_start+20> nop \n" + "\n" + "\n" + "\n" + "\n" + "\n" + "────────────────────────────────────────────────────────────────────────────────\n" + ) + + assert dis == expected + + +AARCH64_SHIFT_INSTRUCTIONS = """ +MOV x0, #3 +MOV x1, #0xF000 +MOV x2, #0x1234 +LSR x3, x1, #4 +LSR x4, x1, x0 +LSL x5, x4, #4 +LSL x6, x4, x2 +ASR x6, x4, #4 +ASR x6, x4, x0 +ROR x6, x4, #4 +ROR x6, x4, x0 +""" + + +def test_aarch64_shift_instructions(qemu_assembly_run): + """ + Test annotations for shift instructions - the format of these has changed between Capstone versions. + Special attention is paid to the shift-by-register amount + - https://github.com/capstone-engine/capstone/issues/2631 + """ + qemu_assembly_run(AARCH64_SHIFT_INSTRUCTIONS, "aarch64") + + dis = gdb.execute("context disasm", to_string=True) + dis = pwndbg.color.strip(dis) + + expected = ( + "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" + "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" + " ► 0x10000000 <_start> mov x0, #3 X0 => 3\n" + " 0x10000004 <_start+4> mov x1, #0xf000 X1 => 0xf000\n" + " 0x10000008 <_start+8> mov x2, #0x1234 X2 => 0x1234\n" + " 0x1000000c <_start+12> lsr x3, x1, #4 X3 => 0xf00 (0xf000 >> 0x4)\n" + " 0x10000010 <_start+16> lsr x4, x1, x0 X4 => 0x1e00\n" + " 0x10000014 <_start+20> lsl x5, x4, #4 X5 => 0x1e000 (0x1e00 << 0x4)\n" + " 0x10000018 <_start+24> lsl x6, x4, x2 X6 => 0xe000000000000000\n" + " 0x1000001c <_start+28> asr x6, x4, #4 X6 => 0x1e0 (0x1e00 >>s 0x4)\n" + " 0x10000020 <_start+32> asr x6, x4, x0 X6 => 0x3c0\n" + " 0x10000024 <_start+36> ror x6, x4, #4 X6 => 0x1e0 (0x1e00 >>r 0x4)\n" + " 0x10000028 <_start+40> ror x6, x4, x0 X6 => 0x3c0\n" + "────────────────────────────────────────────────────────────────────────────────\n" + ) + + assert dis == expected + + REFERENCE_BINARY = user.binaries.get("reference-binary.aarch64.out") diff --git a/tests/qemu-tests/tests/user/test_arm.py b/tests/qemu-tests/tests/user/test_arm.py index 937f35e7e..f494beeeb 100644 --- a/tests/qemu-tests/tests/user/test_arm.py +++ b/tests/qemu-tests/tests/user/test_arm.py @@ -48,14 +48,14 @@ def test_arm_simple_branch(qemu_assembly_run): " ► 0x10000000 <_start> mov r2, #5 R2 => 5\n" " 0x10000004 <_start+4> mov r1, #0xa R1 => 0xa\n" " 0x10000008 <_start+8> cmp r0, r1 0x0 - 0xa CPSR => 0x80000010 [ N z c v q j t e a i f ]\n" - " 0x1000000c <_start+12> ✔ bne #not_equal \n" + " 0x1000000c <_start+12> ✔ bne not_equal \n" " ↓\n" " 0x10000018 mov r3, #1 R3 => 1\n" " 0x1000001c cmp r1, r3 0xa - 0x1 CPSR => 0x20000010 [ n z C v q j t e a i f ]\n" - " 0x10000020 ✔ bgt #greater \n" + " 0x10000020 ✔ bgt greater \n" " ↓\n" " 0x1000002c cmp r3, r1 0x1 - 0xa CPSR => 0x80000010 [ N z c v q j t e a i f ]\n" - " 0x10000030 ✔ bls #end \n" + " 0x10000030 ✔ bls end \n" " ↓\n" " 0x1000003c mov r0, #0 R0 => 0\n" " 0x10000040 mov r7, #0xf8 R7 => 0xf8\n" @@ -71,14 +71,14 @@ def test_arm_simple_branch(qemu_assembly_run): expected = ( "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "──────────────────[ DISASM / arm / arm mode / set emulate on ]──────────────────\n" - " 0x1000000c <_start+12> ✔ bne #not_equal \n" + " 0x1000000c <_start+12> ✔ bne not_equal \n" " ↓\n" " 0x10000018 mov r3, #1 R3 => 1\n" " 0x1000001c cmp r1, r3 0xa - 0x1 CPSR => 0x20000010 [ n z C v q j t e a i f ]\n" - " 0x10000020 ✔ bgt #greater \n" + " 0x10000020 ✔ bgt greater \n" " ↓\n" " 0x1000002c cmp r3, r1 0x1 - 0xa CPSR => 0x80000010 [ N z c v q j t e a i f ]\n" - " ► 0x10000030 ✔ bls #end \n" + " ► 0x10000030 ✔ bls end \n" " ↓\n" " 0x1000003c mov r0, #0 R0 => 0\n" " 0x10000040 mov r7, #0xf8 R7 => 0xf8\n" @@ -419,7 +419,7 @@ def test_arm_cmp_instructions(qemu_assembly_run): " ► 0x10000000 <_start> mov r0, #5 R0 => 5\n" " 0x10000004 <_start+4> mov r1, #5 R1 => 5\n" " 0x10000008 <_start+8> cmp r0, r1 5 - 5 CPSR => 0x60000010 [ n Z C v q j t e a i f ]\n" - " 0x1000000c <_start+12> ✔ beq #end \n" + " 0x1000000c <_start+12> ✔ beq end \n" " ↓\n" " 0x1000001c mov r0, #0 R0 => 0\n" " 0x10000020 mov r7, #0xf8 R7 => 0xf8\n" @@ -467,7 +467,7 @@ def test_arm_call_instructions(qemu_assembly_run): "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "──────────────────[ DISASM / arm / arm mode / set emulate on ]──────────────────\n" " ► 0x10000000 <_start> nop \n" - " 0x10000004 <_start+4> bl #func \n" + " 0x10000004 <_start+4> bl func \n" " \n" " 0x10000008 <_start+8> nop \n" " 0x1000000c <_start+12> nop \n" @@ -536,39 +536,144 @@ def test_arm_exclusive_store(qemu_assembly_run): ARM_SHIFTS = """ -MOV r0, #0xF000 -LSR r1, r0, #4 -MOV r2, #2 -LSR r3, r0, r2 -MOV r4, #0x1234 -LSL r5, r4, #8 +MOV r0, #3 +MOV r1, #0xF000 +MOV r2, #0x1234 +LSR r3, r1, #4 +LSR r4, r1, r0 +LSL r5, r4, #4 LSL r6, r4, r2 +ASR r6, r4, #4 +ASR r6, r4, r0 +ROR r6, r4, #4 +ROR r6, r4, r0 +""" + + +def test_arm_logical_shifts(qemu_assembly_run): + """ + Shifts have a different underlying Capstone representation if it's an constant or a register offset. + This test ensures we handle both cases. + """ + qemu_assembly_run(ARM_SHIFTS, "arm") + dis = gdb.execute("context disasm", to_string=True) + dis = pwndbg.color.strip(dis) + + expected = ( + "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" + "──────────────────[ DISASM / arm / arm mode / set emulate on ]──────────────────\n" + " ► 0x10000000 <_start> mov r0, #3 R0 => 3\n" + " 0x10000004 <_start+4> mov r1, #0xf000 R1 => 0xf000\n" + " 0x10000008 <_start+8> movw r2, #0x1234 R2 => 0x1234\n" + " 0x1000000c <_start+12> lsr r3, r1, #4 R3 => 0xf00 (0xf000 >> 0x4)\n" + " 0x10000010 <_start+16> lsr r4, r1, r0 R4 => 0x1e00 (0xf000 >> 0x3)\n" + " 0x10000014 <_start+20> lsl r5, r4, #4 R5 => 0x1e000 (0x1e00 << 0x4)\n" + " 0x10000018 <_start+24> lsl r6, r4, r2 R6 => 0 (0x1e00 << 0x1234)\n" + " 0x1000001c <_start+28> asr r6, r4, #4 R6 => 0x1e0 (0x1e00 >>s 0x4)\n" + " 0x10000020 <_start+32> asr r6, r4, r0 R6 => 0x3c0 (0x1e00 >>s 0x3)\n" + " 0x10000024 <_start+36> ror r6, r4, #4 R6 => 0x1e0 (0x1e00 >>r 0x4)\n" + " 0x10000028 <_start+40> ror r6, r4, r0 R6 => 0x3c0 (0x1e00 >>r 0x3)\n" + "────────────────────────────────────────────────────────────────────────────────\n" + ) + + assert dis == expected + + +NEGATIVE_DISPONENTS = r""" +LDR r1, =msg +ADD r1, 4 +LDR r0, [r1, #-4] +nop +nop +nop +nop nop nop nop nop + + +.data +msg: + .asciz "ABCDEFGHIJKLMNOPQRSTUVWXYZ!" """ -def test_logical_shifts(qemu_assembly_run): +def test_arm_negative_disponent(qemu_assembly_run): """ - Shifts have a different underlying Capstone representation if it's an constant or a register offset. - This test ensures we handle both cases. + Negative disponents are now represented by a positive offset and a flag that indicates it should be subtracted. + This representation changed in CapstoneV6 """ - qemu_assembly_run(ARM_SHIFTS, "arm") + qemu_assembly_run(NEGATIVE_DISPONENTS, "arm") + dis = gdb.execute("context disasm", to_string=True) + dis = pwndbg.color.strip(dis) + + expected = ( + "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" + "──────────────────[ DISASM / arm / arm mode / set emulate on ]──────────────────\n" + " ► 0x10000000 <_start> ldr r1, [pc, #0x24] R1, [_start+44] => 0x11094 (msg) ◂— 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!'\n" + " 0x10000004 <_start+4> add r1, r1, #4 R1 => 0x11098 (msg+4) (0x11094 + 0x4)\n" + " 0x10000008 <_start+8> ldr r0, [r1, #-4] R0, [msg] => 0x44434241 ('ABCD')\n" + " 0x1000000c <_start+12> nop \n" + " 0x10000010 <_start+16> nop \n" + " 0x10000014 <_start+20> nop \n" + " 0x10000018 <_start+24> nop \n" + " 0x1000001c <_start+28> nop \n" + " 0x10000020 <_start+32> nop \n" + " 0x10000024 <_start+36> nop \n" + " 0x10000028 <_start+40> nop \n" + "────────────────────────────────────────────────────────────────────────────────\n" + ) + + assert dis == expected + + +NEGATIVE_INDEX_REGISTER = r""" +LDR R1, =msg +ADD r1, 4 +ADD r2, r1, 4 + +MOV R3, #4 +MOV R4, #2 + +LDR R5, [R1, -R3] +LDR R6, [R2, -R4, LSL #2] + +nop +nop +nop +nop +nop +nop +nop + +.data +msg: + .asciz "ABCDEFGHIJKLMNOPQRSTUVWXYZ!" +""" + + +def test_arm_negative_index_register(qemu_assembly_run): + """ + In the second LDR instruction above, the index register, R2, is negated. + + This has a specific encoding that has changed in Capstone in the past, so we test to make sure we are handling it correctly. + """ + + qemu_assembly_run(NEGATIVE_INDEX_REGISTER, "arm") dis = gdb.execute("context disasm", to_string=True) dis = pwndbg.color.strip(dis) expected = ( "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "──────────────────[ DISASM / arm / arm mode / set emulate on ]──────────────────\n" - " ► 0x10000000 <_start> mov r0, #0xf000 R0 => 0xf000\n" - " 0x10000004 <_start+4> lsr r1, r0, #4 R1 => 0xf00 (0xf000 >> 0x4)\n" - " 0x10000008 <_start+8> mov r2, #2 R2 => 2\n" - " 0x1000000c <_start+12> lsr r3, r0, r2 R3 => 0x3c00 (0xf000 >> 0x2)\n" - " 0x10000010 <_start+16> movw r4, #0x1234 R4 => 0x1234\n" - " 0x10000014 <_start+20> lsl r5, r4, #8 R5 => 0x123400 (0x1234 << 0x8)\n" - " 0x10000018 <_start+24> lsl r6, r4, r2 R6 => 0x48d0 (0x1234 << 0x2)\n" + " ► 0x10000000 <_start> ldr r1, [pc, #0x30] R1, [_start+56] => 0x11094 (msg) ◂— 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!'\n" + " 0x10000004 <_start+4> add r1, r1, #4 R1 => 0x11098 (msg+4) (0x11094 + 0x4)\n" + " 0x10000008 <_start+8> add r2, r1, #4 R2 => 0x1109c (msg+8) (0x11098 + 0x4)\n" + " 0x1000000c <_start+12> mov r3, #4 R3 => 4\n" + " 0x10000010 <_start+16> mov r4, #2 R4 => 2\n" + " 0x10000014 <_start+20> ldr r5, [r1, -r3] R5, [msg] => 0x44434241 ('ABCD')\n" + " 0x10000018 <_start+24> ldr r6, [r2, -r4, lsl #2] R6, [msg] => 0x44434241 ('ABCD')\n" " 0x1000001c <_start+28> nop \n" " 0x10000020 <_start+32> nop \n" " 0x10000024 <_start+36> nop \n" diff --git a/tests/qemu-tests/tests/user/test_mips.py b/tests/qemu-tests/tests/user/test_mips.py index 026ca87dd..91df28bc6 100644 --- a/tests/qemu-tests/tests/user/test_mips.py +++ b/tests/qemu-tests/tests/user/test_mips.py @@ -100,10 +100,10 @@ def test_mips32_bnez_instruction(qemu_assembly_run): """ qemu_assembly_run(MIPS_BNEZ, "mips") - dis = gdb.execute("context disasm", to_string=True) - dis = pwndbg.color.strip(dis) + dis_1 = gdb.execute("context disasm", to_string=True) + dis_1 = pwndbg.color.strip(dis_1) - expected = ( + expected_1 = ( "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "───────────────────────[ DISASM / mips / set emulate on ]───────────────────────\n" " ► 0x10000000 <_start> addiu $t0, $zero, 0xa T0 => 10 (0x0 + 0xa)\n" @@ -120,15 +120,15 @@ def test_mips32_bnez_instruction(qemu_assembly_run): "────────────────────────────────────────────────────────────────────────────────\n" ) - assert dis == expected + assert dis_1 == expected_1 gdb.execute("set emulate off") - no_emulate_dis = gdb.execute("context disasm", to_string=True) - no_emulate_dis = pwndbg.color.strip(no_emulate_dis) + no_emulate_dis_2 = gdb.execute("context disasm", to_string=True) + no_emulate_dis_2 = pwndbg.color.strip(no_emulate_dis_2) # Without emulation, we cannot determine whether or not we take the branch yet # So the disasm output should just contain the instructions linearly in memory - expected = ( + expected_2 = ( "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "──────────────────────[ DISASM / mips / set emulate off ]───────────────────────\n" " ► 0x10000000 <_start> addiu $t0, $zero, 0xa T0 => 0x0 + 0xa\n" @@ -145,15 +145,15 @@ def test_mips32_bnez_instruction(qemu_assembly_run): "────────────────────────────────────────────────────────────────────────────────\n" ) - assert no_emulate_dis == expected + assert no_emulate_dis_2 == expected_2 # Once we are on the instruction, the branch target should be manually determined gdb.execute("si") - no_emulate_dis = gdb.execute("context disasm", to_string=True) - no_emulate_dis = pwndbg.color.strip(no_emulate_dis) + no_emulate_dis_3 = gdb.execute("context disasm", to_string=True) + no_emulate_dis_3 = pwndbg.color.strip(no_emulate_dis_3) - expected = ( + expected_3 = ( "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "──────────────────────[ DISASM / mips / set emulate off ]───────────────────────\n" " 0x10000000 <_start> addiu $t0, $zero, 0xa T0 => 0x0 + 0xa\n" @@ -170,7 +170,7 @@ def test_mips32_bnez_instruction(qemu_assembly_run): "────────────────────────────────────────────────────────────────────────────────\n" ) - assert no_emulate_dis == expected + assert no_emulate_dis_3 == expected_3 MIPS_CALL = f""" @@ -282,13 +282,13 @@ def test_mips32_store_instruction(qemu_assembly_run): " 0x10000004 <_start+4> ori $t0, $t0, 0x5678 T0 => 0x12345678 (0x12340000 | 0x5678)\n" " 0x10000008 <_start+8> lui $s0, 0x40 S0 => 0x400000\n" " 0x1000000c <_start+12> addiu $s0, $s0, 0x1130 S0 => 0x401130 (value1) (0x400000 + 0x1130)\n" - " 0x10000010 <_start+16> sw $t0, ($s0) [value1] <= 0x12345678\n" + " 0x10000010 <_start+16> sw $t0, 0($s0) [value1] <= 0x12345678\n" " 0x10000014 <_start+20> lui $s1, 0x40 S1 => 0x400000\n" " 0x10000018 <_start+24> addiu $s1, $s1, 0x1134 S1 => 0x401134 (value2) (0x400000 + 0x1134)\n" - " 0x1000001c <_start+28> sh $t0, ($s1) [value2] <= 0x5678\n" + " 0x1000001c <_start+28> sh $t0, 0($s1) [value2] <= 0x5678\n" " 0x10000020 <_start+32> lui $s2, 0x40 S2 => 0x400000\n" " 0x10000024 <_start+36> addiu $s2, $s2, 0x1136 S2 => 0x401136 (value3) (0x400000 + 0x1136)\n" - " 0x10000028 <_start+40> sb $t0, ($s2) [value3] <= 0x78\n" + " 0x10000028 <_start+40> sb $t0, 0($s2) [value3] <= 0x78\n" "────────────────────────────────────────────────────────────────────────────────\n" ) @@ -347,12 +347,12 @@ def test_mips32_load_instructions(qemu_assembly_run): expected = ( "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n" "───────────────────────[ DISASM / mips / set emulate on ]───────────────────────\n" - " ► 0x10000038 lw $t1, ($s0) T1, [value1] => 0xffffffff\n" - " 0x1000003c lhu $t2, ($s1) T2, [value2] => 0xffff\n" - " 0x10000040 lbu $t3, ($s2) T3, [value3] => 0xff\n" - " 0x10000044 lw $t4, ($s0) T4, [value1] => 0xffffffff\n" - " 0x10000048 lh $t5, ($s1) T5, [value2] => 0xffffffff\n" - " 0x1000004c lb $t6, ($s2) T6, [value3] => 0xffffffff\n" + " ► 0x10000038 lw $t1, 0($s0) T1, [value1] => 0xffffffff\n" + " 0x1000003c lhu $t2, 0($s1) T2, [value2] => 0xffff\n" + " 0x10000040 lbu $t3, 0($s2) T3, [value3] => 0xff\n" + " 0x10000044 lw $t4, 0($s0) T4, [value1] => 0xffffffff\n" + " 0x10000048 lh $t5, 0($s1) T5, [value2] => 0xffffffff\n" + " 0x1000004c lb $t6, 0($s2) T6, [value3] => 0xffffffff\n" "\n" "\n" "\n" diff --git a/tests/qemu-tests/tests/user/test_riscv64.py b/tests/qemu-tests/tests/user/test_riscv64.py index aa012c616..239e1d19b 100644 --- a/tests/qemu-tests/tests/user/test_riscv64.py +++ b/tests/qemu-tests/tests/user/test_riscv64.py @@ -103,13 +103,9 @@ data: def test_riscv64_compressed_loads(qemu_assembly_run): """ - RISC-V support in Capstone is fairly new, and as of Capstone 5, there are some inconsistenties, and the underlying - metadata of the instructions can change between versions. + RISC-V support in Capstone is fairly new, and the underlying metadata of the instructions can change between versions. - Currently, compressed load and stores operations have a memory operand representation that is subject to change in Capstone v6. - - If this crashes, it is we likely need to update the parser for compressed memory operands. - - Link: https://github.com/capstone-engine/capstone/issues/2351 + This test ensures that we properly handle compressed load and stores instruction, as the data representation changed between v5 and v6. """ qemu_assembly_run(RISCV64_COMPRESSED_LOAD_STORE, "riscv64") diff --git a/tests/unit-tests/test_bit_math.py b/tests/unit-tests/test_bit_math.py index ecda639a9..389efd417 100644 --- a/tests/unit-tests/test_bit_math.py +++ b/tests/unit-tests/test_bit_math.py @@ -15,6 +15,9 @@ def test_to_signed(): assert bit_math.to_signed(0xFFFF_FFFF, 32) == -1 assert bit_math.to_signed(0x8000_0000, 32) == -(2**31) + # Ignore higher bits + assert bit_math.to_signed(0xF_FF, 8) == -1 + def test_lsl(): assert bit_math.logical_shift_left(0b1000_0000, 1, 8) == 0 diff --git a/uv.lock b/uv.lock index cad2003d1..b432bc352 100644 --- a/uv.lock +++ b/uv.lock @@ -72,20 +72,46 @@ filecache = [ [[package]] name = "capstone" -version = "5.0.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a4/23/759da7f13c2ce29ffe90ccb45eb61ffcd310b436bfb489f3dbd11fba8776/capstone-5.0.5.tar.gz", hash = "sha256:32346f6019d5351adaaf584ffc60c1e40db6b47d1d049eb924f903eb2b073e87", size = 2944153 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/4a/4eb99fa4daf4d3035cfd52790b2ae67810e5663f71797ee5025977464c13/capstone-5.0.5-py3-none-macosx_10_9_universal2.whl", hash = "sha256:24db89d74b571659fe6212e756795cd5d394378c50e19e41dbcfb6c087c2f87d", size = 2177052 }, - { url = "https://files.pythonhosted.org/packages/2f/cc/c7e6a8fef919b28fbfe8a5c8955b40bbe998f84f791699246d7e70f5cdb6/capstone-5.0.5-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:15f4b85df176999bbf7eb3f53f0cf2cee728254600c1be21442e2581189309e9", size = 1180188 }, - { url = "https://files.pythonhosted.org/packages/1a/5b/e7ee66ebec55da1e0ea841de62ba5e971fb8fe7b12a49215fdaa15d1b9f9/capstone-5.0.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5416621ac2d243d89b788f1309b143ea1f400da3eb5c47c6a87f1add99732a83", size = 1192762 }, - { url = "https://files.pythonhosted.org/packages/fb/42/a68064f021d55be5079c21da963f3347d0979476da268b61cb80a7527229/capstone-5.0.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef47da78f44de1cdff1519b360186681fca0097e92046a7d7203d56364f99da", size = 1458295 }, - { url = "https://files.pythonhosted.org/packages/9b/b4/05263fe61b2a8d8211783f911283d1bd296d74bbbd6e9a1e48f6dadb76c9/capstone-5.0.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:754968f057d9e5d9c383f2918a1d56d455bfb274bbf307f219180b16e6d5aaeb", size = 1481602 }, - { url = "https://files.pythonhosted.org/packages/67/7b/4c21ee2001b110182326dd10b30f1a6f2c9227e28e9714145e473d397de8/capstone-5.0.5-py3-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:933797f7e2a257a77c3a699316deea92efa120a10d41e22725a96fc82f0a769e", size = 1480729 }, - { url = "https://files.pythonhosted.org/packages/45/8b/9ad9a8c337d2276b25d71aabc6096243a57246bb9f16431f52c25fa70597/capstone-5.0.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50b646f0c56b0cac5c993dde08b5e5eacf8b1f66031ec8d60154eae6e3c0645e", size = 1458038 }, - { url = "https://files.pythonhosted.org/packages/fd/a2/9fe3a14bb6c397d072ca8332b93b7b63c57bb9c0361c8fa3a8260efd5d90/capstone-5.0.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd35b666739d7b79066fc69fd0c145d5ceb6a4131df3db1225ec6dcfa3fe322f", size = 1484181 }, - { url = "https://files.pythonhosted.org/packages/65/08/6a65de9ab23ce726b680c0e75a064406d4bd2738640fc47a2a6aa679f1d0/capstone-5.0.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:89dac65a1c84670ee30ccaf2ae688c4b27ad514d9dc8738a9826579051e29ecb", size = 1485340 }, - { url = "https://files.pythonhosted.org/packages/fd/77/be4185108d31137e8bb536d6dc3a9222dbc15bd1e84787a3bdec9108fcb9/capstone-5.0.5-py3-none-win_amd64.whl", hash = "sha256:a03b6b42b33bb0739b2436a555e699ac91cd1d1891134269b04e359b607e50e8", size = 1271239 }, +version = "6.0.0a4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/e8/ed930d72d01d84705b85f49d28285eb0667af793d31ac29ca296f5b67b5f/capstone-6.0.0a4.tar.gz", hash = "sha256:62ca96f952e8d38913cf1e1edafbfc6cba533031b45c6dcf3854e8a68605e965", size = 4799040 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/94/a20c93bf0692f073545689a09febc3332ce99255212aa650ee7f953147c7/capstone-6.0.0a4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c043156a9b36208e73e3c4f700a97732bebc6be92ea391f78f83e9ed5b4e41c8", size = 3489947 }, + { url = "https://files.pythonhosted.org/packages/c6/0f/641267fb861383b8a1656de33b57337d5dd1723c0d595715ad6ac203e6de/capstone-6.0.0a4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99fefe28c7da22e817b13dfc59a06a7cd79ab12a4a2edd5d82114298e7f2109c", size = 1895902 }, + { url = "https://files.pythonhosted.org/packages/15/a2/1b685deb8e1cff3ffeb3e9122cf016ba71514bf63a9354bf76fd1bb5af4b/capstone-6.0.0a4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48bbfbcc4f5f22c2f07081db2bc1ad974d5c03fdc29d690d37e7e99047714b41", size = 1961921 }, + { url = "https://files.pythonhosted.org/packages/03/2f/aecf2347dffd9930ef0f771166297088900e5413205dd8e72bf5f1e780b7/capstone-6.0.0a4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d8dddd9dc1d1cedef8186f02c0d2156f2c734e5ff40e7c0c7572df7f4be26b", size = 2279893 }, + { url = "https://files.pythonhosted.org/packages/42/ab/2e6c55b7166ce162dd8d764878caae2b0286beaf0a6a022b550f81e449f5/capstone-6.0.0a4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8351afe611a8ec50df753fe843be3abf740f186f5a535fcc6cf40392771a2f02", size = 2321918 }, + { url = "https://files.pythonhosted.org/packages/33/b0/17e6c6d68ffe33fe87593f50e5788749d3b5fed82f1e3c0685f73106d56c/capstone-6.0.0a4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d40ea5ca2404bc71815b73a5ef743c5240b484da28bd195d1f19526e66841e28", size = 2289663 }, + { url = "https://files.pythonhosted.org/packages/51/d1/618cd90822204c7b86edeb857c8beae27d24bb3c823a8cd5ff37b3523456/capstone-6.0.0a4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:999b9a85adcd389c0b71fec2e340f5445c392cf2826aa30b96793a11c24e90e0", size = 2312542 }, + { url = "https://files.pythonhosted.org/packages/fd/ce/fdf624f12da45dc9b314175d3454c49f33df7c6c6a1c81f14a3d7e9155be/capstone-6.0.0a4-cp310-cp310-win_amd64.whl", hash = "sha256:3a31629da8b276036badc7ac1cd9978d7de4205a0175306b48e74e1337cb4ac1", size = 2252118 }, + { url = "https://files.pythonhosted.org/packages/ab/11/1e820781491b60958a4393d2bfb4705374ebc5d2c034f8e7b7695811c181/capstone-6.0.0a4-cp310-cp310-win_arm64.whl", hash = "sha256:c95f411183ef714bf341073d6093642c6c00a0377b9e9fc581a3b77d9d9e0fe1", size = 2210983 }, + { url = "https://files.pythonhosted.org/packages/52/ea/55e7e726fc5692842fa0288c1be2f7397edd83c71aa3545176a6634728ad/capstone-6.0.0a4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:624db65efa6b6c76e561b8b678ca47b1bc98fdba8c0841d884ac23dad1df21e6", size = 3489946 }, + { url = "https://files.pythonhosted.org/packages/7a/68/0fe8b7472f7d0705ff733b73a627de40539e13fc24fabe0925d5168d5103/capstone-6.0.0a4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b907666508d244b947bb598334954e61f0749c9d7619d2ff5d189fd50facd55a", size = 1895902 }, + { url = "https://files.pythonhosted.org/packages/2a/58/361dd9c17aa6fe0c03a468c24482471ac4199860ee2cd77189a845e2603b/capstone-6.0.0a4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c132a9f5d024fdb5a45db2e78ace7bb8bdb92661a61262921693aa94234c22f", size = 1961922 }, + { url = "https://files.pythonhosted.org/packages/0d/2a/3d63fe56098e7de6ee8fb5ba538a340bc5c62c204b15880af1a786750932/capstone-6.0.0a4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4d5ef6beb87338e637b6d8b04adef463a82f2ea8b99926e9f08ed38df8a19bd", size = 2279891 }, + { url = "https://files.pythonhosted.org/packages/7f/17/b23a035203fe3f1f50951078fd7ab41d41581751badca21d4c321b05d291/capstone-6.0.0a4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac4f8ac1b9ca4337adffe12a2a5c2e82f546f864e3d102207269991e8a047691", size = 2321919 }, + { url = "https://files.pythonhosted.org/packages/de/e1/a20c3a0d85450ef7c9ce2bce8af12c52853869adf2a316ca589f9954b34e/capstone-6.0.0a4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fea6dde6f356a9d3eacda72e93107146265db3e02b06c9e3d82c6d13ce90310c", size = 2289664 }, + { url = "https://files.pythonhosted.org/packages/0c/0b/fa5d56d7095a9853b3702981e284e31fd3326951ab7315a8c6fe98dd87d9/capstone-6.0.0a4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4dd5ede5c2ac679ed8d1e18ef72cf7bbaceaecbf27eb41a76556bfd1331d418", size = 2312539 }, + { url = "https://files.pythonhosted.org/packages/bc/b8/b836911c854ac93b4cdaef4f938ae9fd6d3e8df3960e40adb1d1e8a2ec18/capstone-6.0.0a4-cp311-cp311-win_amd64.whl", hash = "sha256:9f1cb304f7330ecfad18d94fe64169ca2d98c4188574b2e8bfd9515ae9a9c2b0", size = 2252118 }, + { url = "https://files.pythonhosted.org/packages/c3/81/fccc557cd0ff316a6fcc7c7e97cf3a05bcd07357372c14aa3542abf5c79a/capstone-6.0.0a4-cp311-cp311-win_arm64.whl", hash = "sha256:b23a9200fcf878879925fc674311d0431f9de5d07d8a442c353a62b6f3082b25", size = 2210984 }, + { url = "https://files.pythonhosted.org/packages/b0/47/ff0a5fd95060aaf450490a1145e7955fad029be0a063fe866b0306bb79d9/capstone-6.0.0a4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e3a9350abd4433f7a50a43dec9845f58709c14e7c4644d7dcd540a66b985ecc6", size = 3489948 }, + { url = "https://files.pythonhosted.org/packages/c9/54/2b0b473b58cf7fb181c05cb9b1b9f47fd1c2457029d6e5591a127d484c23/capstone-6.0.0a4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a39e8421a61e8e6324a3cdb18f9d599be4c1fdf9ca2d38bf3c2cc513a4331e6b", size = 1895904 }, + { url = "https://files.pythonhosted.org/packages/3a/51/310375f014eeb923ccde94ad0b3809cc2548029f558b7830b8a7766e1389/capstone-6.0.0a4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a8d72f398ead1a51115350b7e57415f7dbd118cecbb9193bc663c481a10baf0", size = 1961922 }, + { url = "https://files.pythonhosted.org/packages/ba/a0/fff69eaba09fd4f1c5fe95a4001bcbbd777dc451e245313dd16e1c7db1ae/capstone-6.0.0a4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:513e392bbaf5e9310a7409eba0699077767b44a0dd41ce70c30fd2132d431ce5", size = 2279894 }, + { url = "https://files.pythonhosted.org/packages/c0/ad/efaa0c8704548da3fa0f8801e4b8d9288e65c6e05fc0e3b861b302676664/capstone-6.0.0a4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e700366f05c291289c994329e00f6ba4899503d02ca77c768734f555841cb7", size = 2321918 }, + { url = "https://files.pythonhosted.org/packages/5c/7d/a2715b78e0342a18f5778e5a92eafd3a5c25494558054c39a89c9c92dcd5/capstone-6.0.0a4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3bc925fa3ad432010132eb8fdc4688cbc7e47aa3f5e2952307f414f25de486fd", size = 2289667 }, + { url = "https://files.pythonhosted.org/packages/7c/3c/482adad4a45f23fe183bbc66870e816101c208b8365bad5c81f131893ef8/capstone-6.0.0a4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:643e77dc7ae9ee5e8333303b66a7e783162d0d05fe7f2deeba094f5f2cfd68d1", size = 2312538 }, + { url = "https://files.pythonhosted.org/packages/d1/95/0eaf2857319fe44c1fde718a3f3bf0e07907951102863469aa2d16e6e71d/capstone-6.0.0a4-cp312-cp312-win_amd64.whl", hash = "sha256:bfb634ac0085a98031b42aaed64d12cdc46fc383a43cd224ef239bfdee22c9f0", size = 2252117 }, + { url = "https://files.pythonhosted.org/packages/db/32/d6caccaa374070903e520742d47a2e367f93aafd030e52bab753918df011/capstone-6.0.0a4-cp312-cp312-win_arm64.whl", hash = "sha256:9a88f020e01e1f451028cd2db609213ac5951c9c473457740fb4e368137464c3", size = 2210985 }, + { url = "https://files.pythonhosted.org/packages/ad/9d/2b84d5d465a44c0c4357279885f4c6de4feff8aef6320744a2650f9612c1/capstone-6.0.0a4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5db66c3c273c82f70ae68751d4b9c6241f7327bbeed9ee55ce4116ef5fcdbc6e", size = 3489946 }, + { url = "https://files.pythonhosted.org/packages/35/81/9366d6de1d7516e759e3110da1118c430db2c1ed33f27f9c67fad14d30cb/capstone-6.0.0a4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4041ff19810d5a81582f8e33c0e39c6db0704945188c21945234c92e0900c465", size = 1895904 }, + { url = "https://files.pythonhosted.org/packages/86/2a/e16249fd0ca2464ad6696d826598015f3e0ee1b8f9deec8b7076f34a8b85/capstone-6.0.0a4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:32fce2e387182a65c2a3bcf4d1e13a5410c45f56f73247777ba0bdea0b365b50", size = 1961922 }, + { url = "https://files.pythonhosted.org/packages/c2/f6/e2c85f0e37321bcc6156bc2ac91dbb9ce403d0832c6c4fca65122d9f1971/capstone-6.0.0a4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805b5a908525d17bb92922316bbeeaba571758f283085bd7d98219f4083761c4", size = 2279893 }, + { url = "https://files.pythonhosted.org/packages/12/13/0f8f7b88611a430e576142976a05d4a2581fbc64617cc3b2070b668ce1b0/capstone-6.0.0a4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f43a8d9d0c483a6b1540829acf47488cd1d2e0de7b5e6a92084234f37461932", size = 2321918 }, + { url = "https://files.pythonhosted.org/packages/00/25/b253ae59c03e5bc33c3cad30137216f0d9a482a80e68eb77492d1a6a2903/capstone-6.0.0a4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d8ebbc57e5437c5345d101c1076d5faff50e21b4c3c1caa0738c6372deaeaf2", size = 2289664 }, + { url = "https://files.pythonhosted.org/packages/b3/7f/2d8a702508d0b19e572bcd10a09a93b0c73caaecea106183b00b9cdcbb1b/capstone-6.0.0a4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a04bb61d769141afd7639b121ff47c9e0b20362f33780083c2f05f3446ee5b58", size = 2312542 }, + { url = "https://files.pythonhosted.org/packages/da/58/fc08a41ce1483b83434cbe9e739bfa1f765bc8a2e27ff4c651c3e4a63bc4/capstone-6.0.0a4-cp313-cp313-win_amd64.whl", hash = "sha256:8c38b86e7b4a9752643d470676ba8996bbdd8e2e7a22eca52485292c439ff93f", size = 2252117 }, + { url = "https://files.pythonhosted.org/packages/13/6d/e7cc611c59f33ab0114827e2c10a2f815a498e669ecaca0235f680c30e54/capstone-6.0.0a4-cp313-cp313-win_arm64.whl", hash = "sha256:8864fac943ac1eb18e051ce20f99ff614adb707ef606df591c3cfaf414dff99d", size = 2210983 }, ] [[package]] @@ -1226,7 +1252,7 @@ tests = [ [package.metadata] requires-dist = [ - { name = "capstone", specifier = ">=5.0.3,<6" }, + { name = "capstone", specifier = "==6.0.0a4" }, { name = "gnureadline", marker = "sys_platform != 'win32' and extra == 'lldb'", specifier = ">=8.2.10,<9" }, { name = "ipython", specifier = ">=8.27.0,<9" }, { name = "pt", git = "https://github.com/martinradev/gdb-pt-dump?rev=50227bda0b6332e94027f811a15879588de6d5cb" },