Debugging von C API-Erweiterungen und CPython-Interna mit GDB

Dieses Dokument erklärt, wie die Python GDB-Erweiterung python-gdb.py mit dem GDB-Debugger verwendet werden kann, um CPython-Erweiterungen und den CPython-Interpreter selbst zu debuggen.

Beim Debuggen von Low-Level-Problemen wie Abstürzen oder Deadlocks ist ein Low-Level-Debugger wie GDB nützlich, um das Problem zu diagnostizieren und zu beheben. Standardmäßig unterstützt GDB (oder eines seiner Frontends) keine High-Level-Informationen, die spezifisch für den CPython-Interpreter sind.

Die Erweiterung python-gdb.py fügt CPython-Interpreterinformationen zu GDB hinzu. Die Erweiterung hilft bei der Introspektion des Stacks der aktuell ausführenden Python-Funktionen. Bei einem Python-Objekt, das durch einen PyObject*-Zeiger repräsentiert wird, zeigt die Erweiterung den Typ und den Wert des Objekts an.

Entwickler, die an CPython-Erweiterungen arbeiten oder Teile von CPython, die in C geschrieben sind, modifizieren, können dieses Dokument verwenden, um zu lernen, wie die python-gdb.py Erweiterung mit GDB zu verwenden ist.

Hinweis

Dieses Dokument setzt voraus, dass Sie mit den Grundlagen von GDB und der CPython C API vertraut sind. Es fasst Anleitungen aus dem devguide und dem Python wiki zusammen.

Voraussetzungen

Sie benötigen

  • GDB 7 oder neuer. (Für frühere Versionen von GDB siehe Misc/gdbinit in den Quellen von Python 3.11 oder früher.)

  • GDB-kompatible Debugging-Informationen für Python und jede Erweiterung, die Sie debuggen.

  • Die python-gdb.py Erweiterung.

Die Erweiterung wird mit Python erstellt, könnte aber separat oder gar nicht verteilt werden. Unten fügen wir Tipps für einige gängige Systeme als Beispiele ein. Beachten Sie, dass die Anleitungen veraltet sein könnten, auch wenn sie mit Ihrem System übereinstimmen.

Einrichtung mit aus Quellen kompiliertem Python

Wenn Sie CPython aus Quellen kompilieren, sollten Debugging-Informationen verfügbar sein, und der Build sollte eine Datei python-gdb.py im Stammverzeichnis Ihres Repositorys hinzufügen.

Um die Unterstützung zu aktivieren, müssen Sie das Verzeichnis, das python-gdb.py enthält, zu GDBs "auto-load-safe-path" hinzufügen. Wenn Sie dies noch nicht getan haben, geben neuere GDB-Versionen eine Warnung mit Anweisungen aus, wie dies zu tun ist.

Hinweis

Wenn Sie keine Anweisungen für Ihre GDB-Version sehen, fügen Sie dies in Ihre Konfigurationsdatei ein (~/.gdbinit oder ~/.config/gdb/gdbinit)

add-auto-load-safe-path /path/to/cpython

Sie können auch mehrere Pfade hinzufügen, getrennt durch :.

Einrichtung für Python aus einer Linux-Distribution

Die meisten Linux-Systeme stellen Debugging-Informationen für das System-Python in einem Paket namens python-debuginfo, python-dbg oder ähnlich bereit. Zum Beispiel

  • Fedora

    sudo dnf install gdb
    sudo dnf debuginfo-install python3
    
  • Ubuntu

    sudo apt install gdb python3-dbg
    

Auf mehreren neueren Linux-Systemen kann GDB Debugging-Symbole automatisch mit debuginfod herunterladen. Dies installiert jedoch nicht die python-gdb.py Erweiterung; Sie müssen das Debug-Info-Paket im Allgemeinen separat installieren.

Verwendung des Debug-Builds und des Entwicklungsmodus

Für einfacheres Debugging möchten Sie vielleicht

  • Einen Debug-Build von Python verwenden. (Beim Kompilieren aus Quellen verwenden Sie configure --with-pydebug. Auf Linux-Distributionen installieren und führen Sie ein Paket wie python-debug oder python-dbg aus, falls verfügbar.)

  • Den Laufzeit-Entwicklungsmodus (-X dev) verwenden.

Beides aktiviert zusätzliche Assertionen und deaktiviert einige Optimierungen. Manchmal verbirgt dies den Bug, den Sie zu finden versuchen, aber in den meisten Fällen erleichtern sie den Prozess.

Verwendung der python-gdb Erweiterung

Wenn die Erweiterung geladen ist, bietet sie zwei Hauptfunktionen: Pretty-Printers für Python-Werte und zusätzliche Befehle.

Pretty-Printers

So sieht ein GDB-Backtrace aus (abgeschnitten), wenn diese Erweiterung aktiviert ist

#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

Beachten Sie, wie das Wörterbuchargument von PyDict_GetItemString als sein repr() angezeigt wird, anstatt als undurchsichtiger PyObject *-Zeiger.

Die Erweiterung funktioniert, indem sie eine benutzerdefinierte Druckroutine für Werte vom Typ PyObject * bereitstellt. Wenn Sie auf Low-Level-Details eines Objekts zugreifen müssen, wandeln Sie den Wert in einen Zeiger des entsprechenden Typs um. Zum Beispiel

