lldb: port version and bugreport commands + refactor (#2708)

* lldb: port version and bugreport commands + refactor

Port `version` and `bugreport` commands to LLDB.

Additionally, refactor them to provide better information and be less
bloated.

* fix lint

* fix lint

* fix lint
pull/2714/head
Disconnect3d 11 months ago committed by GitHub
parent 3fef9a1556
commit 1ce7ee36e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -696,7 +696,6 @@ def load_commands() -> None:
import pwndbg.commands.ropper import pwndbg.commands.ropper
import pwndbg.commands.segments import pwndbg.commands.segments
import pwndbg.commands.shell import pwndbg.commands.shell
import pwndbg.commands.version
import pwndbg.commands.argv import pwndbg.commands.argv
import pwndbg.commands.aslr import pwndbg.commands.aslr
@ -763,6 +762,7 @@ def load_commands() -> None:
import pwndbg.commands.tips import pwndbg.commands.tips
import pwndbg.commands.tls import pwndbg.commands.tls
import pwndbg.commands.valist import pwndbg.commands.valist
import pwndbg.commands.version
import pwndbg.commands.vmmap import pwndbg.commands.vmmap
import pwndbg.commands.windbg import pwndbg.commands.windbg
import pwndbg.commands.xinfo import pwndbg.commands.xinfo

@ -1,5 +1,5 @@
""" """
Displays gdb, python and pwndbg versions. Implements version and bugreport commands.
""" """
from __future__ import annotations from __future__ import annotations
@ -14,8 +14,6 @@ from subprocess import check_output
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from urllib.parse import quote from urllib.parse import quote
import gdb
import pwndbg import pwndbg
import pwndbg.commands import pwndbg.commands
import pwndbg.integration import pwndbg.integration
@ -23,53 +21,81 @@ from pwndbg.color import message
from pwndbg.commands import CommandCategory from pwndbg.commands import CommandCategory
def _gdb_version() -> str: def os_info():
return gdb.VERSION 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 os_info
return sys.version.replace("\n", " ")
def capstone_version(): def module_version(module):
try: try:
import capstone return __import__(module).__version__
return ".".join(map(str, capstone.cs_version()))
except ImportError: except ImportError:
return "not found" return "not found"
def unicorn_version(): def debugger_version():
try: if pwndbg.dbg.is_gdblib_available():
import unicorn import gdb
return unicorn.__version__ return f"GDB: {gdb.VERSION}"
except ImportError: else:
return "not found" return f"LLDB: {'.'.join(map(str, pwndbg.dbg_mod.lldb.LLDB_VERSION))}"
def all_versions(): def all_versions():
gdb_str = f"Gdb: {_gdb_version()}" return (
py_str = f"Python: {_py_version()}" f"Pwndbg: {pwndbg.__version__}",
pwndbg_str = 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()}" # Note: this are only available if given arch is supported by GDB
unicorn_str = f"Unicorn: {unicorn_version()}" # (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 target
return all_versions
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 = "<unavailable>"
return f"Terminal width: {width_info}, height: {height_info}\n"
@pwndbg.commands.ArgparsedCommand( @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: def version() -> None:
"""
Displays GDB, Python, and pwndbg versions.
"""
print("\n".join(map(message.system, all_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): def bugreport(run_browser=False, use_gh=False):
ISSUE_TEMPLATE = """ ISSUE_TEMPLATE = """
<!-- <!--
Before reporting a new issue, make sure that we do not have any duplicates already open. Please see if the bug isn't reported already on: https://github.com/pwndbg/pwndbg/issues
If there is one it might be good to take part in the discussion there. and take part in discussion if it was.
Please make sure you have checked that the issue persists on LATEST pwndbg version. Before reporting a new issue, make sure it happens on latest Pwndbg version.
Below is a template for BUG REPORTS. Use the template below.
Don't include it if this is a FEATURE REQUEST.
--> -->
### Description ### Description
<!-- <!--
Briefly describe the problem you are having in a few paragraphs. Describe the problem you are having in a few paragraphs.
--> -->
### Steps to reproduce ### 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. 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 ### My setup
<!-- <!--
Show us your gdb/python/pwndbg/OS/IDA Pro version (depending on your case). Show us your Pwndbg and any other relevant versions.
NOTE: We are currently testing Pwndbg only on Ubuntu installations but it should work fine on other distros as well.
This can be displayed in pwndbg through `version` command.
If it is somehow unavailable, use:
* `show version` - for gdb
* `py import sys; print(sys.version)` - for python
* pwndbg version/git commit id
--> -->
``` ```
{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") # Commented out for now: do we need this? It seems to be a bloat for GDB.
all_info = all_versions() # People rarely build custom GDB with fancy options...?
os_info = platform.system() # 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"): issue_bugreport = ISSUE_TEMPLATE.format(setup=setup, session_history=session_history)
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
)
print(issue_bugreport) print(issue_bugreport)
please_please_submit = "Please submit the bugreport generated above at " 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) print(please_please_submit + github_issue_url)
else: else:
print(please_please_submit + github_issue_url) 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 "<session history not supported on lldb yet>"

Loading…
Cancel
Save