Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qiling/debugger/qdb/arch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 48 additions & 0 deletions qiling/debugger/qdb/arch/arch_riscv.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion qiling/debugger/qdb/branch_predictor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
]
168 changes: 168 additions & 0 deletions qiling/debugger/qdb/branch_predictor/branch_predictor_riscv.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 4 additions & 2 deletions qiling/debugger/qdb/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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]), {})
Expand Down
1 change: 1 addition & 0 deletions qiling/debugger/qdb/render/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions qiling/debugger/qdb/render/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
24 changes: 24 additions & 0 deletions qiling/debugger/qdb/render/render_riscv.py
Original file line number Diff line number Diff line change
@@ -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.
"""
15 changes: 12 additions & 3 deletions qiling/debugger/qdb/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
ContextRenderX64,
ContextRenderARM,
ContextRenderCORTEX_M,
ContextRenderMIPS
ContextRenderMIPS,
ContextRenderRISCV,
ContextRenderRISCV64,
)

from .branch_predictor import (
Expand All @@ -26,6 +28,8 @@
BranchPredictorARM,
BranchPredictorCORTEX_M,
BranchPredictorMIPS,
BranchPredictorRISCV,
BranchPredictorRISCV64,
)

from .const import color, QDB_MSG
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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]
Expand Down
6 changes: 6 additions & 0 deletions tests/qdb_scripts/riscv32.qdb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
x/8xw $sp
x/8i $pc
s 1
info snapshot
p
q
6 changes: 6 additions & 0 deletions tests/qdb_scripts/riscv64.qdb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
x/8xw $sp
x/8i $pc
s 1
info snapshot
p
q
14 changes: 14 additions & 0 deletions tests/test_qdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()