Содержание страницы
Отладка расширений C API и внутренностей CPython с помощью GDB¶Debugging C API extensions and CPython Internals with GDB
Этот документ описывает, как расширение GDB для Python, python-gdb.py, может
использоваться с отладчиком GDB для отладки расширений CPython и
самого интерпретатора CPython.
При отладке низкоуровневых проблем, таких как крахи или взаимоблокировки, низкоуровневый отладчик, например GDB, полезен для диагностики и исправления проблемы. По умолчанию GDB (или любой его фронтенд) не поддерживает высокоуровневую информацию, специфичную для интерпретатора CPython.
Расширение python-gdb.py добавляет информацию об интерпретаторе CPython в GDB.
Расширение помогает интроспектировать стек текущих выполняемых функций Python.
При наличии объекта Python, представленного указателем PyObject*,
расширение показывает тип и значение объекта.
Разработчики, работающие над расширениями CPython или экспериментирующие с частями
CPython, написанными на C, могут использовать этот документ, чтобы узнать, как использовать
расширение python-gdb.py с GDB.
Примечание
Этот документ предполагает, что вы знакомы с основами GDB и CPython C API. В нем собраны рекомендации из devguide и Python wiki.
Предварительные требования¶Prerequisites
Вам потребуется:
GDB 7 или новее. (Для более ранних версий GDB см.
Misc/gdbinitв исходных кодах Python 3.11 или более ранних.)Отладочная информация, совместимая с GDB, для Python и любых расширений, которые вы отлаживаете.
Расширение
python-gdb.py.
Расширение собирается вместе с Python, но может распространяться отдельно или не распространяться вовсе. Ниже приведены советы для нескольких распространенных систем в качестве примеров. Обратите внимание: даже если инструкции соответствуют вашей системе, они могут быть устаревшими.
Настройка для сборки Python из исходного кода¶Setup with Python built from source
При сборке 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¶Setup for Python from a Linux distro
Большинство систем 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; обычно требуется установить пакет с отладочной информацией отдельно.
Использование отладочной сборки и режима разработки¶Using the Debug build and Development mode
Для упрощения отладки может быть полезно:
Используйте отладочную сборку Python. (При сборке из исходного кода используйте
configure --with-pydebug. В дистрибутивах Linux установите и запустите пакет наподобиеpython-debugилиpython-dbg, если они доступны.)Используйте режим разработки во время выполнения (
-X dev).
Оба включают дополнительные проверки и отключают некоторые оптимизации. Иногда это скрывает искомую ошибку, но в большинстве случаев они упрощают процесс.
Использование расширения python-gdb¶Using the python-gdb extension
После загрузки расширения оно предоставляет две основные возможности: форматировщики вывода (pretty printers) для значений Python и дополнительные команды.
Форматировщики вывода (pretty printers)¶Pretty-printers
Вот как выглядит обратная трассировка 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}}}
Обратите внимание: pretty printers не вызывают repr().
Для базовых типов они стараются точно воспроизвести его результат.
Область, которая может сбивать с толку: пользовательский форматировщик для некоторых типов очень похож на встроенный форматировщик GDB для стандартных типов. Например,
pretty-printer для 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__'
Pretty-printer для экземпляров 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 and py-down
Команды
py-upиpy-downаналогичны обычным командам GDBupи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 и пытается его вывести. Поиск производится сначала в локальных переменных текущего потока, затем в глобальных, и наконец во встроенных (builtins):(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¶Use with GDB commands
Команды расширения дополняют встроенные команды 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()