Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 40 additions & 47 deletions cpython_lldb.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import abc
import argparse
import collections
import io
import re
import shlex
import struct

import lldb


ENCODING_RE = re.compile(r"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")


# Objects


class PyObject(object):
class PyObject:
def __init__(self, lldb_value):
self.lldb_value = lldb_value

Expand All @@ -41,20 +39,22 @@ def from_value(cls, v):

@staticmethod
def typename_of(v):
addr = (
v.GetChildMemberWithName("ob_type")
.GetChildMemberWithName("tp_name")
.unsigned
)
if not addr:
return

try:
addr = (
v.GetChildMemberWithName("ob_type")
.GetChildMemberWithName("tp_name")
.unsigned
)
if not addr:
return
status = lldb.SBError()
cstring = v.GetProcess().ReadCStringFromMemory(addr, 256, status)

process = v.GetProcess()
return process.ReadCStringFromMemory(addr, 256, lldb.SBError())
except Exception:
# if we fail to read tp_name, then it's likely not a PyObject
pass
return cstring if status.Success() else None
except Exception: # noqa
# If we fail to read tp_name, then it's likely not a PyObject.
return

@property
def typename(self):
Expand Down Expand Up @@ -111,7 +111,7 @@ def value(self):
digits = value.GetChildMemberWithName("ob_digit")
abs_value = sum(
digits.GetChildAtIndex(i, 0, True).unsigned * 2 ** (shift * i)
for i in range(0, abs(size))
for i in range(abs(size))
)
return abs_value if size > 0 else -abs_value

Expand Down Expand Up @@ -181,7 +181,7 @@ def _get_encoding(kind):
elif kind == PyUnicodeObject.U_4BYTE_KIND:
return "utf-32"
else:
raise ValueError("Unsupported PyUnicodeObject kind: {}".format(kind))
raise ValueError(f"Unsupported PyUnicodeObject kind: {kind}")

@staticmethod
def _read_string_from_memory(process, addr, length, kind):
Expand Down Expand Up @@ -252,7 +252,7 @@ class PyNoneObject(PyObject):
value = None


class _PySequence(object):
class _PySequence:
@property
def value(self):
value = self.lldb_value.Cast(self.lldb_type.GetPointerType())
Expand Down Expand Up @@ -288,7 +288,7 @@ def lldb_type(self):
return self.target.FindFirstType(self.cpython_struct)


class _PySetObject(object):
class _PySetObject:
cpython_struct = "PySetObject"

@property
Expand Down Expand Up @@ -324,10 +324,10 @@ class PyFrozenSetObject(_PySetObject, PyObject):

@property
def value(self):
return frozenset(super(PyFrozenSetObject, self).value)
return frozenset(super().value)


class _PyDictObject(object):
class _PyDictObject:
DICT_KEYS_GENERAL = 0
DICT_KEYS_UNICODE = 1
DICT_KEYS_SPLIT = 2
Expand Down Expand Up @@ -457,7 +457,7 @@ def value(self):
return PyDictObject(value.GetChildMemberWithName("dict").AddressOf()).value


class _CollectionsUserObject(object):
class _CollectionsUserObject:
@property
def value(self):
# UserDict, UserString, and UserList all have a single instance variable
Expand Down Expand Up @@ -513,7 +513,7 @@ class UserString(_CollectionsUserObject, PyObject):
typename = "UserString"


