mirror of https://github.com/pwndbg/pwndbg.git
Improve RISCV support (#1770)
* Improve RISCV support This is a resurrection of #829 Co-authored-by: Tobias Faller <faller@endiio.com> * Silence bogus vermin warning * Fix relative backwards jump calculations The target address wouldn't be truncated to the pointer size. * Add basic qemu-user test * Run qemu-user tests in CI * Make shfmt happy * Fix pwntools < 4.11.0 support * Support RISCV32 for pwntools < 4.11.0 as well --------- Co-authored-by: Tobias Faller <faller@endiio.com>pull/1782/head
parent
52d729f574
commit
a6cc19aa5c
@ -0,0 +1,95 @@
|
||||
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.
|
||||
"""
|
||||
ptrmask = pwndbg.gdblib.arch.ptrmask
|
||||
# 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) & ptrmask
|
||||
|
||||
# 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) & ptrmask
|
||||
|
||||
# Determine the target address of the indirect jump
|
||||
if instruction.id in [RISCV_INS_JALR, RISCV_INS_C_JALR]:
|
||||
target = (
|
||||
self.register(instruction, instruction.op_find(CS_OP_REG, 1))
|
||||
+ instruction.op_find(CS_OP_IMM, 1).imm
|
||||
) & ptrmask
|
||||
# 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")
|
||||
@ -1,3 +1,15 @@
|
||||
.PHONY: all
|
||||
all: reference-binary.aarch64.out reference-binary.riscv64.out
|
||||
|
||||
reference-binary.aarch64.out : reference-binary.aarch64.c
|
||||
@echo "[+] Building '$@'"
|
||||
@aarch64-linux-gnu-gcc $(CFLAGS) $(EXTRA_FLAGS) -w -o $@ $? $(LDFLAGS)
|
||||
|
||||
# apt install crossbuild-essential-riscv64
|
||||
reference-binary.riscv64.out : reference-binary.riscv64.c
|
||||
@echo "[+] Building '$@'"
|
||||
@riscv64-linux-gnu-gcc -march=rv64gc -mabi=lp64d -g $(CFLAGS) $(EXTRA_FLAGS) -w -o $@ $? $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm reference-binary.aarch64.out
|
||||
rm reference-binary.riscv64.out
|
||||
@ -0,0 +1,10 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char const* argv[]) {
|
||||
if (argc > 1) {
|
||||
puts("Enough args");
|
||||
} else {
|
||||
puts("Not enough args");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -1,28 +1,38 @@
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import gdb
|
||||
|
||||
import pwndbg
|
||||
|
||||
gdb.execute("break break_here")
|
||||
assert pwndbg.gdblib.symbol.address("main") == 0x5500000A1C
|
||||
gdb.execute("continue")
|
||||
|
||||
gdb.execute("argv", to_string=True)
|
||||
assert gdb.execute("argc", to_string=True) == 1
|
||||
gdb.execute("auxv", to_string=True)
|
||||
assert gdb.execute("cpsr", to_string=True) == "cpsr 0x60000000 [ n Z C v q pan il d a i f el sp ]"
|
||||
gdb.execute("context", to_string=True)
|
||||
gdb.execute("hexdump", to_string=True)
|
||||
gdb.execute("telescope", to_string=True)
|
||||
|
||||
# TODO: Broken
|
||||
gdb.execute("retaddr", to_string=True)
|
||||
|
||||
# Broken
|
||||
gdb.execute("procinfo", to_string=True)
|
||||
|
||||
# Broken
|
||||
gdb.execute("vmmap", to_string=True)
|
||||
|
||||
gdb.execute("piebase", to_string=True)
|
||||
|
||||
gdb.execute("nextret", to_string=True)
|
||||
try:
|
||||
gdb.execute("break break_here")
|
||||
assert pwndbg.gdblib.symbol.address("main") == 0x5500000A1C
|
||||
gdb.execute("continue")
|
||||
|
||||
gdb.execute("argv", to_string=True)
|
||||
assert gdb.execute("argc", to_string=True).strip() == "1"
|
||||
gdb.execute("auxv", to_string=True)
|
||||
assert (
|
||||
gdb.execute("cpsr", to_string=True, from_tty=False).strip()
|
||||
== "cpsr 0x60000000 [ n Z C v q pan il d a i f el sp ]"
|
||||
)
|
||||
gdb.execute("context", to_string=True)
|
||||
gdb.execute("hexdump", to_string=True)
|
||||
gdb.execute("telescope", to_string=True)
|
||||
|
||||
# TODO: Broken
|
||||
gdb.execute("retaddr", to_string=True)
|
||||
|
||||
# Broken
|
||||
gdb.execute("procinfo", to_string=True)
|
||||
|
||||
# Broken
|
||||
gdb.execute("vmmap", to_string=True)
|
||||
|
||||
gdb.execute("piebase", to_string=True)
|
||||
|
||||
gdb.execute("nextret", to_string=True)
|
||||
except AssertionError:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
sys.exit(1)
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import gdb
|
||||
|
||||
import pwndbg
|
||||
|
||||
try:
|
||||
gdb.execute("break 4")
|
||||
assert pwndbg.gdblib.symbol.address("main") == 0x4000000668
|
||||
gdb.execute("continue")
|
||||
|
||||
gdb.execute("nextcall", to_string=True)
|
||||
|
||||
# verify call argument are enriched
|
||||
assembly = gdb.execute("nearpc", to_string=True)
|
||||
assert re.search(r"s.*'Not enough args'", assembly), assembly
|
||||
except AssertionError:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
sys.exit(1)
|
||||
Loading…
Reference in new issue