C-API-Erweiterungen und CPython-Interna mit GDB debuggen
********************************************************

Dieses Dokument erklärt, wie sich die Python-GDB-Erweiterung "python-
gdb.py" zusammen mit dem Debugger GDB nutzen lässt, um CPython-
Erweiterungen und den CPython-Interpreter selbst zu debuggen.

Bei der Fehlersuche nach tiefliegenden Problemen wie Abstürzen oder
Verklemmungen hilft ein Debugger auf niedriger Ebene wie GDB, die
Ursache zu finden und zu beheben. Von sich aus unterstützt GDB (oder
eines seiner Frontends) jedoch keine höheren Informationen, die
speziell den CPython-Interpreter betreffen.

Die Erweiterung "python-gdb.py" ergänzt GDB um Informationen über den
CPython-Interpreter. Sie hilft dabei, den Stapel der gerade
ausgeführten Python-Funktionen zu untersuchen. Zu einem Python-Objekt,
das durch einen Zeiger PyObject* dargestellt wird, zeigt die
Erweiterung Typ und Wert des Objekts an.

Wer an CPython-Erweiterungen arbeitet oder an den in C geschriebenen
Teilen von CPython herumbastelt, kann anhand dieses Dokuments lernen,
wie sich die Erweiterung "python-gdb.py" mit GDB einsetzen lässt.

Bemerkung:

  Dieses Dokument setzt voraus, dass du mit den Grundlagen von GDB und
  dem CPython-C-API vertraut bist. Es bündelt die Hinweise aus dem
  devguide  und dem Python-Wiki.


Voraussetzungen
===============

Du brauchst:

* GDB 7 oder neuer. (Für ältere GDB-Versionen siehe "Misc/gdbinit" in
  den Quellen von Python 3.11 oder älter.)

* GDB-kompatible Debug-Informationen für Python und für jede
  Erweiterung, die du debuggst.

* Die Erweiterung "python-gdb.py".

Die Erweiterung wird mit Python gebaut, kann aber separat oder gar
nicht ausgeliefert werden. Weiter unten findest du beispielhaft
Hinweise für einige verbreitete Systeme. Beachte, dass die Anleitungen
auch dann veraltet sein können, wenn sie zu deinem System passen.


Einrichtung bei aus den Quellen gebautem Python
-----------------------------------------------

Wenn du CPython aus den Quellen baust, sollten Debug-Informationen
vorhanden sein, und der Build sollte eine Datei "python-gdb.py" im
Wurzelverzeichnis deines Repositorys ablegen.

Damit die Unterstützung aktiv wird, musst du das Verzeichnis mit
"python-gdb.py" in GDBs „auto-load-safe-path“ aufnehmen. Hast du das
noch nicht getan, gibt neuere GDB-Versionen eine Warnung mit einer
Anleitung dazu aus.

Bemerkung:

  Siehst du keine Anleitung für deine GDB-Version, dann schreibe
  Folgendes in deine Konfigurationsdatei ("~/.gdbinit" oder
  "~/.config/gdb/gdbinit"):

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

  Du kannst auch mehrere Pfade angeben, getrennt durch ":".


Einrichtung bei Python aus einer Linux-Distribution
---------------------------------------------------

Die meisten Linux-Systeme stellen Debug-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 Debug-Symbole automatisch
über *debuginfod* herunterladen. Damit wird allerdings nicht die
Erweiterung "python-gdb.py" installiert. Das Debug-Info-Paket musst du
in der Regel trotzdem separat installieren.


Debug-Build und Entwicklungsmodus nutzen
========================================

Um die Fehlersuche zu erleichtern, kannst du Folgendes tun:

* Verwende einen Debug-Build von Python. (Beim Bauen aus den Quellen
  nimm "configure --with-pydebug". Auf Linux-Distributionen
  installiere und starte ein Paket wie "python-debug" oder "python-
  dbg", sofern verfügbar.)

* Verwende den Entwicklungsmodus zur Laufzeit ("-X dev").

Beides schaltet zusätzliche Zusicherungen ein und einige Optimierungen
aus. Manchmal verdeckt das genau den Fehler, den du suchst, in den
meisten Fällen erleichtert es aber die Arbeit.


Die Erweiterung "python-gdb" verwenden
======================================

Ist die Erweiterung geladen, bietet sie zwei wesentliche Dinge:
Pretty-Printing für Python-Werte und zusätzliche Befehle.


Lesbare Ausgabe mit Pretty-Printern
-----------------------------------

So sieht ein GDB-Backtrace (gekürzt) aus, wenn diese Erweiterung aktiv
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}, '__name__': '__main__', 'z': <Yuck(i=0) at remote 0xaace60>, '__doc__': None}, key=
       0x5c2b8d "__lltrace__") at Objects/dictobject.c:2171

Beachte, wie das Dictionary-Argument von "PyDict_GetItemString" als
sein "repr()" angezeigt wird und nicht als undurchsichtiger Zeiger
"PyObject *".