class PyCodeAddressRange(object):
class PyCodeAddressRange:
"""A class for parsing the line number table implemented in PEP 626.

The format of the line number table is not part of CPython's API and may
Expand Down Expand Up @@ -662,7 +662,7 @@ class PyFrameObject(PyObject):
typename = "frame"

def __init__(self, lldb_value):
super(PyFrameObject, self).__init__(lldb_value)
super().__init__(lldb_value)
self.co = PyCodeObject(self.child("f_code"))

@classmethod
Expand Down Expand Up @@ -790,17 +790,13 @@ def line(self):
return source_file_lines(
self.filename, self.line_number, self.line_number + 1, encoding=encoding
)[0]
except (IOError, IndexError):
except (OSError, IndexError):
return "<source code is not available>"

def to_pythonlike_string(self):
lineno = self.line_number
co_name = PyObject.from_value(self.co.child("co_name")).value
return 'File "{filename}", line {lineno}, in {co_name}'.format(
filename=self.filename,
co_name=co_name,
lineno=lineno,
)
return f'File "{self.filename}", line {lineno}, in {co_name}'


# Commands
Expand Down Expand Up @@ -831,8 +827,8 @@ def __call__(self, debugger, command, exe_ctx, result):
try:
args = self.argument_parser.parse_args(shlex.split(command))
self.execute(debugger, args, result)
except Exception as e:
msg = "Failed to execute command `{}`: {}".format(self.command, e)
except Exception as e: # noqa
msg = f"Failed to execute command `{self.command}`: {e}"

result.SetError(msg)

Expand Down Expand Up @@ -933,7 +929,7 @@ class PyList(Command):

@property
def argument_parser(self):
parser = super(PyList, self).argument_parser
parser = super().argument_parser

parser.add_argument("linenum", nargs="*", type=int, default=[0, 0])

Expand Down Expand Up @@ -982,12 +978,12 @@ def execute(self, debugger, args, result):
for i, line in enumerate(lines, start):
# highlight the current line
if i == current_line_num:
prefix = ">{}".format(i)
prefix = f">{i}"
else:
prefix = "{}".format(i)
prefix = f"{i}"

write_line(result, "{:>5} {}".format(prefix, line.rstrip()))
except IOError:
write_line(result, f"{prefix:>5} {line.rstrip()}")
except OSError:
write_line(result, "<source code is not available>")


Expand Down Expand Up @@ -1054,13 +1050,13 @@ def execute(self, debugger, args, result):
merged_locals.pop(name, None)

for name in sorted(merged_locals.keys()):
write_line(result, "{} = {}".format(name, repr(merged_locals[name])))
write_line(result, f"{name} = {merged_locals[name]!r}")


# Helpers


class Direction(object):
class Direction:
DOWN = -1
UP = 1

Expand Down Expand Up @@ -1096,7 +1092,7 @@ def move_python_frame(debugger, direction):
if direction == Direction.UP:
index_range = range(current_frame.idx + 1, thread.num_frames)
else:
index_range = reversed(range(0, current_frame.idx))
index_range = reversed(range(current_frame.idx))

for index in index_range:
python_frame = PyFrameObject.from_frame(thread.GetFrameAtIndex(index))
Expand All @@ -1112,7 +1108,7 @@ def write_line(result, string):
def source_file_encoding(filename):
"""Determine the text encoding of a Python source file."""

with io.open(filename, "rt", encoding="latin-1") as f:
with open(filename, "rt", encoding="latin-1") as f:
# according to PEP-263 the magic comment must be placed on one of the first two lines
for _ in range(2):
line = f.readline()
Expand All @@ -1131,7 +1127,7 @@ def source_file_lines(filename, start, end, encoding="utf-8"):
"""

lines = []
with io.open(filename, "rt", encoding=encoding) as f:
with open(filename, "rt", encoding=encoding) as f:
for line_num, line in enumerate(f, 1):
if start <= line_num < end:
lines.append(line)
Expand Down Expand Up @@ -1160,10 +1156,7 @@ def general_purpose_registers(frame):
def register_commands(debugger):
for cls in Command.__subclasses__():
debugger.HandleCommand(
"command script add -c cpython_lldb.{cls} {command}".format(
cls=cls.__name__,
command=cls.command,
)
f"command script add -c cpython_lldb.{cls.__name__} {cls.command}"
)


Expand Down Expand Up @@ -1200,7 +1193,7 @@ def register_summaries(debugger):
}
for type_ in cpython_structs:
debugger.HandleCommand(
"type summary add -F cpython_lldb.pretty_printer {}".format(type_)
f"type summary add -F cpython_lldb.pretty_printer {type_}"
)

# cache the result of the lookup, so that we do not need to repeat that at runtime
Expand Down
11 changes: 5 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import contextlib
import io
import re
import os
import re
import subprocess
import sys

