Skip to content

Commit

Permalink
Improve RISCV support
Browse files Browse the repository at this point in the history
This is a resurrection of pwndbg#829

Co-authored-by: Tobias Faller <faller@endiio.com>
  • Loading branch information
peace-maker and TobiasFaller committed Jun 26, 2023
1 parent 8d0ccbc commit 45f6ee7
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 8 deletions.
1 change: 1 addition & 0 deletions pwndbg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import pwndbg.disasm.jump
import pwndbg.disasm.mips
import pwndbg.disasm.ppc
import pwndbg.disasm.riscv
import pwndbg.disasm.sparc
import pwndbg.disasm.x86
import pwndbg.heap
Expand Down
3 changes: 3 additions & 0 deletions pwndbg/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ def get(instruction):
if not target:
return []

if pwndbg.gdblib.arch.current in ["rv32", "rv64"]:
target += instruction.address

name = pwndbg.gdblib.symbol.get(target)
if not name:
return []
Expand Down
3 changes: 2 additions & 1 deletion pwndbg/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"mips": mips,
"x86-64": amd64,
"aarch64": aarch64,
"riscv:rv64": riscv64,
"rv32": riscv64,
"rv64": riscv64,
}


Expand Down
9 changes: 9 additions & 0 deletions pwndbg/disasm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"powerpc": CS_ARCH_PPC,
"mips": CS_ARCH_MIPS,
"sparc": CS_ARCH_SPARC,
"rv32": CS_ARCH_RISCV,
"rv64": CS_ARCH_RISCV,
}

CapstoneEndian = {
Expand All @@ -56,6 +58,8 @@
"x86-64": 16,
"i8086": 16,
"mips": 8,
"rv32": 22,
"rv64": 22,
}

backward_cache: DefaultDict = collections.defaultdict(lambda: None)
Expand Down Expand Up @@ -117,6 +121,11 @@ def get_disassembler(pc):
):
extra = CS_MODE_MIPS32R6

elif pwndbg.gdblib.arch.current == "rv32":
extra = CS_MODE_RISCV32 | CS_MODE_RISCVC
elif pwndbg.gdblib.arch.current == "rv64":
extra = CS_MODE_RISCV64 | CS_MODE_RISCVC

else:
extra = None

Expand Down
94 changes: 94 additions & 0 deletions pwndbg/disasm/riscv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from capstone import * # noqa: F403
from capstone.riscv import * # noqa: F403

import pwndbg.gdblib.arch
import pwndbg.gdblib.regs


class DisassemblyAssistant(pwndbg.disasm.arch.DisassemblyAssistant):
def __init__(self, architecture):
super(DisassemblyAssistant, self).__init__(architecture)
self.architecture = architecture

def _is_condition_taken(self, instruction):
# B-type instructions have two source registers that are compared
src1_unsigned = self.register(instruction, instruction.op_find(CS_OP_REG, 1))
# compressed instructions c.beqz and c.bnez only use one register operand.
if instruction.op_count(CS_OP_REG) > 1:
src2_unsigned = self.register(instruction, instruction.op_find(CS_OP_REG, 2))
else:
src2_unsigned = 0

if self.architecture == "rv32":
src1_signed = src1_unsigned - ((src1_unsigned & 0x80000000) << 1)
src2_signed = src2_unsigned - ((src2_unsigned & 0x80000000) << 1)
elif self.architecture == "rv64":
src1_signed = src1_unsigned - ((src1_unsigned & 0x80000000_00000000) << 1)
src2_signed = src2_unsigned - ((src2_unsigned & 0x80000000_00000000) << 1)
else:
raise NotImplementedError("architecture '{}' not implemented".format(self.architecture))

return {
RISCV_INS_BEQ: src1_signed == src2_signed,
RISCV_INS_BNE: src1_signed != src2_signed,
RISCV_INS_BLT: src1_signed < src2_signed,
RISCV_INS_BGE: src1_signed >= src2_signed,
RISCV_INS_BLTU: src1_unsigned < src2_unsigned,
RISCV_INS_BGEU: src1_unsigned >= src2_unsigned,
RISCV_INS_C_BEQZ: src1_signed == 0,
RISCV_INS_C_BNEZ: src1_signed != 0,
}.get(instruction.id, None)

def condition(self, instruction):
"""Checks if the current instruction is a jump that is taken.
Returns None if the instruction is executed unconditionally,
True if the instruction is executed for sure, False otherwise.
"""
# JAL / JALR is unconditional
if RISCV_GRP_CALL in instruction.groups:
return None

