python3 provides gdb extensions. Using
them, gdb can attach to a running program, select a thread and print its
python backtrace.
On Debian (since at least Buster) the extensions are part of the
python3.x-dbg package (ex. python3.10-dbg
installs
/usr/share/gdb/auto-load/usr/bin/python3.10-gdb.py
) and gdb auto-loads them.
Example with a simple threaded python script:
#!/usr/bin/env python3
import signal
import threading
def a():
while True:
pass
def b():
while True:
signal.pause()
threading.Thread(target=a).start()
threading.Thread(target=b).start()
Running gdb:
user@vsid:~$ ps -C python3 -L
PID LWP TTY TIME CMD
1215 1215 pts/0 00:00:00 python3
1215 1216 pts/0 00:00:19 python3
1215 1217 pts/0 00:00:00 python3
user@vsid:~$ gdb -p 1215
GNU gdb (Debian 10.1-2+b1) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
[...]
(gdb) info auto-load python-scripts
Loaded Script
Yes /usr/share/gdb/auto-load/usr/bin/python3.10-gdb.py
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7f2f034b4740 (LWP 1215) "python3" 0x00007f2f036a60fa in __futex_abstimed_wait_common64 (futex_word=futex_word@entry=0x7f2ef4000b60, expected=expected@entry=0,
clockid=clockid@entry=0, abstime=abstime@entry=0x0, private=<optimized out>,
cancel=cancel@entry=true) at ../sysdeps/nptl/futex-internal.c:74
2 Thread 0x7f2f02ea7640 (LWP 1216) "python3" 0x000000000051b858 in _PyEval_EvalFrameDefault
(tstate=<optimized out>, f=<optimized out>, throwflag=<optimized out>)
at ../Python/ceval.c:3850
3 Thread 0x7f2f026a6640 (LWP 1217) "python3" 0x00007f2f036a3932 in __libc_pause ()
at ../sysdeps/unix/sysv/linux/pause.c:29
(gdb) thread 2
(gdb) py-bt
Traceback (most recent call first):
File "/root/./threaded.py", line 7, in a
while True:
File "/usr/lib/python3.10/threading.py", line 946, in run
self._target(*self._args, **self._kwargs)
File "/usr/lib/python3.10/threading.py", line 1009, in _bootstrap_inner
self.run()
File "/usr/lib/python3.10/threading.py", line 966, in _bootstrap
self._bootstrap_inner()
(gdb)
We can confirm that thread 1216
which used the most cpu time according to
ps
is indeed the thread running function a()
that is busy-looping.