Expand Down Expand Up @@ -51,7 +50,7 @@ def lldb_no_symbols_session(tmpdir_factory):
with tmpdir.as_cwd():
libpython = (
subprocess.check_output(
"ldd %s | grep libpython | awk '{print $3}'" % (sys.executable),
f"ldd {sys.executable} | grep libpython | awk '{{print $3}}'",
shell=True,
)
.decode("utf-8")
Expand Down Expand Up @@ -138,13 +137,13 @@ def run_lldb(lldb_manager, code, breakpoint, commands):
outputs = []

with lldb_manager() as lldb:
with io.open("test.py", "wb") as fp:
with open("test.py", "wb") as fp:
if isinstance(code, str):
code = code.encode("utf-8")

fp.write(code)

lldb.sendline("breakpoint set -r %s" % breakpoint)
lldb.sendline(f"breakpoint set -r {breakpoint}")
lldb.expect(r"Breakpoint \d+")
lldb.expect(re.escape("(lldb) "))
lldb.sendline("run test.py")
Expand All @@ -153,7 +152,7 @@ def run_lldb(lldb_manager, code, breakpoint, commands):

for command in commands:
lldb.sendline(command)
lldb.expect(re.escape("%s\r\n" % command))
lldb.expect(re.escape(f"{command}\r\n"))
lldb.expect(re.escape("(lldb) "))

outputs.append(normalize_stacktrace(lldb.before.replace("\r\n", "\n")))
Expand Down
2 changes: 1 addition & 1 deletion tests/test_extension/test_extension/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from ._test_extension import spam as spam
from ._test_extension import eggs as eggs
from ._test_extension import identity as identity
from ._test_extension import spam as spam
9 changes: 4 additions & 5 deletions tests/test_pretty_printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ def assert_lldb_repr(lldb_manager, value, expected, code_value=None):
# for other data types we can do an exact string match using
# a regular expression (e.g. to account for optional 'u' and 'b'
# in unicode / bytes literals, etc)
assert re.match(expected, match.group(1)), "Expected: %s\nActual: %s" % (
expected,
match.group(1),
assert re.match(expected, match.group(1)), (
f"Expected: {expected}\nActual: {match.group(1)}"
)


Expand Down Expand Up @@ -122,10 +121,10 @@ def test_tuple(lldb):

def test_set(lldb):
assert_lldb_repr(lldb, set(), r"set\(\[\]\)")
assert_lldb_repr(lldb, set([1, 2, 3]), r"set\(\[1, 2, 3\]\)")
assert_lldb_repr(lldb, {1, 2, 3}, r"set\(\[1, 2, 3\]\)")
assert_lldb_repr(
lldb,
set([1, 3.14159, "hello", False, None]),
{1, 3.14159, "hello", False, None},
r"set\(\[False, 1, 3.14159, None, u\'hello\'\]\)",
)
assert_lldb_repr(
Expand Down
4 changes: 2 additions & 2 deletions tests/test_py_bt.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def fc():
lldb,
code=code,
breakpoint="builtin_abs",
commands=["frame select %d" % int(pyframes[2][0]), "py-bt"],
commands=[f"frame select {int(pyframes[2][0])}", "py-bt"],
)[-1]
actual = response.rstrip()
assert actual == backtrace
Expand Down Expand Up @@ -126,7 +126,7 @@ def fc():
lldb,
code=code,
breakpoint="builtin_abs",
commands=["frame select %d" % (int(pyframes[-1][0]) + 1), "py-bt"],
commands=[f"frame select {int(pyframes[-1][0]) + 1}", "py-bt"],
)[-1]
actual = response.rstrip()
assert actual == backtrace
Expand Down
1 change: 0 additions & 1 deletion tests/test_py_list.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from .conftest import run_lldb


CODE = """
SOME_CONST = u'тест'

Expand Down
25 changes: 11 additions & 14 deletions tests/test_py_locals.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from .conftest import run_lldb


CODE = """\
def fa():
abs(1)
Expand Down Expand Up @@ -56,19 +55,17 @@ def test_globals(lldb):
)[-1]
actual = response.rstrip()

actual_keys = set(line.split("=")[0].strip() for line in actual.split("\n") if line)
expected_keys = set(
[
"__builtins__",
"__package__",
"__name__",
"__doc__",
"__file__",
"fa",
"fb",
"fc",
]
)
actual_keys = {line.split("=")[0].strip() for line in actual.split("\n") if line}
expected_keys = {
"__builtins__",
"__package__",
"__name__",
"__doc__",
"__file__",
"fa",
"fb",
"fc",
}
assert (expected_keys & actual_keys) == expected_keys


Expand Down
1 change: 0 additions & 1 deletion tests/test_py_up_down.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from .conftest import run_lldb


CODE = """
SOME_CONST = u'тест'

Expand Down
Loading