Skip to content

Commit 794c602

Browse files
authored
Support option to show opcodes next to disassembly in context and cs command (#583)
It might be helpful to show the opcodes of instructions next to the disassembly. To allow this for context, I added a setting to context, configured through gef config context.show_opcodes_size n, where n is the max number of opcode bytes to show.
1 parent 2934c16 commit 794c602

File tree

4 files changed

+94
-14
lines changed

4 files changed

+94
-14
lines changed

docs/commands/capstone-disassemble.md

+5
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ Show instructions before $pc
2323
```
2424
gef➤ cs nb_prev=3
2525
```
26+
27+
Show opcodes next to disassembly
28+
```
29+
gef➤ cs op
30+
```

docs/commands/context.md

+5
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ gef➤ gef config context.clear_screen 1
161161
gef➤ gef config context.show_registers_raw 1
162162
```
163163

164+
* Number of bytes of opcodes to display next to the disassembly.
165+
```
166+
gef➤ gef config context.show_opcodes_size 4
167+
```
168+
164169
* Don't 'peek' into the start of functions that are called.
165170
```
166171
gef➤ gef config context.peek_calls False

gef.py

+62-14
Original file line numberDiff line numberDiff line change
@@ -547,27 +547,34 @@ def __init__(self, elf="", minimalist=False):
547547

548548
with open(elf, "rb") as fd:
549549
# off 0x0
550-
self.e_magic, self.e_class, self.e_endianness, self.e_eiversion = struct.unpack(">IBBB", fd.read(7))
550+
self.e_magic, self.e_class, self.e_endianness, self.e_eiversion = struct.unpack(
551+
">IBBB", fd.read(7))
551552

552553
# adjust endianness in bin reading
553554
endian = "<" if self.e_endianness == Elf.LITTLE_ENDIAN else ">"
554555

555556
# off 0x7
556-
self.e_osabi, self.e_abiversion = struct.unpack("{}BB".format(endian), fd.read(2))
557+
self.e_osabi, self.e_abiversion = struct.unpack(
558+
"{}BB".format(endian), fd.read(2))
557559
# off 0x9
558560
self.e_pad = fd.read(7)
559561
# off 0x10
560-
self.e_type, self.e_machine, self.e_version = struct.unpack("{}HHI".format(endian), fd.read(8))
562+
self.e_type, self.e_machine, self.e_version = struct.unpack(
563+
"{}HHI".format(endian), fd.read(8))
561564
# off 0x18
562565
if self.e_class == Elf.ELF_64_BITS:
563566
# if arch 64bits
564-
self.e_entry, self.e_phoff, self.e_shoff = struct.unpack("{}QQQ".format(endian), fd.read(24))
567+
self.e_entry, self.e_phoff, self.e_shoff = struct.unpack(
568+
"{}QQQ".format(endian), fd.read(24))
565569
else:
566570
# else arch 32bits
567-
self.e_entry, self.e_phoff, self.e_shoff = struct.unpack("{}III".format(endian), fd.read(12))
571+
self.e_entry, self.e_phoff, self.e_shoff = struct.unpack(
572+
"{}III".format(endian), fd.read(12))
568573

569-
self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = struct.unpack("{}HHHH".format(endian), fd.read(8))
570-
self.e_shentsize, self.e_shnum, self.e_shstrndx = struct.unpack("{}HHH".format(endian), fd.read(6))
574+
self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = struct.unpack(
575+
"{}HHHH".format(endian), fd.read(8))
576+
self.e_shentsize, self.e_shnum, self.e_shstrndx = struct.unpack(
577+
"{}HHH".format(endian), fd.read(6))
571578
return
572579

573580
def is_valid(self):
@@ -577,10 +584,31 @@ def is_valid(self):
577584
class Instruction:
578585
"""GEF representation of a CPU instruction."""
579586

580-
def __init__(self, address, location, mnemo, operands):
581-
self.address, self.location, self.mnemonic, self.operands = address, location, mnemo, operands
587+
def __init__(self, address, location, mnemo, operands, opcodes):
588+
self.address, self.location, self.mnemonic, self.operands, self.opcodes = address, location, mnemo, operands, opcodes
582589
return
583590

591+
# Allow formatting an instruction with {:o} to show opcodes.
592+
# The number of bytes to display can be configured, e.g. {:4o} to only show 4 bytes of the opcodes
593+
def __format__(self, format_spec):
594+
if len(format_spec) == 0 or format_spec[-1] != "o":
595+
return str(self)
596+
597+
if format_spec == "o":
598+
opcodes_len = len(self.opcodes)
599+
else:
600+
opcodes_len = int(format_spec[:-1])
601+
602+
opcodes_text = "".join("{:02x}".format(b) for b in self.opcodes[:opcodes_len])
603+
if opcodes_len < len(self.opcodes):
604+
opcodes_text += "..."
605+
return "{:#10x} {:{:d}} {:16} {:6} {:s}".format(self.address,
606+
opcodes_text,
607+
opcodes_len * 2 + 3,
608+
self.location,
609+
self.mnemonic,
610+
", ".join(self.operands))
611+
584612
def __str__(self):
585613
return "{:#10x} {:16} {:6} {:s}".format(
586614
self.address, self.location, self.mnemonic, ", ".join(self.operands)
@@ -1279,7 +1307,9 @@ def gdb_disassemble(start_pc, **kwargs):
12791307
loc = gdb_get_location_from_symbol(address)
12801308
location = "<{}+{}>".format(*loc) if loc else ""
12811309

1282-
yield Instruction(address, location, mnemo, operands)
1310+
opcodes = read_memory(insn["addr"], insn["length"])
1311+
1312+
yield Instruction(address, location, mnemo, operands, opcodes)
12831313

12841314

12851315
def gdb_get_nth_previous_instruction_address(addr, n):
@@ -1378,7 +1408,7 @@ def cs_insn_to_gef_insn(cs_insn):
13781408
sym_info = gdb_get_location_from_symbol(cs_insn.address)
13791409
loc = "<{}+{}>".format(*sym_info) if sym_info else ""
13801410
ops = [] + cs_insn.op_str.split(", ")
1381-
return Instruction(cs_insn.address, loc, cs_insn.mnemonic, ops)
1411+
return Instruction(cs_insn.address, loc, cs_insn.mnemonic, ops, cs_insn.bytes)
13821412

13831413
capstone = sys.modules["capstone"]
13841414
arch, mode = get_capstone_arch(arch=kwargs.get("arch", None), mode=kwargs.get("mode", None), endian=kwargs.get("endian", None))
@@ -6395,7 +6425,7 @@ class CapstoneDisassembleCommand(GenericCommand):
63956425
"""Use capstone disassembly framework to disassemble code."""
63966426

63976427
_cmdline_ = "capstone-disassemble"
6398-
_syntax_ = "{:s} [LOCATION] [[length=LENGTH] [option=VALUE]] ".format(_cmdline_)
6428+
_syntax_ = "{:s} [LOCATION] [[length=LENGTH] [OPCODES] [option=VALUE]] ".format(_cmdline_)
63996429
_aliases_ = ["cs-dis",]
64006430
_example_ = "{:s} $pc length=50".format(_cmdline_)
64016431

@@ -6414,21 +6444,32 @@ def __init__(self):
64146444
@only_if_gdb_running
64156445
def do_invoke(self, argv):
64166446
location = None
6447+
show_opcodes = False
64176448

64186449
kwargs = {}
64196450
for arg in argv:
64206451
if "=" in arg:
64216452
key, value = arg.split("=", 1)
64226453
kwargs[key] = value
64236454

6455+
elif "opcodes".startswith(arg.lower()):
6456+
show_opcodes = True
6457+
64246458
elif location is None:
64256459
location = parse_address(arg)
64266460

64276461
location = location or current_arch.pc
64286462
length = int(kwargs.get("length", get_gef_setting("context.nb_lines_code")))
64296463

6464+
insns = []
6465+
opcodes_len = 0
64306466
for insn in capstone_disassemble(location, length, skip=length*self.repeat_count, **kwargs):
6431-
text_insn = str(insn)
6467+
insns.append(insn)
6468+
opcodes_len = max(opcodes_len, len(insn.opcodes))
6469+
6470+
for insn in insns:
6471+
insn_fmt = "{{:{}o}}".format(opcodes_len) if show_opcodes else "{}"
6472+
text_insn = insn_fmt.format(insn)
64326473
msg = ""
64336474

64346475
if insn.address == current_arch.pc:
@@ -7571,6 +7612,7 @@ def __init__(self):
75717612
self.add_setting("show_source_code_variable_values", True, "Show extra PC context info in the source code")
75727613
self.add_setting("show_stack_raw", False, "Show the stack pane as raw hexdump (no dereference)")
75737614
self.add_setting("show_registers_raw", False, "Show the registers pane with raw values (no dereference)")
7615+
self.add_setting("show_opcodes_size", 0, "Number of bytes of opcodes to display next to the disassembly")
75747616
self.add_setting("peek_calls", True, "Peek into calls")
75757617
self.add_setting("peek_ret", True, "Peek at return address")
75767618
self.add_setting("nb_lines_stack", 8, "Number of line in the stack pane")
@@ -7783,6 +7825,7 @@ def context_code(self):
77837825
nb_insn = self.get_setting("nb_lines_code")
77847826
nb_insn_prev = self.get_setting("nb_lines_code_prev")
77857827
use_capstone = self.has_setting("use_capstone") and self.get_setting("use_capstone")
7828+
show_opcodes_size = self.has_setting("show_opcodes_size") and self.get_setting("show_opcodes_size")
77867829
past_insns_color = get_gef_setting("theme.old_context")
77877830
cur_insn_color = get_gef_setting("theme.disassemble_current_instruction")
77887831
pc = current_arch.pc
@@ -7801,9 +7844,14 @@ def context_code(self):
78017844
line = []
78027845
is_taken = False
78037846
target = None
7804-
text = str(insn)
78057847
bp_prefix = Color.redify(BP_GLYPH) if self.addr_has_breakpoint(insn.address, bp_locations) else " "
78067848

7849+
if show_opcodes_size == 0:
7850+
text = str(insn)
7851+
else:
7852+
insn_fmt = "{{:{}o}}".format(show_opcodes_size)
7853+
text = insn_fmt.format(insn)
7854+
78077855
if insn.address < pc:
78087856
line += "{} {}".format(bp_prefix, Color.colorify(text, past_insns_color))
78097857

tests/runtests.py

+22
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ def test_cmd_capstone_disassemble(self):
5050
res = gdb_start_silent_cmd("capstone-disassemble")
5151
self.assertNoException(res)
5252
self.assertTrue(len(res.splitlines()) > 1)
53+
54+
self.assertFailIfInactiveSession(gdb_run_cmd("cs opcodes"))
55+
res = gdb_start_silent_cmd("cs opcodes")
56+
self.assertNoException(res)
57+
self.assertTrue(len(res.splitlines()) > 1)
58+
# match the following pattern
59+
# 0x5555555546b2 897dec <main+8> mov DWORD PTR [rbp-0x14], edi
60+
self.assertRegex(res, r"0x.{12}\s([0-9a-f]{2})+\s+.*")
5361
return
5462

5563
def test_cmd_checksec(self):
@@ -605,6 +613,20 @@ def test_func_stack(self):
605613
self.assertRegex(res, r"\+0x0*20: *0x0000000000000000\n")
606614
return
607615

616+
class TestGefContextConfigs(GefUnitTestGeneric):
617+
def test_config_show_opcodes_size(self):
618+
res = gdb_run_cmd("entry-break", before=["gef config context.show_opcodes_size 4",])
619+
self.assertNoException(res)
620+
self.assertTrue(len(res.splitlines()) > 1)
621+
622+
# match one of the following patterns
623+
# 0x5555555546b2 897dec <main+8> mov DWORD PTR [rbp-0x14], edi
624+
# 0x5555555546b5 488975e0 <main+11> mov QWORD PTR [rbp-0x20], rsi
625+
# 0x5555555546b9 488955d8 <main+15> mov QWORD PTR [rbp-0x28], rdx
626+
# 0x5555555546bd 64488b04... <main+19> mov rax, QWORD PTR fs:0x28
627+
self.assertRegex(res, r"0x.{12}\s([0-9a-f]{2}){1,4}(\.\.\.)?\s+.*")
628+
return
629+
608630

609631
def run_tests(name=None):
610632

0 commit comments

Comments
 (0)