(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}}}

Beachten Sie, dass die Pretty-Printers nicht tatsächlich repr() aufrufen. Für grundlegende Typen versuchen sie, dessen Ergebnis eng abzugleichen.

Ein Bereich, der verwirrend sein kann, ist, dass der benutzerdefinierte Printer für einige Typen GDBs eingebautem Printer für Standardtypen sehr ähnlich sieht. Zum Beispiel gibt der Pretty-Printer für einen Python int (PyLongObject*) eine Darstellung, die von der eines regulären maschinen-level Integers nicht zu unterscheiden ist

(gdb) p some_machine_integer
$3 = 42

(gdb) p some_python_integer
$4 = 42

Die interne Struktur kann durch Umwandlung in PyLongObject* enthüllt werden

(gdb) p *(PyLongObject*)some_python_integer
$5 = {ob_base = {ob_base = {ob_refcnt = 8, ob_type = 0x3dad39f5e0}, ob_size = 1},
ob_digit = {42}}

Eine ähnliche Verwirrung kann beim Typ str entstehen, wo die Ausgabe GDBs eingebautem Printer für char * sehr ähnlich sieht

(gdb) p ptr_to_python_str
$6 = '__builtins__'

Der Pretty-Printer für str-Instanzen verwendet standardmäßig einfache Anführungszeichen (wie Pythons repr für Strings), während der Standard-Printer für char *-Werte doppelte Anführungszeichen verwendet und eine hexadezimale Adresse enthält

(gdb) p ptr_to_char_star
$7 = 0x6d72c0 "hello world"

Auch hier können die Implementierungsdetails durch Umwandlung in PyUnicodeObject* enthüllt werden

(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

Die Erweiterung fügt einen Befehl py-list hinzu, der den Python-Quellcode (falls vorhanden) für den aktuellen Frame im ausgewählten Thread auflistet. Die aktuelle Zeile ist mit einem ">" gekennzeichnet

(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...

Verwenden Sie py-list START, um an einer anderen Zeilennummer im Python-Quellcode aufzulisten, und py-list START,END, um einen bestimmten Zeilenbereich im Python-Quellcode aufzulisten.

py-up und py-down

Die Befehle py-up und py-down sind analog zu GDBs regulären Befehlen up und down, versuchen aber, sich auf der Ebene von CPython-Frames anstatt C-Frames zu bewegen.

GDB kann die relevanten Frame-Informationen je nach Optimierungsstufe, mit der CPython kompiliert wurde, nicht immer lesen. Intern suchen die Befehle nach C-Frames, die die Standard-Frame-Auswertungsfunktion ausführen (d. h. die Kern-Bytecode-Interpreter-Schleife innerhalb von CPython) und suchen den Wert des zugehörigen PyFrameObject *.

Sie geben die Frame-Nummer (auf C-Ebene) innerhalb des Threads aus.

Zum Beispiel

(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

wir sind also am Anfang des Python-Stacks.

Die Frame-Nummern entsprechen denen, die von GDBs Standardbefehl backtrace angezeigt werden. Der Befehl überspringt C-Frames, die keinen Python-Code ausführen.

Zurück nach unten gehen

(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

und wir sind am Ende des Python-Stacks.

Beachten Sie, dass in Python 3.12 und neuer derselbe C-Stack-Frame für mehrere Python-Stack-Frames verwendet werden kann. Das bedeutet, dass py-up und py-down mehrere Python-Frames auf einmal bewegen können. Zum Beispiel

(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

Der Befehl py-bt versucht, einen Python-Level-Backtrace des aktuellen Threads anzuzeigen.

Zum Beispiel

(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()

Die Frame-Nummern entsprechen denen, die von GDBs Standardbefehl backtrace angezeigt werden.

py-print

Der Befehl py-print sucht nach einem Python-Namen und versucht, ihn auszugeben. Er sucht in den Locals innerhalb des aktuellen Threads, dann in den Globals und schließlich in den 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

Wenn der aktuelle C-Frame mehreren Python-Frames entspricht, berücksichtigt py-print nur den ersten.

py-locals

Der Befehl py-locals sucht alle Python-Locals innerhalb des aktuellen Python-Frames im ausgewählten Thread und gibt deren Darstellungen aus.

(gdb) py-locals
self = <SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>,
main_page=0) at remote 0x98fa6e4>
d = <gtk.Dialog at remote 0x98faaa4>

Wenn der aktuelle C-Frame mehreren Python-Frames entspricht, werden die Locals aus allen angezeigt.

(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>

Verwendung mit GDB-Befehlen

Die Erweiterungsbefehle ergänzen GDBs eingebaute Befehle. Sie können beispielsweise Frame-Nummern, die von py-bt angezeigt werden, mit dem Befehl frame verwenden, um zu einem bestimmten Frame innerhalb des ausgewählten Threads zu wechseln, wie folgt

(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()

Der Befehl info threads gibt Ihnen eine Liste der Threads innerhalb des Prozesses, und Sie können den Befehl thread verwenden, um einen anderen auszuwählen.

(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

Sie können thread apply all COMMAND oder (t a a COMMAND kurz) verwenden, um einen Befehl auf allen Threads auszuführen. Mit py-bt können Sie so sehen, was jeder Thread auf Python-Ebene tut.

(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()