Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Gwenaël Samain
cython
Commits
2648f3f7
Commit
2648f3f7
authored
Sep 04, 2017
by
Stefan Behnel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update gdb support in libpython.py from CPython 3.7 (git rev 5fe59f8).
parent
a7b253ab
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
505 additions
and
348 deletions
+505
-348
CHANGES.rst
CHANGES.rst
+3
-0
Cython/Debugger/libpython.py
Cython/Debugger/libpython.py
+502
-348
No files found.
CHANGES.rst
View file @
2648f3f7
...
...
@@ -38,6 +38,9 @@ Features added
* Signature annotations are now included in the signature docstring generated by
the ``embedsignature`` directive. Patch by Lisandro Dalcin (Github issue #1781).
* The gdb support for Python code (``libpython.py``) was updated to the latest
version in CPython 3.7 (git rev 5fe59f8).
* ``len(memoryview)`` can be used in nogil sections to get the size of the
first dimension of a memory view (``shape[0]``). (Github issue #1733)
...
...
Cython/Debugger/libpython.py
View file @
2648f3f7
...
...
@@ -25,9 +25,10 @@ giving file/line information and the state of local variables
In particular, given a gdb.Value corresponding to a PyObject* in the inferior
process, we can generate a "proxy value" within the gdb process. For example,
given a PyObject* in the inferior process that is in fact a PyListObject*
holding three PyObject* that turn out to be PyStringObject* instances, we can
generate a proxy value within the gdb process that is a list of strings:
["foo", "bar", "baz"]
holding three PyObject* that turn out to be PyBytesObject* instances, we can
generate a proxy value within the gdb process that is a list of bytes
instances:
[b"foo", b"bar", b"baz"]
Doing so can be expensive for complicated graphs of objects, and could take
some time, so we also have a "write_repr" method that writes a representation
...
...
@@ -46,70 +47,72 @@ the type names are known to the debugger
The module also extends gdb with some python-specific commands.
'''
try
:
input
=
raw_input
except
NameError
:
pass
# NOTE: some gdbs are linked with Python 3, so this file should be dual-syntax
# compatible (2.6+ and 3.0+). See #19308.
from
__future__
import
print_function
import
gdb
import
os
import
re
import
sys
import
struct
import
locale
import
atexit
import
warnings
import
tempfile
import
textwrap
import
itertools
import
gdb
import
sys
try
:
xrange
except
NameError
:
if
sys
.
version_info
[
0
]
>=
3
:
unichr
=
chr
xrange
=
range
if
sys
.
version_info
[
0
]
<
3
:
# I think this is the only way to fix this bug :'(
# http://sourceware.org/bugzilla/show_bug.cgi?id=12285
out
,
err
=
sys
.
stdout
,
sys
.
stderr
reload
(
sys
).
setdefaultencoding
(
'UTF-8'
)
sys
.
stdout
=
out
sys
.
stderr
=
err
long
=
int
# Look up the gdb.Type for some standard types:
_type_char_ptr
=
gdb
.
lookup_type
(
'char'
).
pointer
()
# char*
_type_unsigned_char_ptr
=
gdb
.
lookup_type
(
'unsigned char'
).
pointer
()
_type_void_ptr
=
gdb
.
lookup_type
(
'void'
).
pointer
()
# void*
# Those need to be refreshed as types (pointer sizes) may change when
# gdb loads different executables
SIZEOF_VOID_P
=
_type_void_ptr
.
sizeof
def
_type_char_ptr
():
return
gdb
.
lookup_type
(
'char'
).
pointer
()
# char*
Py_TPFLAGS_HEAPTYPE
=
(
1
<<
9
)
Py_TPFLAGS_INT_SUBCLASS
=
(
1
<<
23
)
def
_type_unsigned_char_ptr
():
return
gdb
.
lookup_type
(
'unsigned char'
).
pointer
()
# unsigned char*
def
_type_unsigned_short_ptr
():
return
gdb
.
lookup_type
(
'unsigned short'
).
pointer
()
def
_type_unsigned_int_ptr
():
return
gdb
.
lookup_type
(
'unsigned int'
).
pointer
()
def
_sizeof_void_p
():
return
gdb
.
lookup_type
(
'void'
).
pointer
().
sizeof
# value computed later, see PyUnicodeObjectPtr.proxy()
_is_pep393
=
None
Py_TPFLAGS_HEAPTYPE
=
(
1
<<
9
)
Py_TPFLAGS_LONG_SUBCLASS
=
(
1
<<
24
)
Py_TPFLAGS_LIST_SUBCLASS
=
(
1
<<
25
)
Py_TPFLAGS_TUPLE_SUBCLASS
=
(
1
<<
26
)
Py_TPFLAGS_STRING_SUBCLASS
=
(
1
<<
27
)
Py_TPFLAGS_BYTES_SUBCLASS
=
(
1
<<
27
)
Py_TPFLAGS_UNICODE_SUBCLASS
=
(
1
<<
28
)
Py_TPFLAGS_DICT_SUBCLASS
=
(
1
<<
29
)
Py_TPFLAGS_BASE_EXC_SUBCLASS
=
(
1
<<
30
)
Py_TPFLAGS_TYPE_SUBCLASS
=
(
1
<<
31
)
MAX_OUTPUT_LEN
=
1024
MAX_OUTPUT_LEN
=
1024
hexdigits
=
"0123456789abcdef"
ENCODING
=
locale
.
getpreferredencoding
()
EVALFRAME
=
'_PyEval_EvalFrameDefault'
class
NullPyObjectPtr
(
RuntimeError
):
pass
def
safety_limit
(
val
):
# Given a integer value from the process being debugged, limit it to some
# Given a
n
integer value from the process being debugged, limit it to some
# safety threshold so that arbitrary breakage within said process doesn't
# break the gdb process too much (e.g. sizes of iterations, sizes of lists)
return
min
(
val
,
1000
)
...
...
@@ -118,42 +121,45 @@ def safety_limit(val):
def
safe_range
(
val
):
# As per range, but don't trust the value too much: cap it to a safety
# threshold in case the data was corrupted
return
range
(
safety_limit
(
val
))
def
write_unicode
(
file
,
text
):
# Write a byte or unicode string to file. Unicode strings are encoded to
# ENCODING encoding with 'backslashreplace' error handler to avoid
# UnicodeEncodeError.
if
not
isinstance
(
text
,
str
):
text
=
text
.
encode
(
ENCODING
,
'backslashreplace'
)
file
.
write
(
text
)
def
os_fsencode
(
filename
):
if
isinstance
(
filename
,
str
):
# only encode in Py2
return
filename
encoding
=
sys
.
getfilesystemencoding
()
if
encoding
==
'mbcs'
:
# mbcs doesn't support surrogateescape
return
filename
.
encode
(
encoding
)
encoded
=
[]
for
char
in
filename
:
# surrogateescape error handler
if
0xDC80
<=
ord
(
char
)
<=
0xDCFF
:
byte
=
chr
(
ord
(
char
)
-
0xDC00
)
else
:
byte
=
char
.
encode
(
encoding
)
encoded
.
append
(
byte
)
return
''
.
join
(
encoded
)
return
xrange
(
safety_limit
(
int
(
val
)))
if
sys
.
version_info
[
0
]
>=
3
:
def
write_unicode
(
file
,
text
):
file
.
write
(
text
)
else
:
def
write_unicode
(
file
,
text
):
# Write a byte or unicode string to file. Unicode strings are encoded to
# ENCODING encoding with 'backslashreplace' error handler to avoid
# UnicodeEncodeError.
if
isinstance
(
text
,
unicode
):
text
=
text
.
encode
(
ENCODING
,
'backslashreplace'
)
file
.
write
(
text
)
try
:
os_fsencode
=
os
.
fsencode
except
AttributeError
:
def
os_fsencode
(
filename
):
if
not
isinstance
(
filename
,
unicode
):
return
filename
encoding
=
sys
.
getfilesystemencoding
()
if
encoding
==
'mbcs'
:
# mbcs doesn't support surrogateescape
return
filename
.
encode
(
encoding
)
encoded
=
[]
for
char
in
filename
:
# surrogateescape error handler
if
0xDC80
<=
ord
(
char
)
<=
0xDCFF
:
byte
=
chr
(
ord
(
char
)
-
0xDC00
)
else
:
byte
=
char
.
encode
(
encoding
)
encoded
.
append
(
byte
)
return
''
.
join
(
encoded
)
class
StringTruncated
(
RuntimeError
):
pass
class
TruncatedStringIO
(
object
):
'''Similar to
c
StringIO, but can truncate the output by raising a
'''Similar to
io.
StringIO, but can truncate the output by raising a
StringTruncated exception'''
def
__init__
(
self
,
maxlen
=
None
):
self
.
_val
=
''
...
...
@@ -171,41 +177,10 @@ class TruncatedStringIO(object):
def
getvalue
(
self
):
return
self
.
_val
# pretty printer lookup
all_pretty_typenames
=
set
()
class
PrettyPrinterTrackerMeta
(
type
):
def
__init__
(
self
,
name
,
bases
,
dict
):
super
(
PrettyPrinterTrackerMeta
,
self
).
__init__
(
name
,
bases
,
dict
)
all_pretty_typenames
.
add
(
self
.
_typename
)
# Class decorator that adds a metaclass and recreates the class with it.
# Copied from 'six'. See Cython/Utils.py.
def
_add_metaclass
(
metaclass
):
"""Class decorator for creating a class with a metaclass."""
def
wrapper
(
cls
):
orig_vars
=
cls
.
__dict__
.
copy
()
slots
=
orig_vars
.
get
(
'__slots__'
)
if
slots
is
not
None
:
if
isinstance
(
slots
,
str
):
slots
=
[
slots
]
for
slots_var
in
slots
:
orig_vars
.
pop
(
slots_var
)
orig_vars
.
pop
(
'__dict__'
,
None
)
orig_vars
.
pop
(
'__weakref__'
,
None
)
return
metaclass
(
cls
.
__name__
,
cls
.
__bases__
,
orig_vars
)
return
wrapper
@
_add_metaclass
(
PrettyPrinterTrackerMeta
)
class
PyObjectPtr
(
object
):
"""
Class wrapping a gdb.Value that's
a
either a (PyObject*) within the
inferior process, or some subclass pointer e.g. (Py
String
Object*)
Class wrapping a gdb.Value that's either a (PyObject*) within the
inferior process, or some subclass pointer e.g. (Py
Bytes
Object*)
There will be a subclass for every refined PyObject type that we care
about.
...
...
@@ -213,7 +188,6 @@ class PyObjectPtr(object):
Note that at every stage the underlying pointer could be NULL, point
to corrupt data, etc; this is the debugger, after all.
"""
_typename
=
'PyObject'
def
__init__
(
self
,
gdbval
,
cast_to
=
None
):
...
...
@@ -286,7 +260,7 @@ class PyObjectPtr(object):
return
PyTypeObjectPtr
(
self
.
field
(
'ob_type'
))
def
is_null
(
self
):
return
not
self
.
_gdbval
return
0
==
long
(
self
.
_gdbval
)
def
is_optimized_out
(
self
):
'''
...
...
@@ -347,7 +321,7 @@ class PyObjectPtr(object):
return
'<%s at remote 0x%x>'
%
(
self
.
tp_name
,
self
.
address
)
return
FakeRepr
(
self
.
safe_tp_name
(),
int
(
self
.
_gdbval
))
long
(
self
.
_gdbval
))
def
write_repr
(
self
,
out
,
visited
):
'''
...
...
@@ -386,44 +360,40 @@ class PyObjectPtr(object):
# class
return
cls
#print
'tp_flags = 0x%08x' % tp_flags
#print
'tp_name = %r' % tp_name
#print
('tp_flags = 0x%08x' % tp_flags)
#print
('tp_name = %r' % tp_name)
name_map
=
{
'bool'
:
PyBoolObjectPtr
,
'classobj'
:
PyClassObjectPtr
,
'instance'
:
PyInstanceObjectPtr
,
'NoneType'
:
PyNoneStructPtr
,
'frame'
:
PyFrameObjectPtr
,
'set'
:
PySetObjectPtr
,
'frozenset'
:
PySetObjectPtr
,
'builtin_function_or_method'
:
PyCFunctionObjectPtr
,
'method-wrapper'
:
wrapperobject
,
}
if
tp_name
in
name_map
:
return
name_map
[
tp_name
]
if
tp_flags
&
(
Py_TPFLAGS_HEAPTYPE
|
Py_TPFLAGS_TYPE_SUBCLASS
)
:
return
Py
TypeObjectPtr
if
tp_flags
&
Py_TPFLAGS_HEAPTYPE
:
return
Heap
TypeObjectPtr
if
tp_flags
&
Py_TPFLAGS_INT_SUBCLASS
:
return
PyIntObjectPtr
if
tp_flags
&
Py_TPFLAGS_LONG_SUBCLASS
:
return
PyLongObjectPtr
if
tp_flags
&
Py_TPFLAGS_LIST_SUBCLASS
:
return
PyListObjectPtr
if
tp_flags
&
Py_TPFLAGS_TUPLE_SUBCLASS
:
return
PyTupleObjectPtr
if
tp_flags
&
Py_TPFLAGS_STRING_SUBCLASS
:
try
:
gdb
.
lookup_type
(
'PyBytesObject'
)
return
PyBytesObjectPtr
except
RuntimeError
:
return
PyStringObjectPtr
if
tp_flags
&
Py_TPFLAGS_BYTES_SUBCLASS
:
return
PyBytesObjectPtr
if
tp_flags
&
Py_TPFLAGS_UNICODE_SUBCLASS
:
return
PyUnicodeObjectPtr
if
tp_flags
&
Py_TPFLAGS_DICT_SUBCLASS
:
return
PyDictObjectPtr
if
tp_flags
&
Py_TPFLAGS_BASE_EXC_SUBCLASS
:
return
PyBaseExceptionObjectPtr
#if tp_flags & Py_TPFLAGS_TYPE_SUBCLASS:
# return PyTypeObjectPtr
# Use the base class:
return
cls
...
...
@@ -438,7 +408,7 @@ class PyObjectPtr(object):
p
=
PyObjectPtr
(
gdbval
)
cls
=
cls
.
subclass_from_type
(
p
.
type
())
return
cls
(
gdbval
,
cast_to
=
cls
.
get_gdb_type
())
except
RuntimeError
as
exc
:
except
RuntimeError
:
# Handle any kind of error e.g. NULL ptrs by simply using the base
# class
pass
...
...
@@ -449,13 +419,11 @@ class PyObjectPtr(object):
return
gdb
.
lookup_type
(
cls
.
_typename
).
pointer
()
def
as_address
(
self
):
return
int
(
self
.
_gdbval
)
return
long
(
self
.
_gdbval
)
class
PyVarObjectPtr
(
PyObjectPtr
):
_typename
=
'PyVarObject'
class
ProxyAlreadyVisited
(
object
):
'''
Placeholder proxy to use when protecting against infinite recursion due to
...
...
@@ -471,7 +439,7 @@ class ProxyAlreadyVisited(object):
def
_write_instance_repr
(
out
,
visited
,
name
,
pyop_attrdict
,
address
):
'''Shared code for use by
old-style and new-style
classes:
'''Shared code for use by
all
classes:
write a representation to file-like object "out"'''
out
.
write
(
'<'
)
out
.
write
(
name
)
...
...
@@ -480,7 +448,7 @@ def _write_instance_repr(out, visited, name, pyop_attrdict, address):
if
isinstance
(
pyop_attrdict
,
PyDictObjectPtr
):
out
.
write
(
'('
)
first
=
True
for
pyop_arg
,
pyop_val
in
pyop_attrdict
.
items
():
for
pyop_arg
,
pyop_val
in
pyop_attrdict
.
ite
rite
ms
():
if
not
first
:
out
.
write
(
', '
)
first
=
False
...
...
@@ -500,24 +468,27 @@ class InstanceProxy(object):
def
__repr__
(
self
):
if
isinstance
(
self
.
attrdict
,
dict
):
kwargs
=
', '
.
join
(
"%s=%r"
%
(
arg
,
val
)
for
arg
,
val
in
self
.
attrdict
.
items
())
return
'<%s(%s) at remote 0x%x>'
%
(
self
.
cl_name
,
kwargs
,
self
.
address
)
kwargs
=
', '
.
join
([
"%s=%r"
%
(
arg
,
val
)
for
arg
,
val
in
self
.
attrdict
.
iteritems
()])
return
'<%s(%s) at remote 0x%x>'
%
(
self
.
cl_name
,
kwargs
,
self
.
address
)
else
:
return
'<%s at remote 0x%x>'
%
(
self
.
cl_name
,
self
.
address
)
return
'<%s at remote 0x%x>'
%
(
self
.
cl_name
,
self
.
address
)
def
_PyObject_VAR_SIZE
(
typeobj
,
nitems
):
if
_PyObject_VAR_SIZE
.
_type_size_t
is
None
:
_PyObject_VAR_SIZE
.
_type_size_t
=
gdb
.
lookup_type
(
'size_t'
)
return
(
(
typeobj
.
field
(
'tp_basicsize'
)
+
nitems
*
typeobj
.
field
(
'tp_itemsize'
)
+
(
SIZEOF_VOID_P
-
1
)
)
&
~
(
SIZEOF_VOID_P
-
1
)
).
cast
(
gdb
.
lookup_type
(
'size_t'
))
(
_sizeof_void_p
()
-
1
)
)
&
~
(
_sizeof_void_p
()
-
1
)
).
cast
(
_PyObject_VAR_SIZE
.
_type_size_t
)
_PyObject_VAR_SIZE
.
_type_size_t
=
None
class
PyTypeObjectPtr
(
PyObjectPtr
):
_typename
=
'PyTypeObject'
class
HeapTypeObjectPtr
(
PyObjectPtr
):
_typename
=
'PyObject'
def
get_attr_dict
(
self
):
'''
...
...
@@ -536,9 +507,9 @@ class PyTypeObjectPtr(PyObjectPtr):
size
=
_PyObject_VAR_SIZE
(
typeobj
,
tsize
)
dictoffset
+=
size
assert
dictoffset
>
0
assert
dictoffset
%
SIZEOF_VOID_P
==
0
assert
dictoffset
%
_sizeof_void_p
()
==
0
dictptr
=
self
.
_gdbval
.
cast
(
_type_char_ptr
)
+
dictoffset
dictptr
=
self
.
_gdbval
.
cast
(
_type_char_ptr
()
)
+
dictoffset
PyObjectPtrPtr
=
PyObjectPtr
.
get_gdb_type
().
pointer
()
dictptr
=
dictptr
.
cast
(
PyObjectPtrPtr
)
return
PyObjectPtr
.
from_pyobject_ptr
(
dictptr
.
dereference
())
...
...
@@ -551,7 +522,7 @@ class PyTypeObjectPtr(PyObjectPtr):
def
proxyval
(
self
,
visited
):
'''
Support for
new-style
classes.
Support for classes.
Currently we just locate the dictionary using a transliteration to
python of _PyObject_GetDictPtr, ignoring descriptors
...
...
@@ -568,8 +539,8 @@ class PyTypeObjectPtr(PyObjectPtr):
attr_dict
=
{}
tp_name
=
self
.
safe_tp_name
()
#
New-style c
lass:
return
InstanceProxy
(
tp_name
,
attr_dict
,
int
(
self
.
_gdbval
))
#
C
lass:
return
InstanceProxy
(
tp_name
,
attr_dict
,
long
(
self
.
_gdbval
))
def
write_repr
(
self
,
out
,
visited
):
# Guard against infinite loops:
...
...
@@ -578,16 +549,9 @@ class PyTypeObjectPtr(PyObjectPtr):
return
visited
.
add
(
self
.
as_address
())
try
:
tp_name
=
self
.
field
(
'tp_name'
).
string
()
except
RuntimeError
:
tp_name
=
'unknown'
out
.
write
(
'<type %s at remote 0x%x>'
%
(
tp_name
,
self
.
as_address
()))
# pyop_attrdict = self.get_attr_dict()
# _write_instance_repr(out, visited,
# self.safe_tp_name(), pyop_attrdict, self.as_address())
pyop_attrdict
=
self
.
get_attr_dict
()
_write_instance_repr
(
out
,
visited
,
self
.
safe_tp_name
(),
pyop_attrdict
,
self
.
as_address
())
class
ProxyException
(
Exception
):
def
__init__
(
self
,
tp_name
,
args
):
...
...
@@ -597,7 +561,6 @@ class ProxyException(Exception):
def
__repr__
(
self
):
return
'%s%r'
%
(
self
.
tp_name
,
self
.
args
)
class
PyBaseExceptionObjectPtr
(
PyObjectPtr
):
"""
Class wrapping a gdb.Value that's a PyBaseExceptionObject* i.e. an exception
...
...
@@ -624,7 +587,6 @@ class PyBaseExceptionObjectPtr(PyObjectPtr):
out
.
write
(
self
.
safe_tp_name
())
self
.
write_field_repr
(
'args'
,
out
,
visited
)
class
PyClassObjectPtr
(
PyObjectPtr
):
"""
Class wrapping a gdb.Value that's a PyClassObject* i.e. a <classobj>
...
...
@@ -640,17 +602,17 @@ class BuiltInFunctionProxy(object):
def
__repr__
(
self
):
return
"<built-in function %s>"
%
self
.
ml_name
class
BuiltInMethodProxy
(
object
):
def
__init__
(
self
,
ml_name
,
pyop_m_self
):
self
.
ml_name
=
ml_name
self
.
pyop_m_self
=
pyop_m_self
def
__repr__
(
self
):
return
'<built-in method %s of %s object at remote 0x%x>'
%
(
self
.
ml_name
,
self
.
pyop_m_self
.
safe_tp_name
(),
self
.
pyop_m_self
.
as_address
())
return
(
'<built-in method %s of %s object at remote 0x%x>'
%
(
self
.
ml_name
,
self
.
pyop_m_self
.
safe_tp_name
(),
self
.
pyop_m_self
.
as_address
())
)
class
PyCFunctionObjectPtr
(
PyObjectPtr
):
"""
...
...
@@ -709,17 +671,21 @@ class PyDictObjectPtr(PyObjectPtr):
def
iteritems
(
self
):
'''
Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs,
anal
agous to dict.
items()
anal
ogous to dict.iter
items()
'''
for
i
in
safe_range
(
self
.
field
(
'ma_mask'
)
+
1
):
ep
=
self
.
field
(
'ma_table'
)
+
i
pyop_value
=
PyObjectPtr
.
from_pyobject_ptr
(
ep
[
'me_value'
])
keys
=
self
.
field
(
'ma_keys'
)
values
=
self
.
field
(
'ma_values'
)
entries
,
nentries
=
self
.
_get_entries
(
keys
)
for
i
in
safe_range
(
nentries
):
ep
=
entries
[
i
]
if
long
(
values
):
pyop_value
=
PyObjectPtr
.
from_pyobject_ptr
(
values
[
i
])
else
:
pyop_value
=
PyObjectPtr
.
from_pyobject_ptr
(
ep
[
'me_value'
])
if
not
pyop_value
.
is_null
():
pyop_key
=
PyObjectPtr
.
from_pyobject_ptr
(
ep
[
'me_key'
])
yield
(
pyop_key
,
pyop_value
)
items
=
iteritems
def
proxyval
(
self
,
visited
):
# Guard against infinite loops:
if
self
.
as_address
()
in
visited
:
...
...
@@ -727,7 +693,7 @@ class PyDictObjectPtr(PyObjectPtr):
visited
.
add
(
self
.
as_address
())
result
=
{}
for
pyop_key
,
pyop_value
in
self
.
items
():
for
pyop_key
,
pyop_value
in
self
.
ite
rite
ms
():
proxy_key
=
pyop_key
.
proxyval
(
visited
)
proxy_value
=
pyop_value
.
proxyval
(
visited
)
result
[
proxy_key
]
=
proxy_value
...
...
@@ -742,7 +708,7 @@ class PyDictObjectPtr(PyObjectPtr):
out
.
write
(
'{'
)
first
=
True
for
pyop_key
,
pyop_value
in
self
.
items
():
for
pyop_key
,
pyop_value
in
self
.
ite
rite
ms
():
if
not
first
:
out
.
write
(
', '
)
first
=
False
...
...
@@ -751,52 +717,31 @@ class PyDictObjectPtr(PyObjectPtr):
pyop_value
.
write_repr
(
out
,
visited
)
out
.
write
(
'}'
)
def
_get_entries
(
self
,
keys
):
dk_nentries
=
int
(
keys
[
'dk_nentries'
])
dk_size
=
int
(
keys
[
'dk_size'
])
try
:
# <= Python 3.5
return
keys
[
'dk_entries'
],
dk_size
except
RuntimeError
:
# >= Python 3.6
pass
class
PyInstanceObjectPtr
(
PyObjectPtr
):
_typename
=
'PyInstanceObject'
def
proxyval
(
self
,
visited
):
# Guard against infinite loops:
if
self
.
as_address
()
in
visited
:
return
ProxyAlreadyVisited
(
'<...>'
)
visited
.
add
(
self
.
as_address
())
# Get name of class:
in_class
=
self
.
pyop_field
(
'in_class'
)
cl_name
=
in_class
.
pyop_field
(
'cl_name'
).
proxyval
(
visited
)
# Get dictionary of instance attributes:
in_dict
=
self
.
pyop_field
(
'in_dict'
).
proxyval
(
visited
)
# Old-style class:
return
InstanceProxy
(
cl_name
,
in_dict
,
int
(
self
.
_gdbval
))
def
write_repr
(
self
,
out
,
visited
):
# Guard against infinite loops:
if
self
.
as_address
()
in
visited
:
out
.
write
(
'<...>'
)
return
visited
.
add
(
self
.
as_address
())
# Old-style class:
# Get name of class:
in_class
=
self
.
pyop_field
(
'in_class'
)
cl_name
=
in_class
.
pyop_field
(
'cl_name'
).
proxyval
(
visited
)
# Get dictionary of instance attributes:
pyop_in_dict
=
self
.
pyop_field
(
'in_dict'
)
_write_instance_repr
(
out
,
visited
,
cl_name
,
pyop_in_dict
,
self
.
as_address
())
if
dk_size
<=
0xFF
:
offset
=
dk_size
elif
dk_size
<=
0xFFFF
:
offset
=
2
*
dk_size
elif
dk_size
<=
0xFFFFFFFF
:
offset
=
4
*
dk_size
else
:
offset
=
8
*
dk_size
class
PyIntObjectPtr
(
PyObjectPtr
):
_typename
=
'PyIntObject'
ent_addr
=
keys
[
'dk_indices'
][
'as_1'
].
address
ent_addr
=
ent_addr
.
cast
(
_type_unsigned_char_ptr
())
+
offset
ent_ptr_t
=
gdb
.
lookup_type
(
'PyDictKeyEntry'
).
pointer
()
ent_addr
=
ent_addr
.
cast
(
ent_ptr_t
)
def
proxyval
(
self
,
visited
):
result
=
int_from_int
(
self
.
field
(
'ob_ival'
))
return
result
return
ent_addr
,
dk_nentries
class
PyListObjectPtr
(
PyObjectPtr
):
...
...
@@ -832,7 +777,6 @@ class PyListObjectPtr(PyObjectPtr):
element
.
write_repr
(
out
,
visited
)
out
.
write
(
']'
)
class
PyLongObjectPtr
(
PyObjectPtr
):
_typename
=
'PyLongObject'
...
...
@@ -854,9 +798,9 @@ class PyLongObjectPtr(PyObjectPtr):
#define PyLong_SHIFT 30
#define PyLong_SHIFT 15
'''
ob_size
=
int
(
self
.
field
(
'ob_size'
))
ob_size
=
long
(
self
.
field
(
'ob_size'
))
if
ob_size
==
0
:
return
int
(
0
)
return
0
ob_digit
=
self
.
field
(
'ob_digit'
)
...
...
@@ -865,7 +809,7 @@ class PyLongObjectPtr(PyObjectPtr):
else
:
SHIFT
=
30
digits
=
[
ob_digit
[
i
]
*
(
1
<<
(
SHIFT
*
i
)
)
digits
=
[
long
(
ob_digit
[
i
])
*
2
**
(
SHIFT
*
i
)
for
i
in
safe_range
(
abs
(
ob_size
))]
result
=
sum
(
digits
)
if
ob_size
<
0
:
...
...
@@ -883,13 +827,11 @@ class PyBoolObjectPtr(PyLongObjectPtr):
Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two
<bool> instances (Py_True/Py_False) within the process being debugged.
"""
_typename
=
'PyBoolObject'
def
proxyval
(
self
,
visited
):
castto
=
gdb
.
lookup_type
(
'PyLongObject'
).
pointer
()
self
.
_gdbval
=
self
.
_gdbval
.
cast
(
castto
)
return
bool
(
PyLongObjectPtr
(
self
.
_gdbval
).
proxyval
(
visited
))
if
PyLongObjectPtr
.
proxyval
(
self
,
visited
):
return
True
else
:
return
False
class
PyNoneStructPtr
(
PyObjectPtr
):
"""
...
...
@@ -939,10 +881,10 @@ class PyFrameObjectPtr(PyObjectPtr):
the global variables of this frame
'''
if
self
.
is_optimized_out
():
return
return
()
pyop_globals
=
self
.
pyop_field
(
'f_globals'
)
return
iter
(
pyop_globals
.
items
()
)
return
pyop_globals
.
iteritems
(
)
def
iter_builtins
(
self
):
'''
...
...
@@ -950,10 +892,10 @@ class PyFrameObjectPtr(PyObjectPtr):
the builtin variables
'''
if
self
.
is_optimized_out
():
return
return
()
pyop_builtins
=
self
.
pyop_field
(
'f_builtins'
)
return
iter
(
pyop_builtins
.
items
()
)
return
pyop_builtins
.
iteritems
(
)
def
get_var_by_name
(
self
,
name
):
'''
...
...
@@ -989,7 +931,7 @@ class PyFrameObjectPtr(PyObjectPtr):
if
self
.
is_optimized_out
():
return
None
f_trace
=
self
.
field
(
'f_trace'
)
if
f_trace
:
if
long
(
f_trace
)
!=
0
:
# we have a non-NULL f_trace:
return
self
.
f_lineno
else
:
...
...
@@ -1004,7 +946,11 @@ class PyFrameObjectPtr(PyObjectPtr):
if
self
.
is_optimized_out
():
return
'(frame information optimized out)'
filename
=
self
.
filename
()
with
open
(
os_fsencode
(
filename
),
'r'
)
as
f
:
try
:
f
=
open
(
os_fsencode
(
filename
),
'r'
)
except
IOError
:
return
None
with
f
:
all_lines
=
f
.
readlines
()
# Convert from 1-based current_line_num to 0-based list offset:
return
all_lines
[
self
.
current_line_num
()
-
1
]
...
...
@@ -1030,25 +976,39 @@ class PyFrameObjectPtr(PyObjectPtr):
out
.
write
(
')'
)
def
print_traceback
(
self
):
if
self
.
is_optimized_out
():
sys
.
stdout
.
write
(
' (frame information optimized out)
\
n
'
)
return
visited
=
set
()
sys
.
stdout
.
write
(
' File "%s", line %i, in %s
\
n
'
%
(
self
.
co_filename
.
proxyval
(
visited
),
self
.
current_line_num
(),
self
.
co_name
.
proxyval
(
visited
)))
class
PySetObjectPtr
(
PyObjectPtr
):
_typename
=
'PySetObject'
@
classmethod
def
_dummy_key
(
self
):
return
gdb
.
lookup_global_symbol
(
'_PySet_Dummy'
).
value
()
def
__iter__
(
self
):
dummy_ptr
=
self
.
_dummy_key
()
table
=
self
.
field
(
'table'
)
for
i
in
safe_range
(
self
.
field
(
'mask'
)
+
1
):
setentry
=
table
[
i
]
key
=
setentry
[
'key'
]
if
key
!=
0
and
key
!=
dummy_ptr
:
yield
PyObjectPtr
.
from_pyobject_ptr
(
key
)
def
proxyval
(
self
,
visited
):
# Guard against infinite loops:
if
self
.
as_address
()
in
visited
:
return
ProxyAlreadyVisited
(
'%s(...)'
%
self
.
safe_tp_name
())
visited
.
add
(
self
.
as_address
())
members
=
[]
table
=
self
.
field
(
'table'
)
for
i
in
safe_range
(
self
.
field
(
'mask'
)
+
1
):
setentry
=
table
[
i
]
key
=
setentry
[
'key'
]
if
key
!=
0
:
key_proxy
=
PyObjectPtr
.
from_pyobject_ptr
(
key
).
proxyval
(
visited
)
if
key_proxy
!=
'<dummy key>'
:
members
.
append
(
key_proxy
)
members
=
(
key
.
proxyval
(
visited
)
for
key
in
self
)
if
self
.
safe_tp_name
()
==
'frozenset'
:
return
frozenset
(
members
)
else
:
...
...
@@ -1077,18 +1037,11 @@ class PySetObjectPtr(PyObjectPtr):
out
.
write
(
'{'
)
first
=
True
table
=
self
.
field
(
'table'
)
for
i
in
safe_range
(
self
.
field
(
'mask'
)
+
1
):
setentry
=
table
[
i
]
key
=
setentry
[
'key'
]
if
key
!=
0
:
pyop_key
=
PyObjectPtr
.
from_pyobject_ptr
(
key
)
key_proxy
=
pyop_key
.
proxyval
(
visited
)
# FIXME!
if
key_proxy
!=
'<dummy key>'
:
if
not
first
:
out
.
write
(
', '
)
first
=
False
pyop_key
.
write_repr
(
out
,
visited
)
for
key
in
self
:
if
not
first
:
out
.
write
(
', '
)
first
=
False
key
.
write_repr
(
out
,
visited
)
out
.
write
(
'}'
)
if
tp_name
!=
'set'
:
...
...
@@ -1101,13 +1054,13 @@ class PyBytesObjectPtr(PyObjectPtr):
def
__str__
(
self
):
field_ob_size
=
self
.
field
(
'ob_size'
)
field_ob_sval
=
self
.
field
(
'ob_sval'
)
return
''
.
join
(
struct
.
pack
(
'b'
,
field_ob_sval
[
i
]
)
for
i
in
safe_range
(
field_ob_size
)
)
char_ptr
=
field_ob_sval
.
address
.
cast
(
_type_unsigned_char_ptr
()
)
return
''
.
join
([
chr
(
char_ptr
[
i
])
for
i
in
safe_range
(
field_ob_size
)]
)
def
proxyval
(
self
,
visited
):
return
str
(
self
)
def
write_repr
(
self
,
out
,
visited
,
py3
=
True
):
def
write_repr
(
self
,
out
,
visited
):
# Write this out as a Python 3 bytes literal, i.e. with a "b" prefix
# Get a PyStringObject* within the Python 2 gdb process:
...
...
@@ -1118,10 +1071,7 @@ class PyBytesObjectPtr(PyObjectPtr):
quote
=
"'"
if
"'"
in
proxy
and
not
'"'
in
proxy
:
quote
=
'"'
if
py3
:
out
.
write
(
'b'
)
out
.
write
(
'b'
)
out
.
write
(
quote
)
for
byte
in
proxy
:
if
byte
==
quote
or
byte
==
'
\
\
'
:
...
...
@@ -1145,9 +1095,6 @@ class PyBytesObjectPtr(PyObjectPtr):
class
PyStringObjectPtr
(
PyBytesObjectPtr
):
_typename
=
'PyStringObject'
def
write_repr
(
self
,
out
,
visited
):
return
super
(
PyStringObjectPtr
,
self
).
write_repr
(
out
,
visited
,
py3
=
False
)
class
PyTupleObjectPtr
(
PyObjectPtr
):
_typename
=
'PyTupleObject'
...
...
@@ -1163,8 +1110,8 @@ class PyTupleObjectPtr(PyObjectPtr):
return
ProxyAlreadyVisited
(
'(...)'
)
visited
.
add
(
self
.
as_address
())
result
=
tuple
(
[
PyObjectPtr
.
from_pyobject_ptr
(
self
[
i
]).
proxyval
(
visited
)
for
i
in
safe_range
(
int_from_int
(
self
.
field
(
'ob_size'
)))]
)
result
=
tuple
(
PyObjectPtr
.
from_pyobject_ptr
(
self
[
i
]).
proxyval
(
visited
)
for
i
in
safe_range
(
int_from_int
(
self
.
field
(
'ob_size'
)))
)
return
result
def
write_repr
(
self
,
out
,
visited
):
...
...
@@ -1185,6 +1132,9 @@ class PyTupleObjectPtr(PyObjectPtr):
else
:
out
.
write
(
')'
)
class
PyTypeObjectPtr
(
PyObjectPtr
):
_typename
=
'PyTypeObject'
def
_unichr_is_printable
(
char
):
# Logic adapted from Python 3's Tools/unicode/makeunicodedata.py
...
...
@@ -1193,12 +1143,8 @@ def _unichr_is_printable(char):
import
unicodedata
return
unicodedata
.
category
(
char
)
not
in
(
"C"
,
"Z"
)
if
sys
.
maxunicode
>=
0x10000
:
try
:
_unichr
=
unichr
except
NameError
:
_unichr
=
chr
_unichr
=
unichr
else
:
# Needed for proper surrogate support if sizeof(Py_UNICODE) is 2 in gdb
def
_unichr
(
x
):
...
...
@@ -1218,15 +1164,46 @@ class PyUnicodeObjectPtr(PyObjectPtr):
return
_type_Py_UNICODE
.
sizeof
def
proxyval
(
self
,
visited
):
# From unicodeobject.h:
# Py_ssize_t length; /* Length of raw Unicode data in buffer */
# Py_UNICODE *str; /* Raw Unicode buffer */
field_length
=
int
(
self
.
field
(
'length'
))
field_str
=
self
.
field
(
'str'
)
global
_is_pep393
if
_is_pep393
is
None
:
fields
=
gdb
.
lookup_type
(
'PyUnicodeObject'
).
target
().
fields
()
_is_pep393
=
'data'
in
[
f
.
name
for
f
in
fields
]
if
_is_pep393
:
# Python 3.3 and newer
may_have_surrogates
=
False
compact
=
self
.
field
(
'_base'
)
ascii
=
compact
[
'_base'
]
state
=
ascii
[
'state'
]
is_compact_ascii
=
(
int
(
state
[
'ascii'
])
and
int
(
state
[
'compact'
]))
if
not
int
(
state
[
'ready'
]):
# string is not ready
field_length
=
long
(
compact
[
'wstr_length'
])
may_have_surrogates
=
True
field_str
=
ascii
[
'wstr'
]
else
:
field_length
=
long
(
ascii
[
'length'
])
if
is_compact_ascii
:
field_str
=
ascii
.
address
+
1
elif
int
(
state
[
'compact'
]):
field_str
=
compact
.
address
+
1
else
:
field_str
=
self
.
field
(
'data'
)[
'any'
]
repr_kind
=
int
(
state
[
'kind'
])
if
repr_kind
==
1
:
field_str
=
field_str
.
cast
(
_type_unsigned_char_ptr
())
elif
repr_kind
==
2
:
field_str
=
field_str
.
cast
(
_type_unsigned_short_ptr
())
elif
repr_kind
==
4
:
field_str
=
field_str
.
cast
(
_type_unsigned_int_ptr
())
else
:
# Python 3.2 and earlier
field_length
=
long
(
self
.
field
(
'length'
))
field_str
=
self
.
field
(
'str'
)
may_have_surrogates
=
self
.
char_width
()
==
2
# Gather a list of ints from the Py_UNICODE array; these are either
# UCS-2 or UCS-4 code points:
if
self
.
char_width
()
>
2
:
# UCS-
1, UCS-
2 or UCS-4 code points:
if
not
may_have_surrogates
:
Py_UNICODEs
=
[
int
(
field_str
[
i
])
for
i
in
safe_range
(
field_length
)]
else
:
# A more elaborate routine if sizeof(Py_UNICODE) is 2 in the
...
...
@@ -1253,24 +1230,19 @@ class PyUnicodeObjectPtr(PyObjectPtr):
# Convert the int code points to unicode characters, and generate a
# local unicode instance.
# This splits surrogate pairs if sizeof(Py_UNICODE) is 2 here (in gdb).
result
=
u''
.
join
([
_unichr
(
ucs
)
for
ucs
in
Py_UNICODEs
])
result
=
u''
.
join
([
(
_unichr
(
ucs
)
if
ucs
<=
0x10ffff
else
'
\
ufffd
'
)
for
ucs
in
Py_UNICODEs
])
return
result
def
write_repr
(
self
,
out
,
visited
):
# Write this out as a Python 3 str literal, i.e. without a "u" prefix
# Get a PyUnicodeObject* within the Python 2 gdb process:
proxy
=
self
.
proxyval
(
visited
)
# Transliteration of Python 3's Object/unicodeobject.c:unicode_repr
# to Python 2:
try
:
gdb
.
parse_and_eval
(
'PyString_Type'
)
except
RuntimeError
:
# Python 3, don't write 'u' as prefix
pass
else
:
# Python 2, write the 'u'
out
.
write
(
'u'
)
if
"'"
in
proxy
and
'"'
not
in
proxy
:
quote
=
'"'
else
:
...
...
@@ -1371,16 +1343,40 @@ class PyUnicodeObjectPtr(PyObjectPtr):
out
.
write
(
quote
)
def
__unicode__
(
self
):
return
self
.
proxyval
(
set
())
def
__str__
(
self
):
# In Python 3, everything is unicode (including attributes of e.g.
# code objects, such as function names). The Python 2 debugger code
# uses PyUnicodePtr objects to format strings etc, whereas with a
# Python 2 debuggee we'd get PyStringObjectPtr instances with __str__.
# Be compatible with that.
return
unicode
(
self
).
encode
(
'UTF-8'
)
class
wrapperobject
(
PyObjectPtr
):
_typename
=
'wrapperobject'
def
safe_name
(
self
):
try
:
name
=
self
.
field
(
'descr'
)[
'd_base'
][
'name'
].
string
()
return
repr
(
name
)
except
(
NullPyObjectPtr
,
RuntimeError
):
return
'<unknown name>'
def
safe_tp_name
(
self
):
try
:
return
self
.
field
(
'self'
)[
'ob_type'
][
'tp_name'
].
string
()
except
(
NullPyObjectPtr
,
RuntimeError
):
return
'<unknown tp_name>'
def
safe_self_addresss
(
self
):
try
:
address
=
long
(
self
.
field
(
'self'
))
return
'%#x'
%
address
except
(
NullPyObjectPtr
,
RuntimeError
):
return
'<failed to get self address>'
def
proxyval
(
self
,
visited
):
name
=
self
.
safe_name
()
tp_name
=
self
.
safe_tp_name
()
self_address
=
self
.
safe_self_addresss
()
return
(
"<method-wrapper %s of %s object at %s>"
%
(
name
,
tp_name
,
self_address
))
def
write_repr
(
self
,
out
,
visited
):
proxy
=
self
.
proxyval
(
visited
)
out
.
write
(
proxy
)
def
int_from_int
(
gdbval
):
...
...
@@ -1413,13 +1409,15 @@ class PyObjectPtrPrinter:
proxyval
=
pyop
.
proxyval
(
set
())
return
stringify
(
proxyval
)
def
pretty_printer_lookup
(
gdbval
):
type
=
gdbval
.
type
.
unqualified
()
if
type
.
code
==
gdb
.
TYPE_CODE_PTR
:
type
=
type
.
target
().
unqualified
()
if
str
(
type
)
in
all_pretty_typenames
:
return
PyObjectPtrPrinter
(
gdbval
)
if
type
.
code
!=
gdb
.
TYPE_CODE_PTR
:
return
None
type
=
type
.
target
().
unqualified
()
t
=
str
(
type
)
if
t
in
(
"PyObject"
,
"PyFrameObject"
,
"PyUnicodeObject"
,
"wrapperobject"
):
return
PyObjectPtrPrinter
(
gdbval
)
"""
During development, I've been manually invoking the code in this way:
...
...
@@ -1440,22 +1438,21 @@ that this python file is installed to the same path as the library (or its
/usr/lib/libpython2.6.so.1.0-gdb.py
/usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py
"""
def
register
(
obj
):
def
register
(
obj
):
if
obj
is
None
:
obj
=
gdb
# Wire up the pretty-printer
obj
.
pretty_printers
.
append
(
pretty_printer_lookup
)
register
(
gdb
.
current_objfile
())
register
(
gdb
.
current_objfile
())
# Unfortunately, the exact API exposed by the gdb module varies somewhat
# from build to build
# See http://bugs.python.org/issue8279?#msg102276
class
Frame
(
object
):
'''
Wrapper for gdb.Frame, adding various methods
...
...
@@ -1500,9 +1497,26 @@ class Frame(object):
iter_frame
=
iter_frame
.
newer
()
return
index
def
is_evalframeex
(
self
):
'''Is this a PyEval_EvalFrameEx frame?'''
if
self
.
_gdbframe
.
name
()
==
'PyEval_EvalFrameEx'
:
# We divide frames into:
# - "python frames":
# - "bytecode frames" i.e. PyEval_EvalFrameEx
# - "other python frames": things that are of interest from a python
# POV, but aren't bytecode (e.g. GC, GIL)
# - everything else
def
is_python_frame
(
self
):
'''Is this a _PyEval_EvalFrameDefault frame, or some other important
frame? (see is_other_python_frame for what "important" means in this
context)'''
if
self
.
is_evalframe
():
return
True
if
self
.
is_other_python_frame
():
return
True
return
False
def
is_evalframe
(
self
):
'''Is this a _PyEval_EvalFrameDefault frame?'''
if
self
.
_gdbframe
.
name
()
==
EVALFRAME
:
'''
I believe we also need to filter on the inline
struct frame_id.inline_depth, only regarding frames with
...
...
@@ -1511,44 +1525,87 @@ class Frame(object):
So we reject those with type gdb.INLINE_FRAME
'''
if
self
.
_gdbframe
.
type
()
==
gdb
.
NORMAL_FRAME
:
# We have a
PyEval_EvalFrameEx
frame:
# We have a
_PyEval_EvalFrameDefault
frame:
return
True
return
False
def
read_var
(
self
,
varname
):
"""
read_var with respect to code blocks (gdbframe.read_var works with
respect to the most recent block)
def
is_other_python_frame
(
self
):
'''Is this frame worth displaying in python backtraces?
Examples:
- waiting on the GIL
- garbage-collecting
- within a CFunction
If it is, return a descriptive string
For other frames, return False
'''
if
self
.
is_waiting_for_gil
():
return
'Waiting for the GIL'
if
self
.
is_gc_collect
():
return
'Garbage-collecting'
# Detect invocations of PyCFunction instances:
frame
=
self
.
_gdbframe
caller
=
frame
.
name
()
if
not
caller
:
return
False
Apparently this function doesn't work, though, as it seems to read
variables in other frames also sometimes.
"""
block
=
self
.
_gdbframe
.
block
()
var
=
None
if
caller
in
(
'_PyCFunction_FastCallDict'
,
'_PyCFunction_FastCallKeywords'
):
arg_name
=
'func'
# Within that frame:
# "func" is the local containing the PyObject* of the
# PyCFunctionObject instance
# "f" is the same value, but cast to (PyCFunctionObject*)
# "self" is the (PyObject*) of the 'self'
try
:
# Use the prettyprinter for the func:
func
=
frame
.
read_var
(
arg_name
)
return
str
(
func
)
except
RuntimeError
:
return
'PyCFunction invocation (unable to read %s)'
%
arg_name
while
block
and
var
is
None
:
if
caller
==
'wrapper_call'
:
try
:
var
=
self
.
_gdbframe
.
read_var
(
varname
,
block
)
except
ValueError
:
pass
func
=
frame
.
read_var
(
'wp'
)
return
str
(
func
)
except
RuntimeError
:
return
'<wrapper_call invocation>'
# This frame isn't worth reporting:
return
False
block
=
block
.
superblock
def
is_waiting_for_gil
(
self
):
'''Is this frame waiting on the GIL?'''
# This assumes the _POSIX_THREADS version of Python/ceval_gil.h:
name
=
self
.
_gdbframe
.
name
()
if
name
:
return
'pthread_cond_timedwait'
in
name
return
var
def
is_gc_collect
(
self
):
'''Is this frame "collect" within the garbage-collector?'''
return
self
.
_gdbframe
.
name
()
==
'collect'
def
get_pyop
(
self
):
try
:
# self.read_var does not always work properly, so select our frame
# and restore the previously selected frame
selected_frame
=
gdb
.
selected_frame
()
self
.
_gdbframe
.
select
()
f
=
gdb
.
parse_and_eval
(
'f'
)
selected_frame
.
select
()
except
RuntimeError
:
f
=
self
.
_gdbframe
.
read_var
(
'f'
)
frame
=
PyFrameObjectPtr
.
from_pyobject_ptr
(
f
)
if
not
frame
.
is_optimized_out
():
return
frame
# gdb is unable to get the "f" argument of PyEval_EvalFrameEx()
# because it was "optimized out". Try to get "f" from the frame
# of the caller, PyEval_EvalCodeEx().
orig_frame
=
frame
caller
=
self
.
_gdbframe
.
older
()
if
caller
:
f
=
caller
.
read_var
(
'f'
)
frame
=
PyFrameObjectPtr
.
from_pyobject_ptr
(
f
)
if
not
frame
.
is_optimized_out
():
return
frame
return
orig_frame
except
ValueError
:
return
None
else
:
return
PyFrameObjectPtr
.
from_pyobject_ptr
(
f
)
@
classmethod
def
get_selected_frame
(
cls
):
...
...
@@ -1559,12 +1616,30 @@ class Frame(object):
@
classmethod
def
get_selected_python_frame
(
cls
):
'''Try to obtain the Frame for the python code in the selected frame,
or None'''
'''Try to obtain the Frame for the python-related code in the selected
frame, or None'''
try
:
frame
=
cls
.
get_selected_frame
()
except
gdb
.
error
:
# No frame: Python didn't start yet
return
None
while
frame
:
if
frame
.
is_python_frame
():
return
frame
frame
=
frame
.
older
()
# Not found:
return
None
@
classmethod
def
get_selected_bytecode_frame
(
cls
):
'''Try to obtain the Frame for the python bytecode interpreter in the
selected GDB frame, or None'''
frame
=
cls
.
get_selected_frame
()
while
frame
:
if
frame
.
is_evalframe
ex
():
if
frame
.
is_evalframe
():
return
frame
frame
=
frame
.
older
()
...
...
@@ -1572,17 +1647,41 @@ class Frame(object):
return
None
def
print_summary
(
self
):
if
self
.
is_evalframe
ex
():
if
self
.
is_evalframe
():
pyop
=
self
.
get_pyop
()
if
pyop
:
line
=
pyop
.
get_truncated_repr
(
MAX_OUTPUT_LEN
)
write_unicode
(
sys
.
stdout
,
'#%i %s
\
n
'
%
(
self
.
get_index
(),
line
))
sys
.
stdout
.
write
(
pyop
.
current_line
())
if
not
pyop
.
is_optimized_out
():
line
=
pyop
.
current_line
()
if
line
is
not
None
:
sys
.
stdout
.
write
(
' %s
\
n
'
%
line
.
strip
())
else
:
sys
.
stdout
.
write
(
'#%i (unable to read python frame information)
\
n
'
%
self
.
get_index
())
else
:
sys
.
stdout
.
write
(
'#%i
\
n
'
%
self
.
get_index
())
info
=
self
.
is_other_python_frame
()
if
info
:
sys
.
stdout
.
write
(
'#%i %s
\
n
'
%
(
self
.
get_index
(),
info
))
else
:
sys
.
stdout
.
write
(
'#%i
\
n
'
%
self
.
get_index
())
def
print_traceback
(
self
):
if
self
.
is_evalframe
():
pyop
=
self
.
get_pyop
()
if
pyop
:
pyop
.
print_traceback
()
if
not
pyop
.
is_optimized_out
():
line
=
pyop
.
current_line
()
if
line
is
not
None
:
sys
.
stdout
.
write
(
' %s
\
n
'
%
line
.
strip
())
else
:
sys
.
stdout
.
write
(
' (unable to read python frame information)
\
n
'
)
else
:
info
=
self
.
is_other_python_frame
()
if
info
:
sys
.
stdout
.
write
(
' %s
\
n
'
%
info
)
else
:
sys
.
stdout
.
write
(
' (not a python frame)
\
n
'
)
class
PyList
(
gdb
.
Command
):
'''List the current Python source code, if any
...
...
@@ -1602,6 +1701,7 @@ class PyList(gdb.Command):
gdb
.
COMMAND_FILES
,
gdb
.
COMPLETE_NONE
)
def
invoke
(
self
,
args
,
from_tty
):
import
re
...
...
@@ -1617,13 +1717,14 @@ class PyList(gdb.Command):
if m:
start, end = map(int, m.groups())
frame = Frame.get_selected_python_frame()
# py-list requires an actual PyEval_EvalFrameEx frame:
frame = Frame.get_selected_bytecode_frame()
if not frame:
print('
Unable
to
locate
python
frame
')
print('
Unable
to
locate
gdb
frame
for
python
bytecode
interpreter
')
return
pyop = frame.get_pyop()
if not pyop:
if not pyop
or pyop.is_optimized_out()
:
print('
Unable
to
read
information
on
python
frame
')
return
...
...
@@ -1637,7 +1738,13 @@ class PyList(gdb.Command):
if start<1:
start = 1
with open(os_fsencode(filename), 'r') as f:
try:
f = open(os_fsencode(filename), 'r')
except IOError as err:
sys.stdout.write('
Unable
to
open
%
s
:
%
s
\
n
'
% (filename, err))
return
with f:
all_lines = f.readlines()
# start and end are 1-based, all_lines is 0-based;
# so [start-1:end] as a python slice gives us [start, end] as a
...
...
@@ -1649,13 +1756,17 @@ class PyList(gdb.Command):
linestr = '
>
' + linestr
sys.stdout.write('
%
4
s
%
s
' % (linestr, line))
# ...and register the command:
PyList()
def move_in_stack(move_up):
'''Move up or down the stack (for the py-up/py-down command)'''
frame = Frame.get_selected_python_frame()
if not frame:
print('
Unable
to
locate
python
frame
')
return
while frame:
if move_up:
iter_frame = frame.older()
...
...
@@ -1665,7 +1776,7 @@ def move_in_stack(move_up):
if not iter_frame:
break
if iter_frame.is_
evalframeex
():
if iter_frame.is_
python_frame
():
# Result:
if iter_frame.select():
iter_frame.print_summary()
...
...
@@ -1678,7 +1789,6 @@ def move_in_stack(move_up):
else:
print('
Unable
to
find
a
newer
python
frame
')
class PyUp(gdb.Command):
'
Select
and
print
the
python
stack
frame
that
called
this
one
(
if
any
)
'
def __init__(self):
...
...
@@ -1687,10 +1797,10 @@ class PyUp(gdb.Command):
gdb.COMMAND_STACK,
gdb.COMPLETE_NONE)
def invoke(self, args, from_tty):
move_in_stack(move_up=True)
class PyDown(gdb.Command):
'
Select
and
print
the
python
stack
frame
called
by
this
one
(
if
any
)
'
def __init__(self):
...
...
@@ -1699,15 +1809,36 @@ class PyDown(gdb.Command):
gdb.COMMAND_STACK,
gdb.COMPLETE_NONE)
def invoke(self, args, from_tty):
move_in_stack(move_up=False)
# Not all builds of gdb have gdb.Frame.select
if hasattr(gdb.Frame, '
select
'):
PyUp()
PyDown()
class PyBacktraceFull(gdb.Command):
'
Display
the
current
python
frame
and
all
the
frames
within
its
call
stack
(
if
any
)
'
def __init__(self):
gdb.Command.__init__ (self,
"py-bt-full",
gdb.COMMAND_STACK,
gdb.COMPLETE_NONE)
def invoke(self, args, from_tty):
frame = Frame.get_selected_python_frame()
if not frame:
print('
Unable
to
locate
python
frame
')
return
while frame:
if frame.is_python_frame():
frame.print_summary()
frame = frame.older()
PyBacktraceFull()
class PyBacktrace(gdb.Command):
'
Display
the
current
python
frame
and
all
the
frames
within
its
call
stack
(
if
any
)
'
...
...
@@ -1720,14 +1851,18 @@ class PyBacktrace(gdb.Command):
def invoke(self, args, from_tty):
frame = Frame.get_selected_python_frame()
if not frame:
print('
Unable
to
locate
python
frame
')
return
sys.stdout.write('
Traceback
(
most
recent
call
first
):
\
n
')
while frame:
if frame.is_
evalframeex
():
frame.print_
summary
()
if frame.is_
python_frame
():
frame.print_
traceback
()
frame = frame.older()
PyBacktrace()
class PyPrint(gdb.Command):
'
Look
up
the
given
python
variable
name
,
and
print
it
'
def __init__(self):
...
...
@@ -1736,6 +1871,7 @@ class PyPrint(gdb.Command):
gdb.COMMAND_DATA,
gdb.COMPLETE_NONE)
def invoke(self, args, from_tty):
name = str(args)
...
...
@@ -1752,16 +1888,23 @@ class PyPrint(gdb.Command):
pyop_var, scope = pyop_frame.get_var_by_name(name)
if pyop_var:
print('
%
s
%
r
=
%
s
' % (
scope, name, pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
print('
%
s
%
r
=
%
s
'
% (scope,
name,
pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
else:
print('
%
r
not
found
' % name)
PyPrint()
class PyLocals(gdb.Command):
'
Look
up
the
given
python
variable
name
,
and
print
it
'
def __init__(self, command="py-locals"):
gdb.Command.__init__ (self,
command,
gdb.COMMAND_DATA,
gdb.COMPLETE_NONE)
def invoke(self, args, from_tty):
name = str(args)
...
...
@@ -1790,6 +1933,18 @@ class PyLocals(gdb.Command):
def get_namespace(self, pyop_frame):
return pyop_frame.iter_locals()
PyLocals()
##################################################################
## added, not in CPython
##################################################################
import atexit
import warnings
import tempfile
import textwrap
import itertools
class PyGlobals(PyLocals):
'
List
all
the
globals
in
the
currently
select
Python
frame
'
...
...
@@ -1798,8 +1953,7 @@ class PyGlobals(PyLocals):
return pyop_frame.iter_globals()
PyLocals("py-locals", gdb.COMMAND_DATA, gdb.COMPLETE_NONE)
PyGlobals("py-globals", gdb.COMMAND_DATA, gdb.COMPLETE_NONE)
PyGlobals("py-globals")
class PyNameEquals(gdb.Function):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment