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/dbg/__init__.py

1194 lines
38 KiB
Python

"""
The abstracted debugger interface.
"""
from __future__ import annotations
import contextlib
from enum import Enum
from typing import Any
from typing import Awaitable
from typing import Callable
from typing import Coroutine
from typing import Generator
from typing import Iterator
from typing import List
from typing import Literal
from typing import Sequence
from typing import Tuple
from typing import TypedDict
from typing import TypeVar
import pwndbg.lib.memory
from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
from pwndbg.lib.arch import ArchDefinition
dbg: Debugger = None
T = TypeVar("T")
@contextlib.contextmanager
def selection(target: T, get_current: Callable[[], T], select: Callable[[T], None]):
"""
Debuggers have global state. Many of our queries require that we select a
given object globally before we make them. When doing that, we must always
be careful to return selection to its previous state before exiting. This
class automatically manages the selection of a single object type.
Upon entrace to the `with` block, the element given by `target` will be
compared to the object returned by calling `get_current`. If they
compare different, the value previously returned by `get_current` is
saved, and the element given by `target` will be selected by passing it
as an argument to `select`, and, after execution leaves the `with`
block, the previously saved element will be selected in the same fashion
as the first element.
If the elements don't compare different, this is a no-op.
"""
current = get_current()
restore = False
if current != target:
select(target)
restore = True
try:
yield
finally:
if restore:
select(current)
class Error(Exception):
pass
class DisassembledInstruction(TypedDict):
addr: int
asm: str
length: int
class DebuggerType(Enum):
GDB = 1
LLDB = 2
class StopPoint:
"""
The handle to either an insalled breakpoint or watchpoint.
May be used in a `with` statement, in which case the stop point is
automatically removed at the end of the statement. This allows for easy
implementation of temporary breakpoints.
"""
def remove(self) -> None:
"""
Removes the breakpoint associated with this handle.
"""
raise NotImplementedError()
def set_enabled(self, enabled: bool) -> None:
"""
Enables or disables this breakpoint.
"""
raise NotImplementedError()
def __enter__(self) -> StopPoint:
return self
def __exit__(self, exc_type, exc_value, traceback) -> None:
"""
Automatic breakpoint removal.
"""
self.remove()
class BreakpointLocation:
"""
This is the location specification for a breakpoint.
"""
address: int
def __init__(self, address: int):
self.address = address
def __eq__(self, other: object) -> bool:
if isinstance(other, BreakpointLocation):
return self.address == other.address
if isinstance(other, int):
return self.address == other
return False
class WatchpointLocation:
"""
This is the location specification for a watchpoint.
"""
address: int
size: int
watch_read: bool
watch_write: bool
def __init__(self, address: int, size: int, watch_read: bool, watch_write: bool):
self.address = address
self.size = size
assert watch_read or watch_write, "Watchpoints must watch at least one of reads or writes"
self.watch_read = watch_read
self.watch_write = watch_write
class Registers:
"""
A handle to the register values in a frame.
"""
def by_name(self, name: str) -> Value | None:
"""
Gets the value of a register if it exists, None otherwise.
"""
raise NotImplementedError()
class SymbolLookupType(Enum):
"""
Enum representing types of symbol lookups for filtering symbol searches.
Attributes:
- ANY: Represents searching for any symbol type (default).
- FUNCTION: Represents searching specifically for function symbols.
- VARIABLE: Represents searching specifically for variable symbols.
"""
ANY = 1
FUNCTION = 2
VARIABLE = 3
class Frame:
def lookup_symbol(
self,
name: str,
*,
type: SymbolLookupType = SymbolLookupType.ANY,
) -> Value | None:
"""
Looks up and returns the address of a symbol in current frame by its name.
Parameters:
- name (str): The name of the symbol to look up.
- type (SymbolLookupType, optional): The type of symbol to search for. Defaults
to SymbolLookupType.ANY.
Returns:
- pwndbg.dbg_mod.Value | None: The value of the symbol if found, or None if not found.
Raises:
- pwndbg.dbg_mod.Error: If symbol name contains invalid characters
"""
raise NotImplementedError()
def evaluate_expression(self, expression: str, lock_scheduler: bool = False) -> Value:
"""
Evaluate the given expression in the context of this frame, and
return a `Value`.
# `lock_scheduler`
Additionally, callers of this function might specify that they want to
enable scheduler locking during the evaluation of this expression. This
is a GDB-only option, and is intended for cases in which the result
would be incorrect without it enabled, when running in GDB. Other
debuggers should ignore this parameter.
"""
raise NotImplementedError()
def regs(self) -> Registers:
"""
Access the values of the registers in this frame.
"""
raise NotImplementedError()
def reg_write(self, name: str, val: int) -> bool:
"""
Sets the value of the register with the given name to the given value.
Returns true if the register exists, false othewise. Throws an exception
if the register exists but cannot be written to.
"""
raise NotImplementedError()
def pc(self) -> int:
"""
The value of the program counter for this frame.
"""
raise NotImplementedError()
def sp(self) -> int:
"""
The value of the stack pointer for this frame.
"""
raise NotImplementedError()
def parent(self) -> Frame | None:
"""
The parent frame of this frame, if it exists.
"""
raise NotImplementedError()
def child(self) -> Frame | None:
"""
The child frame of this frame, if it exists.
"""
raise NotImplementedError()
def sal(self) -> Tuple[str, int] | None:
"""
The filename of the source code file associated with this frame, and the
line number associated with it, if available.
"""
raise NotImplementedError()
def __eq__(self, rhs: object) -> bool:
"""
Whether this frame is the same as the given frame. Two frames are the
same if they point to the same stack frame and have the same execution
context.
"""
raise NotImplementedError()
class Thread:
@contextlib.contextmanager
def bottom_frame(self) -> Iterator[Frame]:
"""
Frame at the bottom of the call stack for this thread.
"""
raise NotImplementedError()
def ptid(self) -> int | None:
"""
The PTID of this thread, if available.
"""
raise NotImplementedError()
def index(self) -> int:
"""
The unique index of this thread from the perspective of the debugger.
"""
raise NotImplementedError()
class MemoryMap:
"""
A wrapper around a sequence of memory ranges
"""
def is_qemu(self) -> bool:
"""
Returns whether this memory map was generated from a QEMU target.
"""
raise NotImplementedError()
def ranges(self) -> Sequence[pwndbg.lib.memory.Page]:
"""
Returns all ranges in this memory map.
"""
raise NotImplementedError()
class ExecutionController:
def single_step(self) -> Awaitable[None]:
"""
Steps to the next instruction.
Throws `CancelledError` if a breakpoint or watchpoint is hit, the program
exits, or if any other unexpected event that diverts execution happens
while fulfulling the step.
"""
raise NotImplementedError()
def cont(self, until: StopPoint) -> Awaitable[None]:
"""
Continues execution until the given breakpoint or whatchpoint is hit.
Throws `CancelledError` if a breakpoint or watchpoint is hit that is not
the one given in `until`, the program exits, or if any other unexpected
event happens.
"""
raise NotImplementedError()
class Process:
def threads(self) -> List[Thread]:
"""
Returns a list containing the threads in this process.
"""
raise NotImplementedError()
def pid(self) -> int | None:
"""
Returns the process ID of this process if it is alive.
"""
raise NotImplementedError()
def alive(self) -> bool:
"""
Returns whether this process is alive.
"""
raise NotImplementedError()
def stopped_with_signal(self) -> bool:
"""
Returns whether this process was stopped by a signal.
"""
raise NotImplementedError()
def evaluate_expression(self, expression: str) -> Value:
"""
Evaluate the given expression in the context of the current process, and
return a `Value`.
"""
raise NotImplementedError()
def vmmap(self) -> MemoryMap:
"""
Returns the virtual memory map of this process.
"""
raise NotImplementedError()
def read_memory(self, address: int, size: int, partial: bool = False) -> bytearray:
"""
Reads the requested number of bytes from the address given in the memory
space of this process. Will read as many bytes as possible starting at
that location, and returns how many were read.
Throws an exception if reading fails and partial is False.
"""
raise NotImplementedError()
def write_memory(self, address: int, data: bytearray, partial: bool = False) -> int:
"""
Writes as many bytes from the given data buffer as possible into the
given address in the memory space of this process.
Throws an exception if writing fails and partial is False.
"""
raise NotImplementedError()
def find_in_memory(
self,
pattern: bytearray,
start: int,
size: int,
align: int,
max_matches: int = -1,
step: int = -1,
) -> Generator[int, None, None]:
"""
Searches for a bit pattern in the memory space of the process. The bit
pattern can be searched for in a given memory range, and with a given
alignment. The maximum number of matches that will be generated is
given by `max_matches`. A value of `max_matches` of `-1` will generate
all matches.
"""
raise NotImplementedError()
def is_remote(self) -> bool:
"""
Returns whether this process is a remote process connected to using the
GDB remote debugging protocol.
"""
raise NotImplementedError()
def send_remote(self, packet: str) -> bytes:
"""
Sends the given packet to the GDB remote debugging protocol server.
Should only be called if `is_remote()` is true.
"""
raise NotImplementedError()
def send_monitor(self, cmd: str) -> str:
"""
Sends the given monitor command to the GDB remote debugging protocol
server. Should only be called if `is_remote()` is true.
"""
raise NotImplementedError()
def download_remote_file(self, remote_path: str, local_path: str) -> None:
"""
Downloads the given file from the remote host and saves it to the local
given path. Should only be called if `is_remote()` is true.
"""
raise NotImplementedError()
def create_value(self, value: int, type: Type | None = None) -> Value:
"""
Create a new value in the context of this process, with the given value
and, optionally, type. If no type is provided, one will be chosen
automatically.
"""
raise NotImplementedError()
# We'll likely have to expand this into a Symbol class and change this to a
# `symbol_at_address` function later on.
def symbol_name_at_address(self, address: int) -> str | None:
"""
Returns the name of the symbol at the given address in the program, if
one exists.
"""
raise NotImplementedError()
def lookup_symbol(
self,
name: str,
*,
prefer_static: bool = False,
type: SymbolLookupType = SymbolLookupType.ANY,
objfile_endswith: str | None = None,
) -> Value | None:
"""
Looks up and returns the address of a symbol by its name.
Parameters:
- name (str): The name of the symbol to look up.
- prefer_static (bool, optional): If True, prioritize symbols in the static block,
if supported by the debugger. Defaults to False.
- type (SymbolLookupType, optional): The type of symbol to search for. Defaults
to SymbolLookupType.ANY.
- objfile_endswith (str | None, optional): If specified, limits the search to the
first object file whose name ends with the provided string.
Returns:
- pwndbg.dbg_mod.Value | None: The value of the symbol if found, or None if not found.
Raises:
- pwndbg.dbg_mod.Error: If no object file matching the `objfile_endswith` pattern is found.
"""
raise NotImplementedError()
# There is an interesting counterpart to this method that exists at the
# module level. Depending on how we want to implement support for multiple
# modules, it might be interesting to repeat it there.
def types_with_name(self, name: str) -> Sequence[Type]:
"""
Returns a list of all types in this process that match the given name.
"""
raise NotImplementedError()
def arch(self) -> ArchDefinition:
"""
The default architecture of this process.
"""
raise NotImplementedError()
def break_at(
self,
location: BreakpointLocation | WatchpointLocation,
stop_handler: Callable[[StopPoint], bool] | None = None,
internal: bool = False,
) -> StopPoint:
"""
Install a breakpoint or watchpoint at the given location.
The type of the location determines whether the newly created object
is a watchpoint or a breakpoint. `BreakpointLocation` locations yield
breakpoints, while `WatchpointLocation` locations yield watchpoints.
Aditionally, one may specify a stop handler function, to be run when
the breakpoint or whatchpoint is hit, and that determines whether
execution should stop. With a return value of `True` being interpreted
as a signal to stop, and a return value of `False` being interpreted as
a signal to continue execution. The extent of the actions that may be
taken during the stop handler is determined by the debugger.
Marking a breakpoint or watchpoint as `internal` hints to the
implementation that the created breakpoint or watchpoint should not be
directly nameable by the user, and that it should not print any messages
upon being triggered. Implementations should try to honor this hint,
but they are not required to in case honoring it is either not possible
or comes at a significant impact to performance.
This function returns a handle to the newly created breakpoint or
watchpoint.
"""
raise NotImplementedError()
# This is a fairly lazy solution. We would ideally support a more robust way
# to query for ABIs, but Pwndbg currely only uses `show osabi` in GDB to
# check for whether the target is running under Linux, so we only implement
# that check.
def is_linux(self) -> bool:
"""
Returns whether the current ABI is GNU/Linux.
"""
raise NotImplementedError()
def disasm(self, address: int) -> DisassembledInstruction | None:
"""
Returns the disassembled instruction at the given address in the address
space of the running process, or `None` if there's no valid instruction
at that address.
"""
raise NotImplementedError()
# We probably want to expose a better module interface in the future, but,
# for now, this is good enough.
def module_section_locations(self) -> List[Tuple[int, int, str, str]]:
"""
Return a list of (address, size, section_name, module_name) tuples for
the loaded sections in every module of this process.
"""
raise NotImplementedError()
def main_module_name(self) -> str | None:
"""
Returns the name of the main module.
On remote targets, this may be prefixed with "target:" string.
"""
raise NotImplementedError()
def main_module_entry(self) -> int | None:
"""
Returns the entry point of the main module.
"""
raise NotImplementedError()
def is_dynamically_linked(self) -> bool:
"""
Returns whether this process makes use of dynamically linked libraries.
# `"dynamically linked"`
What exactly it means to be "dynamically linked" here is a little
ill-defined. Ideally, this function should return true if the process
uses the default dynamic linker for the system, as that would better
reflect whether the process uses dynamic linking.
Currently, though, Pwndbg expects it to behave the same as a check for
the string "No shared libraries loaded at this time." in the output of
the `info dll` GDB command, which checks for the presence of other
modules in the address space of the process, rather than whether or not
the dynamic linker is used.
We should probably sort this out in the future.
"""
raise NotImplementedError()
def dispatch_execution_controller(
self, procedure: Callable[[ExecutionController], Coroutine[Any, Any, None]]
):
"""
Queues up the given execution controller-based coroutine for execution,
sometime between the calling of this function and the
"""
raise NotImplementedError()
class TypeCode(Enum):
"""
Broad categories of types.
"""
INVALID = -1
POINTER = 1
ARRAY = 2
STRUCT = 3
TYPEDEF = 4
UNION = 5
INT = 6
ENUM = 7
FUNC = 8
BOOL = 9
class TypeField:
"""
The fields in a structured type.
Currently this is just a mirror of `gdb.Field`.
"""
def __init__(
self,
bitpos: int,
name: str | None,
type: Type,
parent_type,
enumval: int | None = None,
artificial: bool = False,
is_base_class: bool = False,
bitsize: int = 0,
) -> None:
self.bitpos = bitpos
self.name = name
self.type = type
self.parent_type = parent_type
self.enumval = enumval
self.artificial = artificial
self.is_base_class = is_base_class
self.bitsize = bitsize
class Type:
"""
Class representing a type in the context of an inferior process.
"""
@property
def name_identifier(self) -> str | None:
"""
Returns the identifier of this type, eg:
- someStructName
- someEnumName
- someTypedefName
Returns None if the type is anonymous or does not have a name, such as:
- Anonymous structs
- Anonymous Typedefs
- Basic types like char[], void, etc.
"""
raise NotImplementedError()
@property
def name_to_human_readable(self) -> str:
"""
Returns the human friendly name of this type, eg:
- char [16]
- int
- char *
- void *
- fooStructName
- barEnumName
- barTypedefName
This function is not standardized, may return different names in gdb/lldb, eg:
gdb: `char [16]` or `char [50]` or `struct {...}`
lldb: `char[16]` or `char[]` or `(anonymous struct)`
You should not use this function. Only for human eyes.
"""
raise NotImplementedError()
@property
def sizeof(self) -> int:
"""
The size of this type, in bytes.
"""
raise NotImplementedError()
@property
def alignof(self) -> int:
"""
The alignment of this type, in bytes.
"""
raise NotImplementedError()
@property
def code(self) -> TypeCode:
"""
What category of type this object belongs to.
"""
raise NotImplementedError()
def func_arguments(self) -> List[Type] | None:
"""
Returns a list of function arguments type.
Returns:
List[Type] | None: The function arguments type, or None if debug information is missing.
Raises:
TypeError: If called on an unsupported type.
"""
raise NotImplementedError()
def fields(self) -> List[TypeField]:
"""
List of all fields in this type, if it is a structured type.
"""
raise NotImplementedError()
def has_field(self, name: str) -> bool:
"""
Whether this type has a field with the given name.
"""
# This is a sensible default way to check for a field's existence.
#
# Implementations should, however, override this method if there's a
# debugger-specific check for this that might be faster or more accurate.
fields = self.fields()
if fields:
for field in fields:
if field.name == name:
return True
return False
def array(self, count: int) -> Type:
"""
Return a type that corresponds to an array whose elements have this type.
"""
raise NotImplementedError()
def pointer(self) -> Type:
"""
Return a pointer type that has this type as its pointee.
"""
raise NotImplementedError()
def strip_typedefs(self) -> Type:
"""
Return a type that corresponds to the base type after a typedef chain,
if this is a typedef. Returns the type itself otherwise.
"""
raise NotImplementedError()
def target(self) -> Type:
"""
Return the target of this reference type, if this is a reference type.
"""
raise NotImplementedError()
def keys(self) -> List[str]:
"""
Returns a list containing all the field names of this type.
"""
# Like with `has_fields`, we provide a sensible default implementation
# based on `fields()`. Implementations are encouraged to override this
# if there is a better debugger-specific way to do this.
return [field.name for field in self.fields()]
def enum_member(self, field_name: str) -> int | None:
"""
Retrieve the integer value of an enum member.
It returns:
- integer value, when found field
- returns None, If the field does not exist
"""
if self.code != TypeCode.ENUM:
raise TypeError("only enum supported")
return next((f.enumval for f in self.fields() if f.name == field_name), None)
def _offsetof(
self, field_name: str, *, base_offset_bits: int = 0, nested_cyclic_types: List[Type] = None
) -> int | None:
NESTED_TYPES = (TypeCode.STRUCT, TypeCode.UNION)
struct_type = self
if nested_cyclic_types is None:
nested_cyclic_types = []
if struct_type.code == TypeCode.TYPEDEF:
struct_type = struct_type.strip_typedefs()
if struct_type.code not in NESTED_TYPES:
return None
elif struct_type in nested_cyclic_types:
return None
# note: lldb.SBType and gdb.Type dont support Sets
nested_cyclic_types.append(struct_type)
for field in struct_type.fields():
field_offset_bits = base_offset_bits + field.bitpos
if field.name == field_name:
if field_offset_bits % 8 != 0:
# Possible bit-fields, misaligned struct, or unexpected alignment
# This case is not supported because it introduces complexities
# in handling non-byte-aligned or bit-level field offsets
return None
return field_offset_bits // 8
nested_offset = field.type._offsetof(
field_name,
base_offset_bits=field_offset_bits,
nested_cyclic_types=nested_cyclic_types,
)
if nested_offset is not None:
return nested_offset
return None
def offsetof(self, field_name: str) -> int | None:
"""
Calculate the byte offset of a field within a struct or union.
This method recursively traverses nested structures and unions, and it computes the
byte-aligned offset for the specified field.
It returns:
- offset in bytes if found
- None if the field doesn't exist or if an unsupported alignment/bit-field is encountered
"""
if self.code == TypeCode.POINTER:
return self.target()._offsetof(field_name)
return self._offsetof(field_name)
def __eq__(self, rhs: object) -> bool:
"""
Returns True if types are the same
"""
raise NotImplementedError()
class Value:
"""
Class representing a value in the context of an inferior process.
"""
@property
def address(self) -> Value | None:
"""
The address of this value, in memory, if addressable, otherwise `None`.
"""
raise NotImplementedError()
# is_optimized_out is kind of a janky piece of API, honestly. It makes it
# so that one's ability to call all other methods in this class is often
# conditional on it being false, and it effectively splits the type into
# two.
#
# There's only _one_ part of Pwndbg that uses it, and I really feel like we
# should handle variables that have been optimized out some other way.
#
# TODO: Remove uses of is_optimized_out from plist and get rid of this.
@property
def is_optimized_out(self) -> bool:
"""
Whether this value is present in debugging information, but has been
optimized out of the actual program.
"""
raise NotImplementedError()
@property
def type(self) -> Type:
"""
The type associated with this value.
"""
raise NotImplementedError()
def dereference(self) -> Value:
"""
If this is a poitner value, dereferences the pointer and returns a new
instance of Value, containing the value pointed to by this pointer.
"""
raise NotImplementedError()
# The intent of this function has a great deal of overlap with that of
# `pwndbg.aglib.memory.string()`. It probably makes sense to take this
# functionality out of the debugger API.
#
# TODO: Move to single, common string function.
def string(self) -> str:
"""
If this value is a string, then this method converts it to a Python string.
"""
raise NotImplementedError()
def value_to_human_readable(self) -> str:
"""
Converts a Value to a human-readable string representation.
The format is similar to what is produced by the `str()` function for gdb.Value,
displaying nested fields and pointers in a user-friendly way.
**Usage Notes:**
- This function is intended solely for displaying results to the user.
- The output format may differ between debugger implementations (e.g., GDB vs LLDB),
as each debugger may format values differently. For instance:
- GDB might produce: '{\n value = 0,\n inner = {\n next = 0x555555558098 <inner_a_node_b+8>\n }\n}'
- LLDB might produce: '(inner_a_node) *$PWNDBG_CREATED_VALUE_0 = {\n value = 0\n inner = {\n next = 0x0000555555558098\n }\n}'
- As such, this function should not be relied upon for parsing or programmatic use.
"""
raise NotImplementedError()
# This is a GDB implementation detail.
def fetch_lazy(self) -> None:
"""
Fetches the value if it is lazy, does nothing otherwise.
"""
raise NotImplementedError()
def __int__(self) -> int:
"""
Converts this value to an integer, if possible.
"""
raise NotImplementedError()
# Because casting is still sloppy (i.e. it accepts `gdb.Type` objects) in
# some places, we have to allow `Any` here for lints to pass.
#
# TODO: Remove Any type from this function.
def cast(self, type: Type | Any) -> Value:
"""
Returns a new value with the same value as this object, but of the
given type.
"""
raise NotImplementedError()
def __add__(self, rhs: int) -> Value:
"""
Adds an integer to this value, if that makes sense. Throws an exception
otherwise.
"""
raise NotImplementedError()
def __sub__(self, rhs: int) -> Value:
"""
Subtract an integer from this value, if that makes sense. Throws an
exception otherwise.
"""
raise NotImplementedError()
def __getitem__(self, idx: int | str) -> Value:
"""
Gets the value with the given name that belongs to this value. For
structure types, this is the field with the given name. For array types,
this is the field at the given index. For pointer types, this is the
value of `*(ptr+idx)`.
"""
raise NotImplementedError()
class CommandHandle:
"""
An opaque handle to an installed command.
"""
def remove(self) -> None:
"""
Removes this command from the command palette of the debugger.
"""
raise NotImplementedError()
class EventType(Enum):
"""
Events that can be listened for and reacted to in a debugger.
The events types listed here are defined as follows:
- `START`: This event is fired some time between the creation of or
attachment to the process to be debugged, and the start of its
execution.
- `STOP`: This event is fired after execution of the process has been
suspended, but before control is returned to the user for interactive
debugging.
- `EXIT`: This event is fired after the process being debugged has been
detached from or has finished executing.
- `MEMORY_CHANGED`: This event is fired when the user interactively makes
changes to the memory of the process being debugged.
- `REGISTER_CHANGED`: Like `MEMORY_CHANGED`, but for registers.
- `CONTINUE`: This event is fired after the user has requested for
process execution to continue after it had been previously suspended.
- `NEW_MODULE`: This event is fired when a new application module has
been encountered by the debugger. This usually happens when a new
application module is loaded into the memory space of the process being
debugged. In GDB terminology, these are called `objfile`s.
"""
START = 0
STOP = 1
EXIT = 2
MEMORY_CHANGED = 3
REGISTER_CHANGED = 4
CONTINUE = 5
NEW_MODULE = 6
class Debugger:
"""
The base class representing a debugger.
"""
def setup(self, *args: Any) -> None:
"""
Perform debugger-specific initialization.
This method should be run immediately after `pwndbg.dbg` is set to an
instance of this class, and, as such, is allowed to run code that
depends on it being set.
Because we can't really know what a given debugger object will need as
part of its setup process, we allow for as many arguments as desired to
be passed in, and leave it up to the implementations to decide what they
need. This shouldn't be a problem, seeing as, unlike other methods in
this class, this should only be called as part of the debugger-specific
bringup code.
"""
raise NotImplementedError()
def history(self, last: int = 10) -> List[Tuple[int, str]]:
"""
The command history of the interactive session in this debugger.
This function returns the last `last` items in the command history, as
an oldest-to-youngest-sorted list of tuples, where the first element in
each tuple is the index of the command in the history, and the second
element is a string giving the command itself.
"""
raise NotImplementedError()
def lex_args(self, command_line: str) -> List[str]:
"""
Lexes the given command line into a list of arguments, according to the
conventions of the debugger being used and of the interactive session.
"""
raise NotImplementedError()
def selected_inferior(self) -> Process | None:
"""
The inferior process currently being focused on in this interactive session.
"""
raise NotImplementedError()
def selected_thread(self) -> Thread | None:
"""
The thread currently being focused on in this interactive session.
"""
raise NotImplementedError()
def selected_frame(self) -> Frame | None:
"""
The stack frame currently being focused on in this interactive session.
"""
raise NotImplementedError()
def commands(self) -> List[str]:
"""
List the commands available in this session.
"""
raise NotImplementedError()
def add_command(
self, name: str, handler: Callable[[Debugger, str, bool], None], doc: str | None
) -> CommandHandle:
"""
Adds a command with the given name to the debugger, that invokes the
given function every time it is called.
"""
raise NotImplementedError()
def has_event_type(self, ty: EventType) -> bool:
"""
Whether the given event type is supported by this debugger. Indicates
that a user either can or cannot register an event handler of this type.
"""
raise NotImplementedError()
def event_handler(self, ty: EventType) -> Callable[[Callable[..., T]], Callable[..., T]]:
"""
Sets up the given function to be called when an event of the given type
gets fired. Returns a callable that corresponds to the wrapped function.
This function my be used as a decorator.
"""
raise NotImplementedError()
def suspend_events(self, ty: EventType) -> None:
"""
Suspend delivery of all events of the given type until it is resumed
through a call to `resume_events`.
Events triggered during a suspension will be ignored, and will not be
delived, even after delivery is resumed.
"""
raise NotImplementedError()
def resume_events(self, ty: EventType) -> None:
"""
Resume the delivery of all events of the given type, if previously
suspeded through a call to `suspend_events`. Does nothing if the
delivery has not been previously suspeded.
"""
raise NotImplementedError()
def set_sysroot(self, sysroot: str) -> bool:
"""
Sets the system root for this debugger.
"""
raise NotImplementedError()
def x86_disassembly_flavor(self) -> Literal["att", "intel"]:
"""
The flavor of disassembly to use for x86 targets.
"""
raise NotImplementedError()
def supports_breakpoint_creation_during_stop_handler(self) -> bool:
"""
Whether breakpoint or watchpoint creation through `break_at` is
supported during breakpoint stop handlers.
"""
raise NotImplementedError()
def breakpoint_locations(self) -> List[BreakpointLocation]:
"""
Returns a list of all breakpoint locations that are currently
installed and enabled in the focused process.
"""
raise NotImplementedError()
# WARNING
#
# These are hacky parts of the API that were strictly necessary to bring up
# pwndbg under LLDB without breaking it under GDB. Expect most of them to be
# removed or replaced as the porting work continues.
#
def name(self) -> DebuggerType:
"""
The type of the current debugger.
"""
raise NotImplementedError()
# We'd like to be able to gate some imports off during porting. This aids in
# that.
def is_gdblib_available(self) -> bool:
"""
Whether gdblib is available under this debugger.
"""
raise NotImplementedError()
def string_limit(self) -> int:
"""
The maximum size of a string.
"""
raise NotImplementedError()
def addrsz(self, address: Any) -> str:
"""
Format the given address value.
"""
raise NotImplementedError()
def get_cmd_window_size(self) -> Tuple[int, int]:
"""
The size of the command window, in characters, if available.
"""
raise NotImplementedError()
@property
def pre_ctx_lines(self) -> int:
"""
Our prediction on how many lines of text will be printed as
a preamble (right after the prompt, and before the context)
the next time the context is printed.
This includes any lines the underlying debugger generates.
The user never sees these lines when context-clear-screen
is enabled.
"""
raise NotImplementedError()
def set_python_diagnostics(self, enabled: bool) -> None:
"""
Enables or disables Python diagnostic messages for this debugger.
"""
raise NotImplementedError()