Отладка расширений C API и внутреннего устройства CPython с помощью GDB
В этом документе объясняется, как расширение Python для GDB, python-gdb.py, можно использовать вместе с отладчиком GDB для отладки расширений CPython и самого интерпретатора CPython.
При отладке низкоуровневых проблем, таких как сбои или взаимоблокировки, низкоуровневый отладчик, например GDB, помогает диагностировать и устранять проблему. По умолчанию GDB (или любой из его интерфейсов) не поддерживает высокоуровневую информацию, специфичную для интерпретатора CPython.
Расширение python-gdb.py добавляет в GDB информацию об интерпретаторе CPython. Оно помогает исследовать стек выполняющихся в данный момент функций Python. Если объект Python представлен указателем PyObject*, расширение показывает тип и значение объекта.
Разработчики расширений CPython, а также те, кто экспериментирует с написанными на C частями CPython, могут узнать из этого документа, как использовать расширение python-gdb.py вместе с GDB.
Примечание
Предполагается, что вы знакомы с основами GDB и C API CPython. В документе собраны рекомендации из руководства разработчика и вики Python.
Предварительные требования
Вам понадобятся:
- GDB версии 7 или новее. (Для более ранних версий GDB см.
Misc/gdbinitв исходном коде Python 3.11 или более ранней версии.) - Совместимая с GDB отладочная информация для Python и любого расширения, которое вы отлаживаете.
- Расширение
python-gdb.py.
Расширение собирается вместе с Python, но может распространяться отдельно или не распространяться вовсе. Ниже приведены примеры советов для нескольких распространённых систем. Обратите внимание: даже если инструкции подходят для вашей системы, они могут быть устаревшими.
Настройка Python, собранного из исходного кода
При сборке CPython из исходного кода отладочная информация должна быть доступна, а в корневом каталоге репозитория в результате сборки должен появиться файл python-gdb.py.
Чтобы включить поддержку, необходимо добавить каталог, содержащий python-gdb.py, в список «auto-load-safe-path» GDB. Если вы этого не сделали, последние версии GDB выведут предупреждение с инструкциями.
Примечание
Если инструкции для вашей версии GDB не появились, добавьте это в файл конфигурации (~/.gdbinit или ~/.config/gdb/gdbinit):
add-auto-load-safe-path /path/to/cpython
Также можно добавить несколько путей, разделив их символом :.
Настройка Python из дистрибутива Linux
В большинстве систем Linux отладочная информация для системного Python предоставляется в пакете с названием python-debuginfo, python-dbg или похожим. Например:
-
Fedora:
sudo dnf install gdb sudo dnf debuginfo-install python3
-
Ubuntu:
sudo apt install gdb python3-dbg
В нескольких последних версиях Linux GDB может автоматически загружать отладочные символы с помощью debuginfod. Однако это не устанавливает расширение python-gdb.py; как правило, пакет с отладочной информацией необходимо установить отдельно.
Использование отладочной сборки и режима разработки
Для упрощения отладки можно:
- Использовать отладочную сборку Python. (При сборке из исходного кода используйте
configure --with-pydebug. В дистрибутивах Linux установите и запустите такой пакет, какpython-debugилиpython-dbg, если он доступен.) - Использовать режим разработки во время выполнения (
-X dev).
Оба варианта включают дополнительные проверки и отключают некоторые оптимизации. Иногда из-за этого ошибка, которую вы пытаетесь найти, не проявляется, но в большинстве случаев отладка становится проще.
Использование расширения python-gdb
После загрузки расширение предоставляет две основные возможности: принтеры форматированного вывода для значений Python и дополнительные команды.
Принтеры форматированного вывода
Вот как выглядит (сокращённый) стек вызовов GDB при включённом расширении:
#0 0x000000000041a6b1 in PyObject_Malloc (nbytes=Cannot access memory at address 0x7fffff7fefe8
) at Objects/obmalloc.c:748
#1 0x000000000041b7c0 in _PyObject_DebugMallocApi (id=111 'o', nbytes=24) at Objects/obmalloc.c:1445
#2 0x000000000041b717 in _PyObject_DebugMalloc (nbytes=24) at Objects/obmalloc.c:1412
#3 0x000000000044060a in _PyUnicode_New (length=11) at Objects/unicodeobject.c:346
#4 0x00000000004466aa in PyUnicodeUCS2_DecodeUTF8Stateful (s=0x5c2b8d "__lltrace__", size=11, errors=0x0, consumed=
0x0) at Objects/unicodeobject.c:2531
#5 0x0000000000446647 in PyUnicodeUCS2_DecodeUTF8 (s=0x5c2b8d "__lltrace__", size=11, errors=0x0)
at Objects/unicodeobject.c:2495
#6 0x0000000000440d1b in PyUnicodeUCS2_FromStringAndSize (u=0x5c2b8d "__lltrace__", size=11)
at Objects/unicodeobject.c:551
#7 0x0000000000440d94 in PyUnicodeUCS2_FromString (u=0x5c2b8d "__lltrace__") at Objects/unicodeobject.c:569
#8 0x0000000000584abd in PyDict_GetItemString (v=
{'Yuck': <type at remote 0xad4730>, '__builtins__': <module at remote 0x7ffff7fd5ee8>, '__file__': 'Lib/test/crashers/nasty_eq_vs_dict.py', '__package__': None, 'y': <Yuck(i=0) at remote 0xaacd80>, 'dict': {0: 0, 1: 1, 2: 2, 3: 3}, '__cached__': None, '__name__': '__main__', 'z': <Yuck(i=0) at remote 0xaace60>, '__doc__': None}, key=
0x5c2b8d "__lltrace__") at Objects/dictobject.c:2171
Обратите внимание, что аргумент-словарь команды PyDict_GetItemString отображается как repr(), а не как непрозрачный указатель PyObject *.
Расширение работает, предоставляя пользовательскую функцию форматированного вывода для значений типа PyObject *. Если вам нужно получить доступ к низкоуровневым сведениям об объекте, приведите значение к указателю соответствующего типа. Например:
(gdb) p globals
$1 = {'__builtins__': <module at remote 0x7ffff7fb1868>, '__name__':
'__main__', 'ctypes': <module at remote 0x7ffff7f14360>, '__doc__': None,
'__package__': None}
(gdb) p *(PyDictObject*)globals
$2 = {ob_refcnt = 3, ob_type = 0x3dbdf85820, ma_fill = 5, ma_used = 5,
ma_mask = 7, ma_table = 0x63d0f8, ma_lookup = 0x3dbdc7ea70
<lookdict_string>, ma_smalltable = {{me_hash = 7065186196740147912,
me_key = '__builtins__', me_value = <module at remote 0x7ffff7fb1868>},
{me_hash = -368181376027291943, me_key = '__name__',
me_value ='__main__'}, {me_hash = 0, me_key = 0x0, me_value = 0x0},
{me_hash = 0, me_key = 0x0, me_value = 0x0},
{me_hash = -9177857982131165996, me_key = 'ctypes',
me_value = <module at remote 0x7ffff7f14360>},
{me_hash = -8518757509529533123, me_key = '__doc__', me_value = None},
{me_hash = 0, me_key = 0x0, me_value = 0x0}, {
me_hash = 6614918939584953775, me_key = '__package__', me_value = None}}}
Обратите внимание: принтеры форматированного вывода фактически не вызывают repr(). Для базовых типов они стараются максимально точно воспроизвести его результат.
Путаницу может вызвать то, что пользовательский принтер для некоторых типов выглядит почти так же, как встроенный принтер GDB для стандартных типов. Например, принтер форматированного вывода для Python int (PyLongObject*) выдаёт представление, которое невозможно отличить от обычного целого числа на уровне машины:
(gdb) p some_machine_integer $3 = 42 (gdb) p some_python_integer $4 = 42
Внутреннюю структуру можно увидеть, приведя значение к типу PyLongObject*:
(gdb) p *(PyLongObject*)some_python_integer
$5 = {ob_base = {ob_base = {ob_refcnt = 8, ob_type = 0x3dad39f5e0}, ob_size = 1},
ob_digit = {42}}
Подобная путаница возможна и с типом str: вывод очень похож на результат встроенного принтера gdb для char *:
(gdb) p ptr_to_python_str $6 = '__builtins__'
По умолчанию принтер форматированного вывода для экземпляров str использует одинарные кавычки (как и repr Python для строк), тогда как стандартный принтер для значений char * использует двойные кавычки и содержит шестнадцатеричный адрес:
(gdb) p ptr_to_char_star $7 = 0x6d72c0 "hello world"
И снова детали реализации можно увидеть, приведя значение к типу PyUnicodeObject*:
(gdb) p *(PyUnicodeObject*)$6
$8 = {ob_base = {ob_refcnt = 33, ob_type = 0x3dad3a95a0}, length = 12,
str = 0x7ffff2128500, hash = 7065186196740147912, state = 1, defenc = 0x0}
py-list
Расширение добавляет команду py-list, которая выводит исходный код Python (если он есть) для текущего кадра в выбранном потоке. Текущая строка отмечена символом «>»:
(gdb) py-list 901 if options.profile: 902 options.profile = False 903 profile_me() 904 return 905 >906 u = UI() 907 if not u.quit: 908 try: 909 gtk.main() 910 except KeyboardInterrupt: 911 # properly quit on a keyboard interrupt...
Используйте py-list START, чтобы вывести исходный код Python, начиная с другой строки, а py-list START,END — чтобы вывести определённый диапазон строк исходного кода Python.
py-up и py-down
Команды py-up и py-down аналогичны стандартным командам GDB up и down, но пытаются перемещаться по кадрам CPython, а не по кадрам C.
GDB не всегда может прочитать нужную информацию о кадре: это зависит от уровня оптимизации, с которым был собран CPython. Внутри команды ищут кадры C, в которых выполняется стандартная функция вычисления кадра (то есть основной цикл интерпретатора байт-кода CPython), и получают значение соответствующего PyFrameObject *.
Команды выводят номер кадра (на уровне C) в текущем потоке.
Например:
(gdb) py-up
#37 Frame 0x9420b04, for file /usr/lib/python2.6/site-packages/
gnome_sudoku/main.py, line 906, in start_game ()
u = UI()
(gdb) py-up
#40 Frame 0x948e82c, for file /usr/lib/python2.6/site-packages/
gnome_sudoku/gnome_sudoku.py, line 22, in start_game(main=<module at remote 0xb771b7f4>)
main.start_game()
(gdb) py-up
Unable to find an older python frame
значит, мы находимся в верхней части стека Python.
Номера кадров соответствуют номерам, отображаемым стандартной командой GDB backtrace. Команда пропускает кадры C, в которых не выполняется код Python.
Переместимся вниз по стеку:
(gdb) py-down
#37 Frame 0x9420b04, for file /usr/lib/python2.6/site-packages/gnome_sudoku/main.py, line 906, in start_game ()
u = UI()
(gdb) py-down
#34 (unable to read python frame information)
(gdb) py-down
#23 (unable to read python frame information)
(gdb) py-down
#19 (unable to read python frame information)
(gdb) py-down
#14 Frame 0x99262ac, for file /usr/lib/python2.6/site-packages/gnome_sudoku/game_selector.py, line 201, in run_swallowed_dialog (self=<NewOrSavedGameSelector(new_game_model=<gtk.ListStore at remote 0x98fab44>, puzzle=None, saved_games=[{'gsd.auto_fills': 0, 'tracking': {}, 'trackers': {}, 'notes': [], 'saved_at': 1270084485, 'game': '7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 0 0 0 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5\n7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 1 8 3 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5', 'gsd.impossible_hints': 0, 'timer.__absolute_start_time__': <float at remote 0x984b474>, 'gsd.hints': 0, 'timer.active_time': <float at remote 0x984b494>, 'timer.total_time': <float at remote 0x984b464>}], dialog=<gtk.Dialog at remote 0x98faaa4>, saved_game_model=<gtk.ListStore at remote 0x98fad24>, sudoku_maker=<SudokuMaker(terminated=False, played=[], batch_siz...(truncated)
swallower.run_dialog(self.dialog)
(gdb) py-down
#11 Frame 0x9aead74, for file /usr/lib/python2.6/site-packages/gnome_sudoku/dialog_swallower.py, line 48, in run_dialog (self=<SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, main_page=0) at remote 0x98fa6e4>, d=<gtk.Dialog at remote 0x98faaa4>)
gtk.main()
(gdb) py-down
#8 (unable to read python frame information)
(gdb) py-down
Unable to find a newer python frame
теперь мы в нижней части стека Python.
Обратите внимание: в Python 3.12 и более новых версиях один и тот же кадр стека C может использоваться для нескольких кадров стека Python. Поэтому команды py-up и py-down могут переместиться сразу через несколько кадров Python. Например:
(gdb) py-up #6 Frame 0x7ffff7fb62b0, for file /tmp/rec.py, line 5, in recursive_function (n=0) time.sleep(5) #6 Frame 0x7ffff7fb6240, for file /tmp/rec.py, line 7, in recursive_function (n=1) recursive_function(n-1) #6 Frame 0x7ffff7fb61d0, for file /tmp/rec.py, line 7, in recursive_function (n=2) recursive_function(n-1) #6 Frame 0x7ffff7fb6160, for file /tmp/rec.py, line 7, in recursive_function (n=3) recursive_function(n-1) #6 Frame 0x7ffff7fb60f0, for file /tmp/rec.py, line 7, in recursive_function (n=4) recursive_function(n-1) #6 Frame 0x7ffff7fb6080, for file /tmp/rec.py, line 7, in recursive_function (n=5) recursive_function(n-1) #6 Frame 0x7ffff7fb6020, for file /tmp/rec.py, line 9, in <module> () recursive_function(5) (gdb) py-up Unable to find an older python frame
py-bt
Команда py-bt пытается вывести трассировку стека на уровне Python для текущего потока.
Например:
(gdb) py-bt
#8 (unable to read python frame information)
#11 Frame 0x9aead74, for file /usr/lib/python2.6/site-packages/gnome_sudoku/dialog_swallower.py, line 48, in run_dialog (self=<SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, main_page=0) at remote 0x98fa6e4>, d=<gtk.Dialog at remote 0x98faaa4>)
gtk.main()
#14 Frame 0x99262ac, for file /usr/lib/python2.6/site-packages/gnome_sudoku/game_selector.py, line 201, in run_swallowed_dialog (self=<NewOrSavedGameSelector(new_game_model=<gtk.ListStore at remote 0x98fab44>, puzzle=None, saved_games=[{'gsd.auto_fills': 0, 'tracking': {}, 'trackers': {}, 'notes': [], 'saved_at': 1270084485, 'game': '7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 0 0 0 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5\n7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 1 8 3 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5', 'gsd.impossible_hints': 0, 'timer.__absolute_start_time__': <float at remote 0x984b474>, 'gsd.hints': 0, 'timer.active_time': <float at remote 0x984b494>, 'timer.total_time': <float at remote 0x984b464>}], dialog=<gtk.Dialog at remote 0x98faaa4>, saved_game_model=<gtk.ListStore at remote 0x98fad24>, sudoku_maker=<SudokuMaker(terminated=False, played=[], batch_siz...(truncated)
swallower.run_dialog(self.dialog)
#19 (unable to read python frame information)
#23 (unable to read python frame information)
#34 (unable to read python frame information)
#37 Frame 0x9420b04, for file /usr/lib/python2.6/site-packages/gnome_sudoku/main.py, line 906, in start_game ()
u = UI()
#40 Frame 0x948e82c, for file /usr/lib/python2.6/site-packages/gnome_sudoku/gnome_sudoku.py, line 22, in start_game (main=<module at remote 0xb771b7f4>)
main.start_game()
Номера кадров соответствуют номерам, отображаемым стандартной командой GDB backtrace.
py-print
Команда py-print ищет имя в Python и пытается вывести его значение. Сначала она ищет имя среди локальных переменных текущего потока, затем среди глобальных, а затем среди встроенных:
(gdb) py-print self local 'self' = <SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, main_page=0) at remote 0x98fa6e4> (gdb) py-print __name__ global '__name__' = 'gnome_sudoku.dialog_swallower' (gdb) py-print len builtin 'len' = <built-in function len> (gdb) py-print scarlet_pimpernel 'scarlet_pimpernel' not found
Если текущий кадр C соответствует нескольким кадрам Python, команда py-print рассматривает только первый из них.
py-locals
Команда py-locals ищет все локальные переменные Python в текущем кадре Python выбранного потока и выводит их представления:
(gdb) py-locals self = <SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, main_page=0) at remote 0x98fa6e4> d = <gtk.Dialog at remote 0x98faaa4>
Если текущий кадр C соответствует нескольким кадрам Python, будут показаны локальные переменные из всех этих кадров:
(gdb) py-locals Locals for recursive_function n = 0 Locals for recursive_function n = 1 Locals for recursive_function n = 2 Locals for recursive_function n = 3 Locals for recursive_function n = 4 Locals for recursive_function n = 5 Locals for <module>
Использование команд GDB
Команды расширения дополняют встроенные команды GDB. Например, номера кадров, показанные командой py-bt, можно использовать вместе с командой frame, чтобы перейти к определённому кадру в выбранном потоке:
(gdb) py-bt
(output snipped)
#68 Frame 0xaa4560, for file Lib/test/regrtest.py, line 1548, in <module> ()
main()
(gdb) frame 68
#68 0x00000000004cd1e6 in PyEval_EvalFrameEx (f=Frame 0xaa4560, for file Lib/test/regrtest.py, line 1548, in <module> (), throwflag=0) at Python/ceval.c:2665
2665 x = call_function(&sp, oparg);
(gdb) py-list
1543 # Run the tests in a context manager that temporary changes the CWD to a
1544 # temporary and writable directory. If it's not possible to create or
1545 # change the CWD, the original CWD will be used. The original CWD is
1546 # available from test_support.SAVEDCWD.
1547 with test_support.temp_cwd(TESTCWD, quiet=True):
>1548 main()
Команда info threads выводит список потоков процесса, а с помощью команды thread можно выбрать другой поток:
(gdb) info threads 105 Thread 0x7fffefa18710 (LWP 10260) sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86 104 Thread 0x7fffdf5fe710 (LWP 10259) sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86 * 1 Thread 0x7ffff7fe2700 (LWP 10145) 0x00000038e46d73e3 in select () at ../sysdeps/unix/syscall-template.S:82
Чтобы выполнить команду во всех потоках, используйте thread apply all COMMAND или (сокращённо t a a COMMAND). Вместе с py-bt это позволяет увидеть, что делает каждый поток на уровне Python:
(gdb) t a a py-bt
Thread 105 (Thread 0x7fffefa18710 (LWP 10260)):
#5 Frame 0x7fffd00019d0, for file /home/david/coding/python-svn/Lib/threading.py, line 155, in _acquire_restore (self=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) at remote 0xd7ff40>, count_owner=(1, 140737213728528), count=1, owner=140737213728528)
self.__block.acquire()
#8 Frame 0x7fffac001640, for file /home/david/coding/python-svn/Lib/threading.py, line 269, in wait (self=<_Condition(_Condition__lock=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) at remote 0xd7ff40>, acquire=<instancemethod at remote 0xd80260>, _is_owned=<instancemethod at remote 0xd80160>, _release_save=<instancemethod at remote 0xd803e0>, release=<instancemethod at remote 0xd802e0>, _acquire_restore=<instancemethod at remote 0xd7ee60>, _Verbose__verbose=False, _Condition__waiters=[]) at remote 0xd7fd10>, timeout=None, waiter=<thread.lock at remote 0x858a90>, saved_state=(1, 140737213728528))
self._acquire_restore(saved_state)
#12 Frame 0x7fffb8001a10, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 348, in f ()
cond.wait()
#16 Frame 0x7fffb8001c40, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 37, in task (tid=140737213728528)
f()
Thread 104 (Thread 0x7fffdf5fe710 (LWP 10259)):
#5 Frame 0x7fffe4001580, for file /home/david/coding/python-svn/Lib/threading.py, line 155, in _acquire_restore (self=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) at remote 0xd7ff40>, count_owner=(1, 140736940992272), count=1, owner=140736940992272)
self.__block.acquire()
#8 Frame 0x7fffc8002090, for file /home/david/coding/python-svn/Lib/threading.py, line 269, in wait (self=<_Condition(_Condition__lock=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) at remote 0xd7ff40>, acquire=<instancemethod at remote 0xd80260>, _is_owned=<instancemethod at remote 0xd80160>, _release_save=<instancemethod at remote 0xd803e0>, release=<instancemethod at remote 0xd802e0>, _acquire_restore=<instancemethod at remote 0xd7ee60>, _Verbose__verbose=False, _Condition__waiters=[]) at remote 0xd7fd10>, timeout=None, waiter=<thread.lock at remote 0x858860>, saved_state=(1, 140736940992272))
self._acquire_restore(saved_state)
#12 Frame 0x7fffac001c90, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 348, in f ()
cond.wait()
#16 Frame 0x7fffac0011c0, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 37, in task (tid=140736940992272)
f()
Thread 1 (Thread 0x7ffff7fe2700 (LWP 10145)):
#5 Frame 0xcb5380, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 16, in _wait ()
time.sleep(0.01)
#8 Frame 0x7fffd00024a0, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 378, in _check_notify (self=<ConditionTests(_testMethodName='test_notify', _resultForDoCleanups=<TestResult(_original_stdout=<cStringIO.StringO at remote 0xc191e0>, skipped=[], _mirrorOutput=False, testsRun=39, buffer=False, _original_stderr=<file at remote 0x7ffff7fc6340>, _stdout_buffer=<cStringIO.StringO at remote 0xc9c7f8>, _stderr_buffer=<cStringIO.StringO at remote 0xc9c790>, _moduleSetUpFailed=False, expectedFailures=[], errors=[], _previousTestClass=<type at remote 0x928310>, unexpectedSuccesses=[], failures=[], shouldStop=False, failfast=False) at remote 0xc185a0>, _threads=(0,), _cleanups=[], _type_equality_funcs={<type at remote 0x7eba00>: <instancemethod at remote 0xd750e0>, <type at remote 0x7e7820>: <instancemethod at remote 0xd75160>, <type at remote 0x7e30e0>: <instancemethod at remote 0xd75060>, <type at remote 0x7e7d20>: <instancemethod at remote 0xd751e0>, <type at remote 0x7f19e0...(truncated)
_wait()
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/gdb_helpers.html