Expand Debugger-agnostic API and split `pwndbg.dbg.evaluate_expression` (#2280)

pull/2299/head
Matt 1 year ago committed by GitHub
parent a076273a62
commit aa566e595a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

8
poetry.lock generated

@ -2059,13 +2059,13 @@ files = [
[[package]]
name = "types-gdb"
version = "12.1.4.20240408"
version = "12.1.4.20240704"
description = "Typing stubs for gdb"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-gdb-12.1.4.20240408.tar.gz", hash = "sha256:f2136ccf15ab74b5b2702b88b3c38a41029a89c12d9c12851c6a0b9789047d83"},
{file = "types_gdb-12.1.4.20240408-py3-none-any.whl", hash = "sha256:b5fc44c1929cc5eb071792d5f66b5f74e880589683d065651bf9464d8f41ad75"},
{file = "types-gdb-12.1.4.20240704.tar.gz", hash = "sha256:0b537f6acf8339e82dc92388aad057a30c83d89105cd820cf5dc948d6debce10"},
{file = "types_gdb-12.1.4.20240704-py3-none-any.whl", hash = "sha256:da273ebf395ded84ea12745ef70d0c1560694c26b6d9c04575d500a74929fed1"},
]
[[package]]
@ -2354,4 +2354,4 @@ cffi = ["cffi (>=1.11)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "06adbdf62440aaedba8a00255a1ba7db2206e6124a4f4beef8fadce656df8f10"
content-hash = "c46fbb9c8a498c53bc1acb13d027819c5c3c88124a6c58b8408d30699f1a81f1"

@ -157,6 +157,10 @@ class Command:
pwndbg.exception.handle(self.function.__name__)
return
if not pwndbg.dbg.selected_inferior():
log.error("Pwndbg commands require a target binary to be selected")
return
try:
self.repeat = self.check_repeated(argument, from_tty)
self(*args, **kwargs)
@ -220,15 +224,20 @@ def fix(
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:
parsed = pwndbg.dbg.evaluate_expression(arg)
return parsed
return target.evaluate_expression(arg)
except Exception:
pass
try:
arg = pwndbg.gdblib.regs.fix(arg)
return pwndbg.dbg.evaluate_expression(arg)
return target.evaluate_expression(arg)
except Exception as e:
if not quiet:
print(e)
@ -609,8 +618,15 @@ def sloppy_gdb_parse(s: str) -> int | str:
: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.dbg.evaluate_expression(s)
val = target.evaluate_expression(s)
# We can't just return int(val) because GDB may return:
# "Python Exception <class 'gdb.error'> Cannot convert value to long."
# e.g. for:

@ -13,6 +13,38 @@ from typing import Tuple
dbg: Debugger = None
class Frame:
def evaluate_expression(self, expression: str) -> Value:
"""
Evaluate the given expression in the context of this frame, and
return a `Value`.
"""
raise NotImplementedError()
class Thread:
def bottom_frame(self) -> Frame:
"""
Frame at the bottom of the call stack for this thread.
"""
raise NotImplementedError()
class Process:
def threads(self) -> List[Thread]:
"""
Returns a list containing the threads in this process.
"""
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()
class TypeCode(Enum):
"""
Broad categories of types.
@ -228,6 +260,24 @@ class Debugger:
"""
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 add_command(
self, name: str, handler: Callable[[Debugger, str, bool], None]
) -> CommandHandle:
@ -244,18 +294,6 @@ class Debugger:
# removed or replaced as the porting work continues.
#
# This function will be split up into a frame-context and a global-context
# versions very soon, in a way that properly represents the way evaluation
# works under both GDB and LLDB.
#
# TODO: Split up `evaluate_expressions` into its global and local versions.
def evaluate_expression(self, expression: str) -> Value:
"""
Evaluate the given expression in the context of the current frame, and
return a `Value`.
"""
raise NotImplementedError()
def addrsz(self, address: Any) -> str:
"""
Format the given address value.

@ -17,6 +17,66 @@ from pwndbg.gdblib import gdb_version
from pwndbg.gdblib import load_gdblib
def parse_and_eval(expression: str, global_context: bool) -> gdb.Value:
"""
Same as `gdb.parse_and_eval`, but only uses `global_context` if it is
supported by the current version of GDB.
`global_context` was introduced in GDB 14.
"""
try:
return gdb.parse_and_eval(expression, global_context)
except TypeError:
return gdb.parse_and_eval(expression)
class GDBFrame(pwndbg.dbg_mod.Frame):
def __init__(self, inner: gdb.Frame):
self.inner = inner
@override
def evaluate_expression(self, expression: str) -> pwndbg.dbg_mod.Value:
selected = gdb.selected_frame()
restore = False
if selected != self.inner:
self.inner.select()
restore = True
value = parse_and_eval(expression, global_context=False)
if restore:
selected.select()
return GDBValue(value)
class GDBThread(pwndbg.dbg_mod.Thread):
def __init__(self, inner: gdb.InferiorThread):
self.inner = inner
@override
def bottom_frame(self) -> pwndbg.dbg_mod.Frame:
selected = gdb.selected_thread()
restore = False
if selected != self.inner:
self.inner.switch()
restore = True
value = gdb.newest_frame()
if restore:
selected.switch()
return GDBFrame(value)
class GDBProcess(pwndbg.dbg_mod.Process):
def __init__(self, inner: gdb.Inferior):
self.inner = inner
@override
def evaluate_expression(self, expression: str) -> pwndbg.dbg_mod.Value:
return GDBValue(parse_and_eval(expression, global_context=True))
class GDBCommand(gdb.Command):
def __init__(
self,
@ -326,8 +386,25 @@ class GDB(pwndbg.dbg_mod.Debugger):
return gdb.string_to_argv(command_line)
@override
def evaluate_expression(self, expression: str) -> pwndbg.dbg_mod.Value:
return GDBValue(gdb.parse_and_eval(expression))
def selected_thread(self) -> pwndbg.dbg_mod.Thread | None:
thread = gdb.selected_thread()
if thread:
return GDBThread(thread)
return None
@override
def selected_frame(self) -> pwndbg.dbg_mod.Frame | None:
try:
frame = gdb.selected_frame()
if frame:
return GDBFrame(frame)
except gdb.error:
pass
return None
@override
def selected_inferior(self) -> pwndbg.dbg_mod.Process | None:
return GDBProcess(gdb.selected_inferior())
@override
def addrsz(self, address: Any) -> str:

@ -177,7 +177,7 @@ ruff = "^0.4.1"
sortedcontainers-stubs = "^2.4.2"
testresources = "^2.0.1"
tomli = "^2.0.1"
types-gdb = "^12.1.4.20240408"
types-gdb = "^12.1.4.20240704"
types-psutil = "^5.9.5.20240423"
types-pygments = "^2.17.0.20240310"
types-requests = "^2.31.0.20240406"

Loading…
Cancel
Save