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')