feat: add riscv qdb support

Signed-off-by: Jvle <[email protected]>
This commit is contained in:
Jvle
2026-08-04 16:40:00 +08:00
committed by Keke Ming
parent 499218bd1a
commit b69128fee9
11 changed files with 287 additions and 6 deletions
+1
View File
@@ -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
+48
View File
@@ -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
@@ -7,10 +7,12 @@ from .branch_predictor import BranchPredictor
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'
]
@@ -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
+4 -2
View File
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, List, Tuple
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]), {})
+1
View File
@@ -7,3 +7,4 @@ from .render import ContextRender
from .render_intel import ContextRenderX86, ContextRenderX64
from .render_mips import ContextRenderMIPS
from .render_arm import ContextRenderARM, ContextRenderCORTEX_M
from .render_riscv import ContextRenderRISCV, ContextRenderRISCV64
@@ -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.
"""
+12 -3
View File
@@ -16,7 +16,9 @@ from .render import (
ContextRenderX64,
ContextRenderARM,
ContextRenderCORTEX_M,
ContextRenderMIPS
ContextRenderMIPS,
ContextRenderRISCV,
ContextRenderRISCV64,
)
from .branch_predictor import (
@@ -26,6 +28,8 @@ from .branch_predictor import (
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]
+6
View File
@@ -0,0 +1,6 @@
x/8xw $sp
x/8i $pc
s 1
info snapshot
p
q
+6
View File
@@ -0,0 +1,6 @@
x/8xw $sp
x/8i $pc
s 1
info snapshot
p
q
+14
View File
@@ -53,6 +53,20 @@ class DebuggerTest(unittest.TestCase):
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()