From 87bf6ac0f9c37c90cc0da8da6a78654256003305 Mon Sep 17 00:00:00 2001 From: anthraxx Date: Tue, 30 Mar 2021 22:48:46 +0200 Subject: [PATCH] chore(ghidra): simplify logic and clean up code flow Exception driven code flow for expected code paths is not great for readability and suffers some performance degeneration that can be circumvented with conditional checks. Use exceptions exclusively for fatal failure handling and either return a simple string from the decompile function or throw an exception. --- pwndbg/commands/context.py | 16 +++++---- pwndbg/commands/ghidra.py | 5 ++- pwndbg/ghidra.py | 68 +++++++++++++++++--------------------- pwndbg/symbol.py | 28 ++++++++++++++++ 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/pwndbg/commands/context.py b/pwndbg/commands/context.py index 4649f857c..0e94202f8 100644 --- a/pwndbg/commands/context.py +++ b/pwndbg/commands/context.py @@ -224,14 +224,16 @@ def context_ghidra(target=sys.stdout, with_banner=True, width=None): if config_context_ghidra == "never": return [] - elif config_context_ghidra == "if-no-source": - try: - with open(gdb.selected_frame().find_sal().symtab.fullname()) as _: - return [] # return nothing if we found the source - except Exception: # a lot can go wrong in search of source code. - pass # we don't care what, just that it did not work out well... - return banner + pwndbg.ghidra.decompile() + if config_context_ghidra == "if-no-source": + source_filename = pwndbg.symbol.selected_frame_source_absolute_filename() + if source_filename and os.path.exists(source_filename): + return [] + + try: + return banner + pwndbg.ghidra.decompile().split('\n') + except Exception as e: + return banner + [message.error(e)] diff --git a/pwndbg/commands/ghidra.py b/pwndbg/commands/ghidra.py index fbd219c8d..2da94a7c5 100644 --- a/pwndbg/commands/ghidra.py +++ b/pwndbg/commands/ghidra.py @@ -12,4 +12,7 @@ parser.add_argument("func", type=str, default=None, nargs="?", help="Function to @pwndbg.commands.OnlyWithFile @pwndbg.commands.ArgparsedCommand(parser) def ghidra(func): - print("\n".join(pwndbg.ghidra.decompile(func))) + try: + print(pwndbg.ghidra.decompile(func)) + except Exception as e: + print(message.error(e)) diff --git a/pwndbg/ghidra.py b/pwndbg/ghidra.py index 05465299a..c154a8169 100644 --- a/pwndbg/ghidra.py +++ b/pwndbg/ghidra.py @@ -19,58 +19,52 @@ def decompile(func=None): """ try: r2 = pwndbg.radare2.r2pipe() - # LD list supported decompilers (e cmd.pdc=?) - # Outputs for example:: pdc\npdg - if not "pdg" in r2.cmd("LD").split("\n"): - return ["radare2 plugin r2ghidra-dec must be installed and available from r2"] - except ImportError: # no r2pipe present - return ["r2pipe not available, but required for r2->ghidra-bridge"] - if func is None: - try: - func = hex(pwndbg.regs[pwndbg.regs.current.pc]) - except: - func = "main" + except ImportError: + raise Exception('r2pipe not available, but required for r2->ghidra bridge') + + # LD -> list supported decompilers (e cmd.pdc=?) + # Outputs for example: pdc\npdg + if "pdg" not in r2.cmd("LD").split("\n"): + raise Exception('radare2 plugin r2ghidra must be installed and available from r2') + + if not func: + func = hex(pwndbg.regs[pwndbg.regs.current.pc]) if pwndbg.proc.alive else 'main' + src = r2.cmdj("pdgj @" + func) - # Early exit if decompile command failed horribly, like unknown addr/func if not src: - return [] + raise Exception("Decompile command failed, check if '{}' is a valid target".format(func)) + current_line_marker = '/*%%PWNDBG_CODE_MARKER%%*/' source = src.get("code", "") - curline = None - try: - cur = pwndbg.regs[pwndbg.regs.current.pc] - except AttributeError: - cur = None # If not running there is no current.pc - if cur is not None: + + # If not running there is no current pc to mark + if pwndbg.proc.alive: + pc = pwndbg.regs[pwndbg.regs.current.pc] + closest = 0 for off in (a.get("offset", 0) for a in src.get("annotations", [])): - if abs(cur - closest) > abs(cur - off): + if abs(pc - closest) > abs(pc - off): closest = off - pos_annotations = sorted([a for a in src.get("annotations", []) if a.get("offset") == closest], - key=lambda a: a["start"]) + pos_annotations = sorted([a for a in src.get("annotations", []) if a.get("offset") == closest], key=lambda a: a["start"]) + + # Append code prefix marker for the current line and replace it later if pos_annotations: curline = source.count("\n", 0, pos_annotations[0]["start"]) - source = source.split("\n") + source = source.split("\n") + line = source[curline] + if line.startswith(' '): + line = line[min(4, len(pwndbg.config.code_prefix) + 1):] + source[curline] = current_line_marker + ' ' + line + source = "\n".join(source) - # Append code prefix marker for the current line and replace it later - current_line_marker = '/*%%PWNDBG_CODE_MARKER%%*/' - if curline is not None: - line = source[curline] - if line.startswith(' '): - line = line[min(4, len(pwndbg.config.code_prefix) + 1):] - source[curline] = current_line_marker + ' ' + line - # Join the source for highlighting - source = "\n".join(source) if pwndbg.config.syntax_highlight: # highlighting depends on the file extension to guess the language, so try to get one... - try: # try to read the source filename from debug information - src_filename = gdb.selected_frame().find_sal().symtab.fullname() - except: # if non, take the original filename and maybe append .c (just assuming is was c) + src_filename = pwndbg.symbol.selected_frame_source_absolute_filename() + if not src_filename: filename = gdb.current_progspace().filename - src_filename = filename+".c" if os.path.basename(filename).find(".") < 0 else filename + src_filename = filename + ".c" if os.path.basename(filename).find(".") < 0 else filename source = H.syntax_highlight(source, src_filename) # Replace code prefix marker after syntax highlighting source = source.replace(current_line_marker, C.prefix(pwndbg.config.code_prefix), 1) - source = source.split("\n") return source diff --git a/pwndbg/symbol.py b/pwndbg/symbol.py index fbaf91772..ef6a49fdd 100644 --- a/pwndbg/symbol.py +++ b/pwndbg/symbol.py @@ -262,5 +262,33 @@ def add_main_exe_to_symbols(): except gdb.error: pass + +@pwndbg.memoize.reset_on_stop +@pwndbg.memoize.reset_on_start +def selected_frame_source_absolute_filename(): + """ + Retrieve the symbol table’s source absolute file name from the selected frame. + + In case of missing symbol table or frame information, None is returned. + """ + try: + frame = gdb.selected_frame() + except gdb.error: + return None + + if not frame: + return None + + sal = frame.find_sal() + if not sal: + return None + + symtab = sal.symtab + if not symtab: + return None + + return symtab.fullname() + + if '/usr/lib/debug' not in get_directory(): set_directory(get_directory() + ':/usr/lib/debug')