# We can't reason about anything except the current instruction
# as the comparison result is dependent on the register state.
if instruction.address != pwndbg.gdblib.regs.pc:
return False

# Determine if the conditional jump is taken
if RISCV_GRP_BRANCH_RELATIVE in instruction.groups:
return self._is_condition_taken(instruction)

return None

def next(self, instruction, call=False):
"""Return the address of the jump / conditional jump,
None if the next address is not dependent on instruction.
"""
# JAL is unconditional and independent of current register status
if instruction.id in [RISCV_INS_JAL, RISCV_INS_C_JAL]:
return instruction.address + instruction.op_find(CS_OP_IMM, 1).imm

# We can't reason about anything except the current instruction
# as the comparison result is dependent on the register state.
if instruction.address != pwndbg.gdblib.regs.pc:
return None

# Determine if the conditional jump is taken
if RISCV_GRP_BRANCH_RELATIVE in instruction.groups and self._is_condition_taken(
instruction
):
return instruction.address + instruction.op_find(CS_OP_IMM, 1).imm

# Determine the target address of the indirect jump
if instruction.id in [RISCV_INS_JALR, RISCV_INS_C_JALR]:
target = (
self.register(instruction.op_find(CS_OP_REG, 1).reg)
+ instruction.op_find(CS_OP_IMM, 1).imm
)
# Clear the lowest bit without knowing the register width
return target ^ (target & 1)

return super().next(instruction, call)


assistant_rv32 = DisassemblyAssistant("rv32")
assistant_rv64 = DisassemblyAssistant("rv64")
8 changes: 8 additions & 0 deletions pwndbg/emu/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import capstone as C
import gdb
import unicorn as U
import unicorn.riscv_const

import pwndbg.disasm
import pwndbg.gdblib.arch
Expand Down Expand Up @@ -36,6 +37,8 @@ def parse_consts(u_consts):
"arm": U.UC_ARCH_ARM,
"aarch64": U.UC_ARCH_ARM64,
# 'powerpc': U.UC_ARCH_PPC,
"rv32": U.UC_ARCH_RISCV,
"rv64": U.UC_ARCH_RISCV,
}

arch_to_UC_consts = {
Expand All @@ -45,6 +48,8 @@ def parse_consts(u_consts):
"sparc": parse_consts(U.sparc_const),
"arm": parse_consts(U.arm_const),
"aarch64": parse_consts(U.arm64_const),
"rv32": parse_consts(U.riscv_const),
"rv64": parse_consts(U.riscv_const),
}

# Map our internal architecture names onto Unicorn Engine's architecture types.
Expand All @@ -56,6 +61,8 @@ def parse_consts(u_consts):
"arm": C.CS_ARCH_ARM,
"aarch64": C.CS_ARCH_ARM64,
# 'powerpc': C.CS_ARCH_PPC,
"rv32": C.CS_ARCH_RISCV,
"rv64": C.CS_ARCH_RISCV,
}

DEBUG = False
Expand Down Expand Up @@ -87,6 +94,7 @@ def debug(fmt, args=()) -> None:
U.UC_ARCH_ARM: [C.arm_const.ARM_INS_SVC],
U.UC_ARCH_ARM64: [C.arm64_const.ARM64_INS_SVC],
U.UC_ARCH_PPC: [C.ppc_const.PPC_INS_SC],
U.UC_ARCH_RISCV: [C.riscv_const.RISCV_INS_ECALL],
}

blacklisted_regs = ["ip", "cs", "ds", "es", "fs", "gs", "ss", "fsbase", "gsbase"]
Expand Down
5 changes: 3 additions & 2 deletions pwndbg/gdblib/arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# TODO: x86-64 needs to come before i386 in the current implementation, make
# this order-independent
ARCHS = ("x86-64", "i386", "aarch64", "mips", "powerpc", "sparc", "arm", "armcm", "riscv:rv64")
ARCHS = ("x86-64", "i386", "aarch64", "mips", "powerpc", "sparc", "arm", "armcm", "rv32", "rv64")

# mapping between gdb and pwntools arch names
pwnlib_archs_mapping = {
Expand All @@ -19,7 +19,8 @@
"sparc": "sparc",
"arm": "arm",
"armcm": "thumb",
"riscv:rv64": "riscv",
"rv32": "riscv32",
"rv64": "riscv64",
}

