[Python-Dev] GDB macros in Misc/gdbinit are broken

Victor Stinner victor.stinner at gmail.com
Tue Feb 28 15:40:47 EST 2017


Ok, it seems like very few people know how to use python-gdb.py :-/
Sorry, I expect that everybody was using it!

python-gdb.py was written by Dave Malcolm, it includes:

* a pretty printer for many builtin Python types: str, tuple, list,
dict, frame, etc. It means that a regular gdb "print obj" command
displays the Python object content instead of a raw pointer. This
feature is super useful, it avoids to call _PyObject_Dump(obj) which
is likely to crash!
* commands to navigate between Python frames: py-up, py-down
* command to dump the Python traceback: py-bt, py-bt-full (also
include C frames)
* a few more commands

For me, these commands are really amazing! It's impressive to be able
to that in a debugger which doesn't know Python internals at all! It's
cool to be able to "program" (extend) a debugger!

I never tried to install it, I always use it with a ./python binary
compiled in the source code tree:
---
$ make
$ gdb ./python
# load ./python-gdb.py
...
---

Note: ./python-gdb.py is simply a copy of Tools/gdb/libpython.py,
copied by Makefile.

On Fedora, gdb doesn't load python-gdb.py because it doesn't trust my
$HOME/prog directory (root of all my development directories). I had
to trust it using this ~/.gdbinit config:
---
add-auto-load-safe-path ~/prog/
---

More generally, when gdb loads a "program", it tries to load
"program-gdb.py" in the same directory. Maybe it can load
"program-gdb.py" from other directories, but I never understood this
part, and hopefully I never had to understand it :-D

Maybe the debug package of Python on Debian and Fedora installs
pythonX.Y-gdb.py in the right directory, I didn't check, but I always
debug using a freshly compiled Python, so I never tried to understand
how these things work.

Example:
----
haypo at selma$ gdb ./python
(gdb) b _PyEval_EvalFrameDefault
(gdb) run

Breakpoint 1, _PyEval_EvalFrameDefault (
    f=Frame 0x7ffff7f22058, for file <frozen importlib._bootstrap>,
line 25, in <module> (), throwflag=0) at Python/ceval.c:678
678        PyObject *retval = NULL;            /* Return value */

=> gdb displays the content of the frame "f", you can see immediately
the filename and the line number (well, this frame is a little bit
special, it's the frozen module importlib)

(gdb) py-bt
Traceback (most recent call first):
  File "<frozen importlib._bootstrap>", line 25, in <module>
---

Example of Python traceback:
----
haypo at selma$ gdb -args ./python -m test -r
(gdb) run
...
^C
(gdb) py-bt
Traceback (most recent call first):
  File "/home/haypo/prog/python/master/Lib/test/test_long.py", line
947, in test_bit_length
    self.assertEqual(k, len(bin(x).lstrip('-0b')))
  File "/home/haypo/prog/python/master/Lib/unittest/case.py", line 601, in run
    testMethod()
  File "/home/haypo/prog/python/master/Lib/unittest/case.py", line
649, in __call__
    return self.run(*args, **kwds)
  File "/home/haypo/prog/python/master/Lib/unittest/suite.py", line 122, in run
    test(result)
  File "/home/haypo/prog/python/master/Lib/unittest/suite.py", line
84, in __call__
    return self.run(*args, **kwds)
  ...

=> you get filename, line number, function name and even the Python
line! It seems obvious to get such information, but remind that you
are in gdb, not in Python

(gdb) py-list
 942        def test_bit_length(self):
 943            tiny = 1e-10
 944            for x in range(-65000, 65000):
 945                k = x.bit_length()
 946                # Check equivalence with Python version
>947                self.assertEqual(k, len(bin(x).lstrip('-0b')))
 948                # Behaviour as specified in the docs
 949                if x != 0:
 950                    self.assertTrue(2**(k-1) <= abs(x) < 2**k)
 951                else:
 952                    self.assertEqual(k, 0)

# move to the parent Python frame (= skip mutiple C frames until the
next Python frame)
(gdb) py-up
(...)

(gdb) py-list
 596                with outcome.testPartExecutor(self):
 597                    self.setUp()
 598                if outcome.success:
 599                    outcome.expecting_failure = expecting_failure
 600                    with outcome.testPartExecutor(self, isTest=True):
>601                        testMethod()
 602                    outcome.expecting_failure = False
 603                    with outcome.testPartExecutor(self):
 604                        self.tearDown()
 605
 606                self.doCleanups()

(gdb) py-locals
self = <LongTest(_testMethodName='...
result = <TestResult(...
testMethod = <method at remote 0x7fffe8bf4d78>
expecting_failure_method = False
expecting_failure_class = False
expecting_failure = False
...

=> dump all variables of the current Python frame!
--------

Victor


More information about the Python-Dev mailing list