diff --git a/pwndbg/commands/__init__.py b/pwndbg/commands/__init__.py index cd64c9890..cfde47451 100644 --- a/pwndbg/commands/__init__.py +++ b/pwndbg/commands/__init__.py @@ -696,7 +696,6 @@ def load_commands() -> None: import pwndbg.commands.ropper import pwndbg.commands.segments import pwndbg.commands.shell - import pwndbg.commands.version import pwndbg.commands.argv import pwndbg.commands.aslr @@ -763,6 +762,7 @@ def load_commands() -> None: import pwndbg.commands.tips import pwndbg.commands.tls import pwndbg.commands.valist + import pwndbg.commands.version import pwndbg.commands.vmmap import pwndbg.commands.windbg import pwndbg.commands.xinfo diff --git a/pwndbg/commands/version.py b/pwndbg/commands/version.py index 4a02ae0a7..14ad41838 100644 --- a/pwndbg/commands/version.py +++ b/pwndbg/commands/version.py @@ -1,5 +1,5 @@ """ -Displays gdb, python and pwndbg versions. +Implements version and bugreport commands. """ from __future__ import annotations @@ -14,8 +14,6 @@ from subprocess import check_output from tempfile import NamedTemporaryFile from urllib.parse import quote -import gdb - import pwndbg import pwndbg.commands import pwndbg.integration @@ -23,53 +21,81 @@ from pwndbg.color import message from pwndbg.commands import CommandCategory -def _gdb_version() -> str: - return gdb.VERSION +def os_info(): + os_info = platform.system() + if os_info.lower() == "linux" and os.path.isfile("/etc/os-release"): + with open("/etc/os-release") as os_release: + contents = os_release.read() + match = re.search('PRETTY_NAME="?([^",\n]+)', contents) + if match: + os_info = match.group(1) -def _py_version(): - return sys.version.replace("\n", " ") + return os_info -def capstone_version(): +def module_version(module): try: - import capstone - - return ".".join(map(str, capstone.cs_version())) + return __import__(module).__version__ except ImportError: return "not found" -def unicorn_version(): - try: - import unicorn +def debugger_version(): + if pwndbg.dbg.is_gdblib_available(): + import gdb - return unicorn.__version__ - except ImportError: - return "not found" + return f"GDB: {gdb.VERSION}" + else: + return f"LLDB: {'.'.join(map(str, pwndbg.dbg_mod.lldb.LLDB_VERSION))}" def all_versions(): - gdb_str = f"Gdb: {_gdb_version()}" - py_str = f"Python: {_py_version()}" - pwndbg_str = f"Pwndbg: {pwndbg.__version__}" + return ( + f"Pwndbg: {pwndbg.__version__}", + f"Python: {sys.version.replace('\n', ' ')}", + debugger_version(), + f"Capstone: {module_version('capstone')}", + f"Unicorn: {module_version('unicorn')}", + f"Pwnlib: {module_version('pwnlib')}", + ) + pwndbg.integration.provider.get_versions() + + +def get_target_arch(): + arch_info = pwndbg.aglib.arch.current + target = f"Target Arch: {arch_info}\n" + + if pwndbg.dbg.is_gdblib_available(): + import gdb - capstone_str = f"Capstone: {capstone_version()}" - unicorn_str = f"Unicorn: {unicorn_version()}" + # Note: this are only available if given arch is supported by GDB + # (e.g., `gdb-multiarch` on Ubuntu) + if arch_info in ("arm", "armcm", "aarch64"): + arm_info = gdb.execute("show arm", to_string=True) + target += f"ARM: {arm_info}\n" - all_versions = (gdb_str, py_str, pwndbg_str, capstone_str, unicorn_str) + elif arch_info in ("mips", "mips64"): + mips_info = gdb.execute("show mips", to_string=True) + target += f"MIPS: {mips_info}\n" - all_versions += pwndbg.integration.provider.get_versions() - return all_versions + return target + + +def get_terminal_size(): + try: + width_info = os.get_terminal_size().columns + height_info = os.get_terminal_size().lines + except OSError: + # Terminal size may not be available in non-interactive environments (e.g., scripts, IDEs) + width_info = height_info = "" + + return f"Terminal width: {width_info}, height: {height_info}\n" @pwndbg.commands.ArgparsedCommand( - "Displays GDB, Python, and pwndbg versions.", category=CommandCategory.PWNDBG + "Displays Pwndbg and its important deps versions.", category=CommandCategory.PWNDBG ) def version() -> None: - """ - Displays GDB, Python, and pwndbg versions. - """ print("\n".join(map(message.system, all_versions()))) @@ -87,20 +113,19 @@ bugreport_group.add_argument( def bugreport(run_browser=False, use_gh=False): ISSUE_TEMPLATE = """ ### Description ### Steps to reproduce @@ -111,132 +136,34 @@ If this is connected to particular C/asm code or a binary, please provide the binary or if possible, a smallest C code that reproduces the issue. --> -Gdb session history: +Session history: ``` -{gdb_history} +{session_history} ``` ### My setup ``` {setup} ```""" + setup = "\n".join(all_versions()) + "\n" + setup += f"OS: {os_info()} ({platform.platform()}, {sys.byteorder} endian)\n" + setup += f"OS ABI: {platform.uname().version}\n" + setup += f"Charset: {sys.getdefaultencoding()}\n" + setup += get_terminal_size() + setup += get_target_arch() - gdb_config = gdb.execute("show configuration", to_string=True).split("\n") - all_info = all_versions() - os_info = platform.system() + # Commented out for now: do we need this? It seems to be a bloat for GDB. + # People rarely build custom GDB with fancy options...? + # setup += get_debugger_configuration() - current_setup = f"Platform: {platform.platform()}\n" + session_history = get_debugger_session_history() - if os_info.lower() == "linux" and os.path.isfile("/etc/os-release"): - with open("/etc/os-release") as os_release: - contents = os_release.read() - match = re.search('PRETTY_NAME="?([^",\n]+)', contents) - if match: - os_info = match.group(1) - - current_setup += f"OS: {os_info}\n" - - # 1. showing osabi - osabi_info = platform.uname().version - current_setup += f"OS ABI: {osabi_info}\n" - - # 2. showing architecture - arch_info = platform.machine() - current_setup += f"Architecture: {arch_info}\n" - - # 3. showing endian - endian_info = sys.byteorder - current_setup += f"Endian: {endian_info}\n" - - # 4. Depending on current arch -- note that those are only available if given arch is supported by current GDB, like gdb-multiarch - if arch_info in ["armv7l", "aarch64"]: - arm_info = gdb.execute("show arm", to_string=True) - current_setup += f"ARM: {arm_info}\n" - - elif arch_info in ["mips", "mips64"]: - mips_info = gdb.execute("show mips", to_string=True) - current_setup += f"MIPS: {mips_info}\n" - - # 7. showing charset - charset_info = sys.getdefaultencoding() - current_setup += f"Charset: {charset_info}\n" - - # 8. showing width, height - try: - width_info = os.get_terminal_size().columns - height_info = os.get_terminal_size().lines - except OSError: - # Terminal size may not be available in non-interactive environments (e.g., scripts, IDEs) - width_info = "no terminal size" - height_info = "no terminal size" - - current_setup += f"Width: {width_info}\n" - current_setup += f"Height: {height_info}\n" - - current_setup += "\n".join(all_info) - current_setup += "\n" + "\n".join(gdb_config) - - # get saved history size (not including current gdb session) - gdb_history_file = gdb.execute("show history filename", to_string=True) - gdb_history_file = gdb_history_file[ - gdb_history_file.index('"') + 1 : gdb_history_file.rindex('"') - ] - gdb_history_len = 0 - try: - with open(gdb_history_file) as f: - gdb_history_len = len(f.readlines()) - except FileNotFoundError: - pass - - max_command_no = 0 - history_commands = gdb.execute("show commands", to_string=True) - if history_commands: - history_commands = history_commands.split("\n") - if len(history_commands) > 1: - # The last element of the list is the `show commands` command we - # just ran, so we need to get the second to last one - last_command = history_commands[-2] - max_command_no = int(last_command.split()[0]) - 1 - - show_command_size = 10 # 'show command' returns 10 commands - gdb_current_session_history = {} - current_command_no = gdb_history_len + 1 - - while current_command_no <= max_command_no: - cmds = gdb.execute( - "show commands " + str(current_command_no + (show_command_size // 2) + 1), - to_string=True, - ).split("\n")[:-1] - for cmd in cmds: - cmd_no, cmd = cmd.split(maxsplit=1) - cmd_no = int(cmd_no) - if cmd_no <= gdb_history_len: - continue - if current_command_no > max_command_no: - break - gdb_current_session_history[cmd_no] = cmd - current_command_no += 1 - - gdb_current_session_history = (v for (k, v) in sorted(gdb_current_session_history.items())) - gdb_current_session_history = "\n".join(gdb_current_session_history) - - issue_bugreport = ISSUE_TEMPLATE.format( - gdb_history=gdb_current_session_history, setup=current_setup - ) + issue_bugreport = ISSUE_TEMPLATE.format(setup=setup, session_history=session_history) print(issue_bugreport) please_please_submit = "Please submit the bugreport generated above at " @@ -259,3 +186,68 @@ If it is somehow unavailable, use: print(please_please_submit + github_issue_url) else: print(please_please_submit + github_issue_url) + + +def get_debugger_configuration(): + if pwndbg.dbg.is_gdblib_available(): + import gdb + + gdb_config = gdb.execute("show configuration", to_string=True).split("\n") + return "\n" + "\n".join(gdb_config) + + # LLDB: TODO/FIXME: Do we need this? + else: + return "" + + +def get_debugger_session_history(): + if pwndbg.dbg.is_gdblib_available(): + import gdb + + # get saved history size (not including current gdb session) + gdb_history_file = gdb.execute("show history filename", to_string=True) + gdb_history_file = gdb_history_file[ + gdb_history_file.index('"') + 1 : gdb_history_file.rindex('"') + ] + gdb_history_len = 0 + try: + with open(gdb_history_file) as f: + gdb_history_len = len(f.readlines()) + except FileNotFoundError: + pass + + max_command_no = 0 + history_commands = gdb.execute("show commands", to_string=True) + if history_commands: + history_commands = history_commands.split("\n") + if len(history_commands) > 1: + # The last element of the list is the `show commands` command we + # just ran, so we need to get the second to last one + last_command = history_commands[-2] + max_command_no = int(last_command.split()[0]) - 1 + + show_command_size = 10 # 'show command' returns 10 commands + gdb_current_session_history = {} + current_command_no = gdb_history_len + 1 + + while current_command_no <= max_command_no: + cmds = gdb.execute( + "show commands " + str(current_command_no + (show_command_size // 2) + 1), + to_string=True, + ).split("\n")[:-1] + for cmd in cmds: + cmd_no, cmd = cmd.split(maxsplit=1) + cmd_no = int(cmd_no) + if cmd_no <= gdb_history_len: + continue + if current_command_no > max_command_no: + break + gdb_current_session_history[cmd_no] = cmd + current_command_no += 1 + + gdb_current_session_history = (v for (k, v) in sorted(gdb_current_session_history.items())) + return "\n".join(gdb_current_session_history) + + # LLDB: TODO/FIXME: Not yet supported + else: + return ""