arch = Arch("i386", typeinfo.ptrsize, "little")
Expand Down
3 changes: 2 additions & 1 deletion pwndbg/gdblib/vmmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ def get() -> Tuple[pwndbg.lib.memory.Page, ...]:
"i386",
"x86-64",
"aarch64",
"riscv:rv64",
"rv32",
"rv64",
):
# If kernel_vmmap_via_pt is not set to the default value of "deprecated",
# That means the user was explicitly setting it themselves and need to
Expand Down
8 changes: 6 additions & 2 deletions pwndbg/lib/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class SigreturnABI(SyscallABI):
linux_mips = ABI(["$a0", "$a1", "$a2", "$a3"], 4, 0)
linux_ppc = ABI(["r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10"], 4, 0)
linux_ppc64 = ABI(["r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10"], 8, 0)
linux_riscv32 = ABI(["a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7"], 4, 0)
linux_riscv64 = ABI(["a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7"], 8, 0)

linux_i386_syscall = SyscallABI(["eax", "ebx", "ecx", "edx", "esi", "edi", "ebp"], 4, 0)
Expand All @@ -80,6 +81,7 @@ class SigreturnABI(SyscallABI):
linux_mips_syscall = SyscallABI(["$v0", "$a0", "$a1", "$a2", "$a3"], 4, 0)
linux_ppc_syscall = SyscallABI(["r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9"], 4, 0)
linux_ppc64_syscall = SyscallABI(["r0", "r3", "r4", "r5", "r6", "r7", "r8"], 8, 0)
linux_riscv32_syscall = SyscallABI(["a7", "a0", "a1", "a2", "a3", "a4", "a5", "a6"], 4, 0)
linux_riscv64_syscall = SyscallABI(["a7", "a0", "a1", "a2", "a3", "a4", "a5", "a6"], 8, 0)

linux_i386_sigreturn = SigreturnABI(["eax"], 4, 0)
Expand All @@ -100,7 +102,8 @@ class SigreturnABI(SyscallABI):
(32, "mips", "linux"): linux_mips,
(32, "powerpc", "linux"): linux_ppc,
(64, "powerpc", "linux"): linux_ppc64,
(64, "riscv64", "linux"): linux_riscv64,
(32, "rv32", "linux"): linux_riscv32,
(64, "rv64", "linux"): linux_riscv64,
}

SYSCALL_ABIS = {
Expand All @@ -112,7 +115,8 @@ class SigreturnABI(SyscallABI):
(32, "mips", "linux"): linux_mips_syscall,
(32, "powerpc", "linux"): linux_ppc_syscall,
(64, "powerpc", "linux"): linux_ppc64_syscall,
(64, "riscv64", "linux"): linux_riscv64_syscall,
(32, "rv32", "linux"): linux_riscv32_syscall,
(64, "rv64", "linux"): linux_riscv64_syscall,
}

SIGRETURN_ABIS = {
Expand Down
25 changes: 24 additions & 1 deletion pwndbg/lib/regs.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,28 @@ def __iter__(self):
retval="v0",
)

# https://riscv.org/technical/specifications/
# Volume 1, Unprivileged Spec v. 20191213
# Chapter 25 - RISC-V Assembly Programmer’s Handbook
# x0 => zero (Hard-wired zero)
# x1 => ra (Return address)
# x2 => sp (Stack pointer)
# x3 => gp (Global pointer)
# x4 => tp (Thread pointer)
# x5 => t0 (Temporary/alternate link register)
# x6–7 => t1–2 (Temporaries)
# x8 => s0/fp (Saved register/frame pointer)
# x9 => s1 (Saved register)
# x10-11 => a0–1 (Function arguments/return values)
# x12–17 => a2–7 (Function arguments)
# x18–27 => s2–11 (Saved registers)
# x28–31 => t3–6 (Temporaries)
# f0–7 => ft0–7 (FP temporaries)
# f8–9 => fs0–1 (FP saved registers)
# f10–11 => fa0–1 (FP arguments/return values)
# f12–17 => fa2–7 (FP arguments)
# f18–27 => fs2–11 (FP saved registers)
# f28–31 => ft8–11 (FP temporaries)
riscv = RegisterSet(
pc="pc",
stack="sp",
Expand Down Expand Up @@ -518,7 +540,8 @@ def __iter__(self):
"i386": i386,
"i8086": i386,
"x86-64": amd64,
"riscv:rv64": riscv,
"rv32": riscv,
"rv64": riscv,
"mips": mips,
"sparc": sparc,
"arm": arm,
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
capstone==4.0.2
capstone==5.0.0rc4
psutil==5.9.5
pwntools==4.10.0
pycparser==2.21
Expand Down

0 comments on commit 45f6ee7

Please sign in to comment.