diff --git a/qiling/debugger/qdb/arch/__init__.py b/qiling/debugger/qdb/arch/__init__.py index 12ed30d11..3c08d7301 100644 --- a/qiling/debugger/qdb/arch/__init__.py +++ b/qiling/debugger/qdb/arch/__init__.py @@ -6,3 +6,4 @@ from .arch_arm import ArchARM, ArchCORTEX_M from .arch_intel import ArchIntel, ArchX86, ArchX64 from .arch_mips import ArchMIPS +from .arch_riscv import ArchRISCV, ArchRISCV64 diff --git a/qiling/debugger/qdb/arch/arch_riscv.py b/qiling/debugger/qdb/arch/arch_riscv.py new file mode 100644 index 000000000..a4cb987eb --- /dev/null +++ b/qiling/debugger/qdb/arch/arch_riscv.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from .arch import Arch + + +class ArchRISCV(Arch): + def __init__(self) -> None: + regs = ( + 'zero', 'ra', 'sp', 'gp', 'tp', + 't0', 't1', 't2', 's0', 's1', + 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', + 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', + 't3', 't4', 't5', 't6', 'pc' + ) + + aliases = { + 's0': 'fp' + } + + super().__init__(regs, aliases, 4, 4) + + def unalias(self, name: str) -> str: + if name.startswith('x') and name[1:].isdigit(): + idx = int(name[1:]) + + xregs = ( + 'zero', 'ra', 'sp', 'gp', 'tp', + 't0', 't1', 't2', 's0', 's1', + 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', + 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', + 't3', 't4', 't5', 't6' + ) + + if idx < len(xregs): + return xregs[idx] + + return super().unalias(name) + + +class ArchRISCV64(ArchRISCV): + def __init__(self) -> None: + super().__init__() + self._asize = 8 diff --git a/qiling/debugger/qdb/branch_predictor/__init__.py b/qiling/debugger/qdb/branch_predictor/__init__.py index 670f65347..952bb44a0 100644 --- a/qiling/debugger/qdb/branch_predictor/__init__.py +++ b/qiling/debugger/qdb/branch_predictor/__init__.py @@ -7,10 +7,12 @@ from .branch_predictor_arm import BranchPredictorARM, BranchPredictorCORTEX_M from .branch_predictor_intel import BranchPredictorX86, BranchPredictorX64 from .branch_predictor_mips import BranchPredictorMIPS +from .branch_predictor_riscv import BranchPredictorRISCV, BranchPredictorRISCV64 __all__ = [ 'BranchPredictor', 'BranchPredictorARM', 'BranchPredictorCORTEX_M', 'BranchPredictorX86', 'BranchPredictorX64', - 'BranchPredictorMIPS' + 'BranchPredictorMIPS', + 'BranchPredictorRISCV', 'BranchPredictorRISCV64' ] diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_riscv.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_riscv.py new file mode 100644 index 000000000..2f71ec52a --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_riscv.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from __future__ import annotations + +from typing import Callable, Dict, List, Optional, Set + +from capstone import CS_OP_IMM, CS_OP_MEM, CS_OP_REG + +from .branch_predictor import BranchPredictor, Prophecy +from ..arch import ArchRISCV, ArchRISCV64 +from ..misc import InvalidInsn + + +class BranchPredictorRISCV(BranchPredictor, ArchRISCV): + """Branch Predictor for RISC-V 32-bit. + """ + + stop = 'ebreak' + xlen = 32 + supports_c_jal = True + + def _unconditional_branches(self) -> Set[str]: + branches = {'j', 'jal', 'jalr', 'jr', 'ret'} + + if self.supports_c_jal: + branches.add('c.jal') + + return branches + + def _normalize_mnemonic(self, mnemonic: str) -> str: + return (mnemonic or '').lower() + + def _signed(self, val: int) -> int: + mask = (1 << self.xlen) - 1 + sign = 1 << (self.xlen - 1) + + val &= mask + return (val ^ sign) - sign + + def _unsigned(self, val: int) -> int: + return val & ((1 << self.xlen) - 1) + + def predict(self) -> Prophecy: + insn = self.disasm(self.cur_addr, True) + + going = False + where = 0 + + if isinstance(insn, InvalidInsn): + return Prophecy(going, where) + + mnemonic = self._normalize_mnemonic(insn.mnemonic) + base = mnemonic[2:] if mnemonic.startswith('c.') else mnemonic + operands: List[object] = list(insn.operands) + + conditional: Dict[str, Callable[..., bool]] = { + 'beq' : lambda a, b: a == b, + 'bne' : lambda a, b: a != b, + 'blt' : lambda a, b: a < b, + 'bge' : lambda a, b: a >= b, + 'bltu': lambda a, b: a < b, + 'bgeu': lambda a, b: a >= b, + 'beqz': lambda a: a == 0, + 'bnez': lambda a: a != 0, + 'bgez': lambda a: a >= 0, + 'bltz': lambda a: a < 0, + 'bgtz': lambda a: a > 0, + 'blez': lambda a: a <= 0, + } + + def __read_reg(reg: int) -> Optional[int]: + name = insn.reg_name(reg) + + return name and self.read_reg(self.unalias(name)) + + def __parse_op(op: object) -> Optional[int]: + if getattr(op, 'type', None) == CS_OP_REG: + return __read_reg(op.reg) + + if getattr(op, 'type', None) == CS_OP_IMM: + return op.imm + + if getattr(op, 'type', None) == CS_OP_MEM: + mem = op.mem + base_reg = __read_reg(mem.base) or 0 + index = __read_reg(mem.index) or 0 + return base_reg + index * mem.scale + mem.disp + + return None + + def __direct_target() -> Optional[int]: + imms = [op for op in operands if getattr(op, 'type', None) == CS_OP_IMM] + + if not imms: + return None + + return self.cur_addr + imms[-1].imm + + def __indirect_target() -> Optional[int]: + memop = next((op for op in operands if getattr(op, 'type', None) == CS_OP_MEM), None) + if memop is not None: + return __parse_op(memop) + + regs = [op for op in operands if getattr(op, 'type', None) == CS_OP_REG] + imms = [op for op in operands if getattr(op, 'type', None) == CS_OP_IMM] + + if regs and imms: + return (__parse_op(regs[-1]) or 0) + imms[-1].imm + + if regs: + return __parse_op(regs[-1]) + + return None + + unconditional = self._unconditional_branches() + is_unconditional = mnemonic in unconditional or base in unconditional + + if mnemonic == 'c.jal' and not self.supports_c_jal: + is_unconditional = False + + if is_unconditional: + going = True + + if base == 'ret' and not operands: + where = self.read_reg('ra') + elif base in {'jalr', 'jr', 'ret'}: + where = __indirect_target() or 0 + else: + where = __direct_target() or 0 + + if base in {'jalr', 'jr', 'ret'}: + where &= ~1 + + elif base in conditional: + predicate = conditional[base] + + if base in {'beqz', 'bnez', 'bgez', 'bltz', 'bgtz', 'blez'}: + reg = __parse_op(operands[0]) if operands else None + going = reg is not None and predicate(self._signed(reg)) + else: + lhs = __parse_op(operands[0]) if len(operands) > 0 else None + rhs = __parse_op(operands[1]) if len(operands) > 1 else None + + if base in {'blt', 'bge'}: + lhs = None if lhs is None else self._signed(lhs) + rhs = None if rhs is None else self._signed(rhs) + + if base in {'bltu', 'bgeu'}: + lhs = None if lhs is None else self._unsigned(lhs) + rhs = None if rhs is None else self._unsigned(rhs) + + going = lhs is not None and rhs is not None and predicate(lhs, rhs) + + if going: + where = __direct_target() or 0 + + return Prophecy(going, where) + + +class BranchPredictorRISCV64(BranchPredictorRISCV, ArchRISCV64): + """Branch Predictor for RISC-V 64-bit. + """ + + xlen = 64 + supports_c_jal = False diff --git a/qiling/debugger/qdb/helper.py b/qiling/debugger/qdb/helper.py index fd6c05bf3..6245c806e 100644 --- a/qiling/debugger/qdb/helper.py +++ b/qiling/debugger/qdb/helper.py @@ -11,7 +11,7 @@ from qiling.const import QL_ARCH from .context import Context -from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86, ArchX64 +from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchRISCV, ArchRISCV64, ArchX86, ArchX64 if TYPE_CHECKING: @@ -26,7 +26,9 @@ def setup_command_helper(ql: Qiling): QL_ARCH.X8664: ArchX64, QL_ARCH.MIPS: ArchMIPS, QL_ARCH.ARM: ArchARM, - QL_ARCH.CORTEX_M: ArchCORTEX_M + QL_ARCH.CORTEX_M: ArchCORTEX_M, + QL_ARCH.RISCV: ArchRISCV, + QL_ARCH.RISCV64: ArchRISCV64, } ret = type('CommandHelper', (CommandHelper, atypes[ql.arch.type]), {}) diff --git a/qiling/debugger/qdb/render/__init__.py b/qiling/debugger/qdb/render/__init__.py index 0b7e61807..17e430084 100644 --- a/qiling/debugger/qdb/render/__init__.py +++ b/qiling/debugger/qdb/render/__init__.py @@ -7,3 +7,4 @@ from .render_intel import ContextRenderX86, ContextRenderX64 from .render_mips import ContextRenderMIPS from .render_arm import ContextRenderARM, ContextRenderCORTEX_M +from .render_riscv import ContextRenderRISCV, ContextRenderRISCV64 diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index b1d62b85d..8d39008ed 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -108,6 +108,9 @@ def __render_regs_line() -> Iterator[str]: elements.clear() + if elements: + yield '\t'.join(elements) + for line in __render_regs_line(): print(line) diff --git a/qiling/debugger/qdb/render/render_riscv.py b/qiling/debugger/qdb/render/render_riscv.py new file mode 100644 index 000000000..ce95592a2 --- /dev/null +++ b/qiling/debugger/qdb/render/render_riscv.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from .render import ContextRender +from ..arch import ArchRISCV, ArchRISCV64 + + +class ContextRenderRISCV(ContextRender, ArchRISCV): + """Context renderer for RISC-V architecture. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.regs_a_row = 5 + + def print_mode_info(self) -> None: + pass + + +class ContextRenderRISCV64(ContextRenderRISCV, ArchRISCV64): + """Context renderer for RISC-V 64-bit architecture. + """ diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 03be0ba89..8da059c1f 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -16,7 +16,9 @@ ContextRenderX64, ContextRenderARM, ContextRenderCORTEX_M, - ContextRenderMIPS + ContextRenderMIPS, + ContextRenderRISCV, + ContextRenderRISCV64, ) from .branch_predictor import ( @@ -26,6 +28,8 @@ BranchPredictorARM, BranchPredictorCORTEX_M, BranchPredictorMIPS, + BranchPredictorRISCV, + BranchPredictorRISCV64, ) from .const import color, QDB_MSG @@ -111,13 +115,16 @@ def setup_branch_predictor(ql: Qiling) -> BranchPredictor: QL_ARCH.X8664: BranchPredictorX64, QL_ARCH.ARM: BranchPredictorARM, QL_ARCH.CORTEX_M: BranchPredictorCORTEX_M, - QL_ARCH.MIPS: BranchPredictorMIPS + QL_ARCH.MIPS: BranchPredictorMIPS, + QL_ARCH.RISCV: BranchPredictorRISCV, + QL_ARCH.RISCV64: BranchPredictorRISCV64, } p = preds[ql.arch.type] return p(ql) + def setup_context_render(ql: Qiling, predictor: BranchPredictor) -> ContextRender: """Setup context render according to arch. """ @@ -127,7 +134,9 @@ def setup_context_render(ql: Qiling, predictor: BranchPredictor) -> ContextRende QL_ARCH.X8664: ContextRenderX64, QL_ARCH.ARM: ContextRenderARM, QL_ARCH.CORTEX_M: ContextRenderCORTEX_M, - QL_ARCH.MIPS: ContextRenderMIPS + QL_ARCH.MIPS: ContextRenderMIPS, + QL_ARCH.RISCV: ContextRenderRISCV, + QL_ARCH.RISCV64: ContextRenderRISCV64, } r = rends[ql.arch.type] diff --git a/tests/qdb_scripts/riscv32.qdb b/tests/qdb_scripts/riscv32.qdb new file mode 100644 index 000000000..745aad556 --- /dev/null +++ b/tests/qdb_scripts/riscv32.qdb @@ -0,0 +1,6 @@ +x/8xw $sp +x/8i $pc +s 1 +info snapshot +p +q diff --git a/tests/qdb_scripts/riscv64.qdb b/tests/qdb_scripts/riscv64.qdb new file mode 100644 index 000000000..745aad556 --- /dev/null +++ b/tests/qdb_scripts/riscv64.qdb @@ -0,0 +1,6 @@ +x/8xw $sp +x/8i $pc +s 1 +info snapshot +p +q diff --git a/tests/test_qdb.py b/tests/test_qdb.py index 563dd840e..8498d6c82 100644 --- a/tests/test_qdb.py +++ b/tests/test_qdb.py @@ -53,6 +53,20 @@ def test_qdb_x86_hello(self): r'qdb_scripts/x86.qdb' ) + def test_qdb_riscv32_hello(self): + self.__test_common( + r'/bin/hello', + r'../examples/rootfs/riscv32_linux', + r'qdb_scripts/riscv32.qdb' + ) + + def test_qdb_riscv64_hello(self): + self.__test_common( + r'/bin/hello', + r'../examples/rootfs/riscv64_linux', + r'qdb_scripts/riscv64.qdb' + ) + if __name__ == '__main__': unittest.main()