You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pwndbg/pwndbg/commands/__init__.py

1003 lines
36 KiB
Python

"""
Pwndbg command implementations.
As well as various command-handling logic.
"""
from __future__ import annotations
import argparse
import functools
import inspect
import io
import logging
from enum import Enum
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Literal
from typing import Optional
from typing import Set
from typing import Tuple
from typing import TypeVar
from typing_extensions import ParamSpec
from typing_extensions import override
import pwndbg.aglib.heap
import pwndbg.aglib.kernel
import pwndbg.aglib.proc
import pwndbg.aglib.qemu
import pwndbg.aglib.regs
import pwndbg.color.message as message
import pwndbg.exception
from pwndbg.aglib.heap.ptmalloc import DebugSymsHeap
from pwndbg.aglib.heap.ptmalloc import GlibcMemoryAllocator
from pwndbg.aglib.heap.ptmalloc import HeuristicHeap
from pwndbg.aglib.heap.ptmalloc import SymbolUnresolvableError
log = logging.getLogger(__name__)
T = TypeVar("T")
P = ParamSpec("P")
commands: List[CommandObj] = []
command_names: Set[str] = set()
class CommandCategory(str, Enum):
START = "Start"
NEXT = "Step/Next/Continue"
CONTEXT = "Context"
PTMALLOC2 = "GLibc ptmalloc2 Heap"
JEMALLOC = "jemalloc Heap"
BREAKPOINT = "Breakpoint"
MEMORY = "Memory"
MUSL = "musl"
STACK = "Stack"
REGISTER = "Register"
PROCESS = "Process"
LINUX = "Linux/libc/ELF"
DARWIN = "Darwin/libsystem/Mach-O"
DISASS = "Disassemble"
MISC = "Misc"
KERNEL = "Kernel"
INTEGRATIONS = "Integrations"
WINDBG = "WinDbg"
PWNDBG = "Pwndbg"
SHELL = "Shell"
DEV = "Developer"
GDB_BUILTIN_COMMANDS = pwndbg.dbg.commands()
# Set in `reload` command so that we can skip double checking for registration
# of an already existing command when re-registering GDB CLI commands
# (there is no way to unregister a command in GDB 12.x)
pwndbg_is_reloading = False
if pwndbg.dbg.is_gdblib_available():
import gdb
pwndbg_is_reloading = getattr(gdb, "pwndbg_is_reloading", False)
class InvalidDebuggerError(Exception):
"""
Raised when a command is called in a debugger for which
it is disallowed.
"""
pass
class CommandFormatter(argparse.RawDescriptionHelpFormatter):
"""
The formatter_class that is passed to argparse for all
commands.
Subclassing this isn't officially supported, but there
isn't a good alternative.
"""
@override
def _get_help_string(self, action):
# Yoinked from argparse.ArgumentDefaultsHelpFormatter with
# the added ` and action.default not in (None, False)` check.
help_ = action.help
if help_ is None:
help_ = ""
if "%(default)" not in help_:
is_false_bool = (
action.type is bool or isinstance(action.default, bool)
) and not action.default
is_none = action.default is None
if action.default is not argparse.SUPPRESS and not (is_false_bool or is_none):
defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]
if action.option_strings or action.nargs in defaulting_nargs:
if action.type is str:
help_ += " (default: '%(default)s')"
else:
help_ += " (default: %(default)s)"
return help_
class CommandObj:
"""
Represents a command that can be invoked from the
debugger.
"""
builtin_override_whitelist: Set[str] = {
"up",
"down",
"search",
"pwd",
"start",
"starti",
"ignore",
}
history: Dict[int, str] = {}
def __init__(
self,
function: Callable[..., str | None],
parser: argparse.ArgumentParser,
command_name: str | None,
category: CommandCategory,
aliases: List[str],
examples: str,
notes: str,
/, # All parameters must be passed in positionally
) -> None:
assert function
self.function = function
self.command_name = command_name
if self.command_name is None:
# Take the command name from the name of the function
# which defines it, but replace '_' with '-'.
self.command_name = function.__name__.replace("_", "-")
assert "_" not in self.command_name and "Use '-' instead of '_' in command names."
assert self.command_name not in command_names and "Command already exists."
assert (
not (
self.command_name in GDB_BUILTIN_COMMANDS
and self.command_name not in CommandObj.builtin_override_whitelist
and not pwndbg_is_reloading
)
and "Cannot override non-whitelisted built-in command."
)
assert category
self.category = category
self.aliases = aliases
self.examples = examples.strip()
self.notes = notes.strip()
assert parser
self.parser = parser
# Sets self.help_str and self.description (among other stuff).
self.initialize_parser()
# Let the debugger and pwndbg global state know about it.
self.register_command()
# For commands like hexdump where you get new output from
# continuous invocations.
self.repeat = False
def register_command(self):
"""
Register this object command with the underlying debugger
and update pwndbg global state to know about this command.
"""
def _handler(_debugger, arguments, is_interactive):
self.invoke(arguments, is_interactive)
# Keep a handle to the command and its aliases so we can
# easily remove them if necessary (not supported with GDB).
self.handles = []
# Tell the debugger about the command...
self.handles.append(pwndbg.dbg.add_command(self.command_name, _handler, self.help_str))
# ...and all of its aliases.
for alias in self.aliases:
self.handles.append(pwndbg.dbg.add_command(alias, _handler, self.help_str))
command_names.add(self.command_name)
commands.append(self)
@staticmethod
def has_notes_string(text: str) -> bool:
return any(nt in text.lower() for nt in ("note:", "notes:"))
@staticmethod
def has_examples_string(text: str) -> bool:
return any(ex in text.lower() for ex in ("example:", "examples:"))
def initialize_parser(self):
# Set parser.prog so the help is generated properly.
self.parser.prog = self.command_name
# We want to run all integer and otherwise-unspecified arguments
# through fix() so that GDB parses it.
# FIXME: this is weird
def process_actions(actions):
"""Recursively process actions, including subparsers"""
for action in actions:
if isinstance(action, argparse._SubParsersAction):
action.type = str
# Recursively process each subparser's actions
for subparser in action.choices.values():
process_actions(subparser._actions)
if action.dest == "help":
continue
if action.type is int:
action.type = fix_int_reraise_arg
elif action.type is None:
action.type = fix_reraise_arg
process_actions(self.parser._actions)
assert (
self.parser.formatter_class is argparse.HelpFormatter
and "All pwndbg commands should use the same formatter."
)
self.parser.formatter_class = CommandFormatter
# Used by `pwndbg [filter]`
assert (
self.parser.description
and self.parser.description.strip()
and "A command must contain a description."
)
self.description = self.parser.description = self.parser.description.strip()
assert (
not self.has_examples_string(self.description)
and "Put examples into pwndbg.commands.Command(examples=your_example)."
)
assert (
not self.has_notes_string(self.description)
and "Put notes into pwndbg.commands.Command(notes=your_note)."
)
# Build the actual epilog from the examples, notes and passed epilog.
self.epilog = ""
self.pure_epilog = ""
if self.examples:
assert (
not self.has_examples_string(self.examples)
and "No need, `Examples:` is added automatically."
)
# Not putting '\n' in the notice() so .strip() works properly.
self.epilog += "\n" + message.notice("Examples:") + "\n"
self.epilog += self.examples + "\n"
if self.notes:
assert (
not self.has_notes_string(self.notes)
and "No need, `Notes:` is added automatically."
)
self.epilog += "\n" + message.notice("Notes:") + "\n"
self.epilog += self.notes + "\n"
if self.parser.epilog:
self.pure_epilog = self.parser.epilog.strip()
assert (
not self.has_examples_string(self.pure_epilog)
and "Put examples into pwndbg.commands.Command(examples=your_example)."
)
assert (
not self.has_notes_string(self.pure_epilog)
and "Put notes into pwndbg.commands.Command(notes=your_note)."
)
self.epilog += "\n" + self.pure_epilog + "\n"
if self.aliases:
alias_txt = "Alias" + ("es" if len(self.aliases) > 1 else "") + ": "
self.epilog += "\n" + message.notice(alias_txt)
self.epilog += ", ".join(self.aliases) + "\n"
# Update the parser so the help is correctly generated.
self.parser.epilog = self.epilog = self.epilog.strip()
# Generate command help (after stripping the parser's variables
# and defining a formatter).
self.help_str = self.parser.format_help()
def invoke(self, argument: str, from_tty: bool) -> None:
"""Invoke the command with an argument string"""
if not pwndbg.dbg.selected_inferior():
log.error("Pwndbg commands require a target binary to be selected")
return
# Put the arguments through the debugger
try:
arg_list = pwndbg.dbg.lex_args(argument)
except (TypeError, pwndbg.dbg_mod.Error):
pwndbg.exception.handle(self.function.__name__)
return
# Put the arguments through argparse
try:
kwargs = vars(self.parser.parse_args(arg_list))
except SystemExit:
# argparse complained about incorrect usage or printed
# help and exited. Either way the appropriate message
# is already printed and we shouldn't call the function.
return
try:
self.repeat = self.check_repeated(argument, from_tty)
# Call this object, same as `self(**kwargs)` but faster.
self.__call__(**kwargs)
finally:
self.repeat = False
def check_repeated(self, argument: str, from_tty: bool) -> bool:
"""
Keep a record of all commands which come from the TTY.
Returns:
True if this command was executed by the user just hitting "enter".
"""
# Don't care unless it's interactive use
if not from_tty:
return False
last_line = pwndbg.dbg.history(1)
# No history
if not last_line:
return False
number, command = last_line[-1]
# A new command was entered by the user
if number not in CommandObj.history:
CommandObj.history[number] = command
return False
# Somehow the command is different than we got before?
if not command.endswith(argument):
return False
return True
def __call__(self, *args: Any, **kwargs: Any) -> str | None:
try:
return self.function(*args, **kwargs)
except TypeError:
print(f"{self.command_name}: {self.description}")
pwndbg.exception.handle(self.function.__name__)
except ConnectionRefusedError:
print(message.error("Connection Refused Exception."))
print(message.hint("Did an integration provider die?"), end="")
# If yes, the resulting state can be really messy.
if pwndbg.integration.provider_name != "none":
print(
message.hint(
f" Automatically disabled {pwndbg.integration.provider_name} integration."
)
)
pwndbg.integration.provider.disable()
else:
print()
except Exception:
pwndbg.exception.handle(self.function.__name__)
return None
class Command:
"""
Parametrized decorator for functions that serve as pwndbg commands.
Always use this to decorate your commands.
"""
def __init__(
self,
parser_or_desc: argparse.ArgumentParser | str,
*, # All further parameters are not positional
category: CommandCategory,
command_name: str | None = None,
aliases: List[str] = [],
examples: str = "",
notes: str = "",
only_debuggers: Set[pwndbg.dbg_mod.DebuggerType] = None,
exclude_debuggers: Set[pwndbg.dbg_mod.DebuggerType] = None,
) -> None:
# Setup an ArgumentParser even if we were only passed a description.
if isinstance(parser_or_desc, str):
self.parser = argparse.ArgumentParser(description=parser_or_desc)
else:
assert isinstance(parser_or_desc, argparse.ArgumentParser)
self.parser = parser_or_desc
self.category = category
self.command_name = command_name
self.aliases = aliases
self.examples = examples
self.notes = notes
self.only_debuggers = only_debuggers
self.exclude_debuggers = exclude_debuggers
def __call__(self, function: Callable[..., Any]) -> CommandObj:
# Since this is the __call__ of a parametrized decorator, it is
# invoked during decoration, and it must return a callable object
# i.e. the "real" decorator of the function.
# If this command is not valid for this debugger, do not even
# pass it to ComandObj to be registered with the debugger API.
# Also make sure it raises an error if it is called from the code.
if self.only_debuggers is not None and pwndbg.dbg.name() not in self.only_debuggers:
def decorator(*args, **kwargs):
raise InvalidDebuggerError(
f"This command cannot be used in {pwndbg.dbg.name()}.\n"
f"It is only valid for {self.only_debuggers}."
)
return decorator # type: ignore[return-value]
if self.exclude_debuggers is not None and pwndbg.dbg.name() in self.exclude_debuggers:
def decorator(*args, **kwargs):
raise InvalidDebuggerError(
f"This command cannot be used in {pwndbg.dbg.name()}.\n"
f"It is invalid for {self.exclude_debuggers}."
)
return decorator # type: ignore[return-value]
# Since CommandObj has __call__ defined, an instance of it is a
# callable object (which essentially decorates the function).
return CommandObj(
function,
self.parser,
self.command_name,
self.category,
self.aliases,
self.examples,
self.notes,
)
def fix(
arg: pwndbg.dbg_mod.Value | str, sloppy: bool = False, quiet: bool = True, reraise: bool = False
) -> str | pwndbg.dbg_mod.Value | None:
"""Fix a single command-line argument coming from the CLI.
Arguments:
arg: Original string representation (e.g. '0', '$rax', '$rax+44')
sloppy: If ``arg`` cannot be evaluated, return ``arg``. (default: False)
quiet: If an error occurs, suppress it. (default: True)
reraise: If an error occurs, raise the exception. (default: False)
Returns:
Ideally a ``Value`` object. May return a ``str`` if ``sloppy==True``.
May return ``None`` if ``sloppy == False and reraise == False``.
"""
if isinstance(arg, pwndbg.dbg_mod.Value):
return arg
frame = pwndbg.dbg.selected_frame()
target: pwndbg.dbg_mod.Frame | pwndbg.dbg_mod.Process = (
frame if frame else pwndbg.dbg.selected_inferior()
)
assert target, "Reached command expression evaluation with no frame or inferior"
# Try to evaluate the expression in the local, or, failing that, global
# context.
try:
return target.evaluate_expression(arg)
except Exception:
pass
ex = None
try:
# This will fail if gdblib is not available. While the next check
# alleviates the need for this call, it's not really equivalent, and
# we'll need a debugger-agnostic version of regs.fix() if we want to
# completely get rid of this call. We can't do that now because there's
# no debugger-agnostic architecture functions. Those will come later.
#
# TODO: Port architecutre functions and `pwndbg.gdblib.regs.fix` to debugger-agnostic API and remove this.
arg = pwndbg.aglib.regs.fix(arg)
return target.evaluate_expression(arg)
except Exception as e:
ex = e
# If that fails, try to treat the argument as the name of a register, and
# see if that yields anything.
if frame:
regs = frame.regs()
arg = arg.strip()
if arg.startswith("$"):
arg = arg[1:]
reg = regs.by_name(arg)
if reg:
return reg
# If both fail, check whether we want to print or re-raise the error we
# might've gotten from `evaluate_expression`.
if ex:
if not quiet:
print(ex)
if reraise:
raise ex
if sloppy:
return arg
return None
def fix_reraise(*a, **kw) -> str | pwndbg.dbg_mod.Value | None:
# Type error likely due to https://github.com/python/mypy/issues/6799
return fix(*a, reraise=True, **kw) # type: ignore[misc]
def fix_reraise_arg(arg) -> pwndbg.dbg_mod.Value:
"""fix_reraise wrapper for evaluating command arguments"""
try:
# Will always return pwndbg.dbg_mod.Value because
# sloppy=False (not str) and reraise=True (not None)
fixed = fix(arg, sloppy=False, quiet=True, reraise=True)
assert isinstance(fixed, pwndbg.dbg_mod.Value)
return fixed
except pwndbg.dbg_mod.Error as dbge:
raise argparse.ArgumentTypeError(f"debugger couldn't resolve argument '{arg}': {dbge}")
def fix_int(*a, **kw) -> int:
return int(fix(*a, **kw))
def fix_int_reraise(*a, **kw) -> int:
return fix_int(*a, reraise=True, **kw)
def fix_int_reraise_arg(arg) -> int:
"""fix_int_reraise wrapper for evaluating command arguments"""
try:
fixed = fix_reraise_arg(arg)
return int(fixed)
except pwndbg.dbg_mod.Error as e:
raise argparse.ArgumentTypeError(
f"couldn't convert '{arg}' ({fixed.type.name_to_human_readable}) to int: {e}"
)
def func_name(function: Callable[P, T]) -> str:
return function.__name__.replace("_", "-")
def OnlyWhenLocal(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWhenLocal(*a: P.args, **kw: P.kwargs) -> Optional[T]:
if not pwndbg.aglib.remote.is_remote():
return function(*a, **kw)
msg = f'The "remote" target does not support "{function.__name__}".'
if pwndbg.dbg.is_gdblib_available():
msg += ' Try "help target" or "continue".'
log.error(msg)
return None
return _OnlyWhenLocal
def OnlyWithFile(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWithFile(*a: P.args, **kw: P.kwargs) -> Optional[T]:
if pwndbg.aglib.proc.exe:
return function(*a, **kw)
else:
if pwndbg.aglib.qemu.is_qemu():
log.error("Could not determine the target binary on QEMU.")
else:
log.error(f"{func_name(function)}: There is no file loaded.")
return None
return _OnlyWithFile
def OnlyWhenQemuKernel(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWhenQemuKernel(*a: P.args, **kw: P.kwargs) -> Optional[T]:
if pwndbg.aglib.qemu.is_qemu_kernel():
return function(*a, **kw)
else:
log.error(
f"{func_name(function)}: This command may only be run when debugging the Linux kernel in QEMU."
)
return None
return _OnlyWhenQemuKernel
def OnlyWhenUserspace(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWhenUserspace(*a: P.args, **kw: P.kwargs) -> Optional[T]:
if not pwndbg.aglib.qemu.is_qemu_kernel():
return function(*a, **kw)
else:
log.error(
f"{func_name(function)}: This command may only be run when not debugging a QEMU kernel target."
)
return None
return _OnlyWhenUserspace
def OnlyWithKernelDebugInfo(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWithKernelDebugInfo(*a: P.args, **kw: P.kwargs) -> Optional[T]:
if pwndbg.aglib.kernel.has_debug_info():
return function(*a, **kw)
else:
log.error(
f"{func_name(function)}: This command may only be run when debugging a Linux kernel with debug info."
)
return None
return _OnlyWithKernelDebugInfo
def OnlyWithKernelSymbols(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWithKernelSymbols(*a: P.args, **kw: P.kwargs) -> Optional[T]:
if pwndbg.aglib.kernel.has_debug_symbols():
return function(*a, **kw)
else:
log.error(
f"{func_name(function)}: This command may only be run when debugging a Linux kernel with symbols.\n"
+ message.hint(
"Check out vmlinux-to-elf to get them easily (https://github.com/marin-m/vmlinux-to-elf) or compile the kernel yourself."
)
)
return None
return _OnlyWithKernelSymbols
def OnlyWhenPagingEnabled(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWhenPagingEnabled(*a: P.args, **kw: P.kwargs) -> Optional[T]:
if pwndbg.aglib.kernel.paging_enabled():
return function(*a, **kw)
else:
log.error(
f"{func_name(function)}: This command may only be run when paging is enabled."
)
return None
return _OnlyWhenPagingEnabled
def OnlyWhenRunning(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWhenRunning(*a: P.args, **kw: P.kwargs) -> Optional[T]:
# TODO: Properly support OnlyWhenRunning without `gdblib`.
if pwndbg.aglib.proc.alive:
return function(*a, **kw)
else:
log.error(f"{func_name(function)}: The program is not being run.")
return None
return _OnlyWhenRunning
def OnlyWithTcache(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWithTcache(*a: P.args, **kw: P.kwargs) -> Optional[T]:
assert isinstance(pwndbg.aglib.heap.current, GlibcMemoryAllocator)
if pwndbg.aglib.heap.current.has_tcache():
return function(*a, **kw)
else:
log.error(
f"{func_name(function)}: This version of GLIBC was not compiled with tcache support."
)
return None
return _OnlyWithTcache
def OnlyWhenHeapIsInitialized(function: Callable[P, T]) -> Callable[P, Optional[T]]:
@functools.wraps(function)
def _OnlyWhenHeapIsInitialized(*a: P.args, **kw: P.kwargs) -> Optional[T]:
if pwndbg.aglib.heap.current is not None and pwndbg.aglib.heap.current.is_initialized():
return function(*a, **kw)
else:
log.error(f"{func_name(function)}: Heap is not initialized yet.")
return None
return _OnlyWhenHeapIsInitialized
def _try2run_heap_command(function: Callable[P, T], *a: P.args, **kw: P.kwargs) -> T | None:
e = log.error
w = log.warning
# Note: We will still raise the error for developers when exception-* is set to "on"
try:
return function(*a, **kw)
except SymbolUnresolvableError as err:
e(f"{func_name(function)}: Fail to resolve the symbol: `{err.symbol}`")
if "thread_arena" == err.symbol:
w(
"You are probably debugging a multi-threaded target without debug symbols, so we failed to determine which arena is used by the current thread.\n"
"To resolve this issue, you can use the `arenas` command to list all arenas, and use `set thread-arena <addr>` to set the current thread's arena address you think is correct.\n"
)
else:
w(
f"You can try to determine the libc symbols addresses manually and set them appropriately. For this, see the `heap-config` command output and set the config for `{err.symbol}`."
)
if pwndbg.config.exception_verbose or pwndbg.config.exception_debugger:
raise err
pwndbg.exception.inform_verbose_and_debug()
except Exception as err:
e(f"{func_name(function)}: An unknown error occurred when running this command.")
if isinstance(pwndbg.aglib.heap.current, HeuristicHeap):
w(
"Maybe you can try to determine the libc symbols addresses manually, set them appropriately and re-run this command. For this, see the `heap-config` command output and set the `main_arena`, `mp_`, `global_max_fast`, `tcache` and `thread_arena` addresses."
)
else:
w("You can try `set resolve-heap-via-heuristic force` and re-run this command.\n")
if pwndbg.config.exception_verbose or pwndbg.config.exception_debugger:
raise err
pwndbg.exception.inform_verbose_and_debug()
return None
def OnlyWithResolvedHeapSyms(function: Callable[P, T]) -> Callable[P, T | None]:
@functools.wraps(function)
def _OnlyWithResolvedHeapSyms(*a: P.args, **kw: P.kwargs) -> T | None:
e = log.error
w = log.warning
if (
isinstance(pwndbg.aglib.heap.current, HeuristicHeap)
and pwndbg.config.resolve_heap_via_heuristic == "auto"
and DebugSymsHeap().can_be_resolved()
):
# In auto mode, we will try to use the debug symbols if possible
pwndbg.aglib.heap.current = DebugSymsHeap()
if (
pwndbg.aglib.heap.current is not None
and isinstance(pwndbg.aglib.heap.current, GlibcMemoryAllocator)
and pwndbg.aglib.heap.current.can_be_resolved()
):
return _try2run_heap_command(function, *a, **kw)
else:
static = not pwndbg.dbg.selected_inferior().is_dynamically_linked()
if (
isinstance(pwndbg.aglib.heap.current, DebugSymsHeap)
and pwndbg.config.resolve_heap_via_heuristic == "auto"
):
# In auto mode, if the debug symbols are not enough, we will try to use the heuristic if possible
heuristic_heap = HeuristicHeap()
if heuristic_heap.can_be_resolved():
pwndbg.aglib.heap.current = heuristic_heap
w(
"pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.\n"
"This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.\n"
)
return _try2run_heap_command(function, *a, **kw)
elif static:
e(
"Can't find GLIBC version required for this command to work since this is a statically linked binary"
)
w(
"Please set the GLIBC version you think the target binary was compiled (using `set glibc <version>` command; e.g. 2.32) and re-run this command."
)
else:
e(
"Can't find GLIBC version required for this command to work, maybe is because GLIBC is not loaded yet."
)
w(
"If you believe the GLIBC is loaded or this is a statically linked binary. "
"Please set the GLIBC version you think the target binary was compiled (using `set glibc <version>` command; e.g. 2.32) and re-run this command"
)
elif (
isinstance(pwndbg.aglib.heap.current, DebugSymsHeap)
and pwndbg.config.resolve_heap_via_heuristic == "force"
):
e(
"You are forcing to resolve the heap symbols via heuristic, but we cannot resolve the heap via the debug symbols."
)
w("Use `set resolve-heap-via-heuristic auto` and re-run this command.")
elif pwndbg.glibc.get_version() is None:
if static:
e("Can't resolve the heap since the GLIBC version is not set.")
w(
"Please set the GLIBC version you think the target binary was compiled (using `set glibc <version>` command; e.g. 2.32) and re-run this command."
)
else:
e(
"Can't find GLIBC version required for this command to work, maybe is because GLIBC is not loaded yet."
)
w(
"If you believe the GLIBC is loaded or this is a statically linked binary. "
"Please set the GLIBC version you think the target binary was compiled (using `set glibc <version>` command; e.g. 2.32) and re-run this command"
)
else:
# Note: Should not see this error, but just in case
e("An unknown error occurred when resolved the heap.")
pwndbg.exception.inform_report_issue(
"An unknown error occurred when resolved the heap"
)
return None
return _OnlyWithResolvedHeapSyms
def sloppy_gdb_parse(s: str) -> int | str:
"""
This function should be used as ``argparse.ArgumentParser`` .add_argument method's `type` helper.
This makes the type being parsed as gdb value and if that parsing fails,
a string is returned.
:param s: String.
:return: Whatever gdb.parse_and_eval returns or string.
"""
frame = pwndbg.dbg.selected_frame()
target: pwndbg.dbg_mod.Frame | pwndbg.dbg_mod.Process = (
frame if frame else pwndbg.dbg.selected_inferior()
)
assert target, "Reached command expression evaluation with no frame or inferior"
try:
val = pwndbg.aglib.symbol.lookup_symbol(s) or target.evaluate_expression(s)
if val.type.code == pwndbg.dbg_mod.TypeCode.FUNC:
return int(val.address)
return int(val)
except (TypeError, pwndbg.dbg_mod.Error):
return s
def AddressExpr(s: str) -> int:
"""
Parses an address expression. Returns an int.
"""
val = sloppy_gdb_parse(s)
if not isinstance(val, int):
raise argparse.ArgumentTypeError(f"Incorrect address (or GDB expression): {s}")
return val
def HexOrAddressExpr(s: str) -> int:
"""
Parses string as hexadecimal int or an address expression. Returns an int.
(e.g. '1234' will return 0x1234)
"""
try:
return int(s, 16)
except ValueError:
return AddressExpr(s)
def load_commands() -> None:
# pylint: disable=import-outside-toplevel
import pwndbg.dbg
if pwndbg.dbg.is_gdblib_available():
import pwndbg.commands.ai
import pwndbg.commands.attachp
import pwndbg.commands.binja_functions
import pwndbg.commands.branch
import pwndbg.commands.cymbol
import pwndbg.commands.got
import pwndbg.commands.got_tracking
import pwndbg.commands.ptmalloc2_tracking
import pwndbg.commands.ida
import pwndbg.commands.ignore
import pwndbg.commands.ipython_interactive
import pwndbg.commands.killthreads
import pwndbg.commands.peda
import pwndbg.commands.reload
import pwndbg.commands.ropper
import pwndbg.commands.segments
import pwndbg.commands.argv
import pwndbg.commands.aslr
import pwndbg.commands.asm
import pwndbg.commands.auxv
import pwndbg.commands.binder
import pwndbg.commands.binja
import pwndbg.commands.buddydump
import pwndbg.commands.canary
import pwndbg.commands.checksec
import pwndbg.commands.comments
import pwndbg.commands.commpage
import pwndbg.commands.config
import pwndbg.commands.context
import pwndbg.commands.cpsr
import pwndbg.commands.cyclic
import pwndbg.commands.dev
import pwndbg.commands.distance
import pwndbg.commands.dt
import pwndbg.commands.dumpargs
import pwndbg.commands.elf
import pwndbg.commands.flags
import pwndbg.commands.gdt
import pwndbg.commands.ghidra
import pwndbg.commands.godbg
import pwndbg.commands.hex2ptr
import pwndbg.commands.hexdump
import pwndbg.commands.hijack_fd
import pwndbg.commands.integration
import pwndbg.commands.jemalloc
import pwndbg.commands.kbase
import pwndbg.commands.kbpf
import pwndbg.commands.kchecksec
import pwndbg.commands.kcmdline
import pwndbg.commands.kconfig
import pwndbg.commands.kcurrent
import pwndbg.commands.kdmabuf
import pwndbg.commands.kdmesg
import pwndbg.commands.klookup
import pwndbg.commands.kmem_trace
import pwndbg.commands.kmod
import pwndbg.commands.knft
import pwndbg.commands.ksyscalls
import pwndbg.commands.ktask
import pwndbg.commands.kversion
import pwndbg.commands.leakfind
import pwndbg.commands.libcinfo
import pwndbg.commands.linkmap
import pwndbg.commands.mallocng
import pwndbg.commands.memoize
import pwndbg.commands.misc
import pwndbg.commands.mmap
import pwndbg.commands.mprotect
import pwndbg.commands.msr
import pwndbg.commands.nearpc
import pwndbg.commands.next
import pwndbg.commands.onegadget
import pwndbg.commands.p2p
import pwndbg.commands.paging
import pwndbg.commands.parse_seccomp
import pwndbg.commands.patch
import pwndbg.commands.pie
import pwndbg.commands.plist
import pwndbg.commands.probeleak
import pwndbg.commands.procinfo
import pwndbg.commands.profiler
import pwndbg.commands.ptmalloc2
import pwndbg.commands.radare2
import pwndbg.commands.retaddr
import pwndbg.commands.rizin
import pwndbg.commands.rop
import pwndbg.commands.saved_register_frames
import pwndbg.commands.search
import pwndbg.commands.sigreturn
import pwndbg.commands.slab
import pwndbg.commands.spray
import pwndbg.commands.start
import pwndbg.commands.strings
import pwndbg.commands.telescope
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
import pwndbg.commands.xor