Die Erweiterung arbeitet, indem sie eine eigene Ausgaberoutine für
Werte des Typs "PyObject *" bereitstellt.  Brauchst du Zugriff auf
tiefer liegende Details eines Objekts, dann wandle den Wert in einen
Zeiger des passenden 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}}}

Beachte, dass die Pretty-Printings nicht wirklich "repr()" aufrufen.
Bei einfachen Typen versuchen sie, dessen Ergebnis möglichst genau
nachzubilden.

Verwirrend kann sein, dass die eigene Ausgabe für manche Typen der
eingebauten Ausgabe von GDB für Standardtypen sehr ähnlich sieht.  Die
Pretty-Printing für ein Python-"int" (PyLongObject*) liefert zum
Beispiel eine Darstellung, die sich nicht von der einer gewöhnlichen
maschinennahen Ganzzahl unterscheiden lässt:

   (gdb) p some_machine_integer
   $3 = 42

   (gdb) p some_python_integer
   $4 = 42

Die interne Struktur lässt sich durch eine Umwandlung in PyLongObject*
sichtbar machen:

   (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
der eingebauten GDB-Ausgabe 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 (so wie Pythons "repr" für Zeichenketten),
während die Standardausgabe 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 lassen sich die Interna durch eine Umwandlung in
PyUnicodeObject* sichtbar machen:

   (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 ergänzt einen Befehl "py-list", der den Python-
   Quellcode (sofern vorhanden) für den aktuellen Rahmen im
   ausgewählten Thread auflistet.  Die aktuelle Zeile ist mit „>“
   markiert:

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

   Mit "py-list START" listest du ab einer anderen Zeilennummer
   innerhalb des Python-Quellcodes auf, mit "py-list START,END" einen
   bestimmten Zeilenbereich innerhalb des Python-Quellcodes.


"py-up" and "py-down"
---------------------

   Die Befehle "py-up" und "py-down" entsprechen den gewöhnlichen GDB-
   Befehlen "up" und "down", versuchen aber, sich auf Ebene der
   CPython-Rahmen zu bewegen statt auf Ebene der C-Rahmen.

   GDB kann die zugehörigen Rahmeninformationen nicht immer lesen. Das
   hängt davon ab, mit welcher Optimierungsstufe CPython übersetzt
   wurde. Intern suchen die Befehle nach C-Rahmen, die die Standard-
   Rahmenauswertungsfunktion ausführen (also die zentrale Bytecode-
   Interpreterschleife in CPython), und lesen den Wert des zugehörigen
   "PyFrameObject *" aus.

   Sie geben die Rahmennummer (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

   also sind wir an der Spitze des Python-Stapels.

   Die Rahmennummern entsprechen denen, die GDBs Standardbefehl
   "backtrace" anzeigt. Der Befehl überspringt C-Rahmen, die keinen
   Python-Code ausführen.

   Und zurück nach unten:

      (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 Fuß des Python-Stapels.

   Beachte, dass in Python 3.12 und neuer derselbe C-Stapelrahmen für
   mehrere Python-Stapelrahmen genutzt werden kann. Das bedeutet, dass
   "py-up" und "py-down" mehrere Python-Rahmen auf einmal überspringen
   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 Backtrace des aktuellen Threads
   auf Python-Ebene 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 Rahmennummern entsprechen denen, die GDBs Standardbefehl
   "backtrace" anzeigt.


"py-print"
----------

   Der Befehl "py-print" sucht einen Python-Namen und versucht, ihn
   auszugeben. Er schaut zuerst in den lokalen Namen des aktuellen
   Threads, dann in den globalen und schließlich in den eingebauten:

      (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

   Entspricht der aktuelle C-Rahmen mehreren Python-Rahmen,
   berücksichtigt "py-print" nur den ersten davon.


"py-locals"
-----------

   Der Befehl "py-locals" sucht alle lokalen Python-Namen innerhalb
   des aktuellen Python-Rahmens im ausgewählten Thread und gibt ihre
   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>

   Entspricht der aktuelle C-Rahmen mehreren Python-Rahmen, werden die
   lokalen Namen 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>


Zusammen mit GDB-Befehlen verwenden
===================================

Die Befehle der Erweiterung ergänzen die eingebauten Befehle von GDB.
Du kannst zum Beispiel eine der von "py-bt" angezeigten Rahmennummern
mit dem Befehl "frame" verwenden, um zu einem bestimmten Rahmen
innerhalb des ausgewählten Threads zu springen, etwa so:

   (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" liefert dir eine Liste der Threads innerhalb
des Prozesses, und mit dem Befehl "thread" wählst du einen anderen
aus:

   (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

Mit "thread apply all COMMAND" oder kurz "t a a COMMAND" führst du
einen Befehl auf allen Threads aus.  Zusammen mit "py-bt" siehst du
damit, was jeder Thread auf Python-Ebene gerade 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()
