اشکال‌زدایی افزونه‌های C API و درونی‌های CPython با GDB

این سند توضیح می‌دهد که چگونه می‌توان از افزونه GDB پایتون، python-gdb.py، به همراه اشکال‌زدای GDB برای اشکال‌زدایی افزونه‌های CPython و خود مفسر CPython استفاده کرد.

هنگام اشکال‌زدایی از مشکلات سطح پایین مانند فروپاشی‌ها یا بن‌بست‌ها، یک اشکال‌زدای سطح پایین مانند GDB برای تشخیص و اصلاح مشکل مفید است. به‌طور پیش‌فرض، GDB (یا هر یک از پیشانه‌های آن) از اطلاعات سطح بالایی که مختص مفسر CPython است پشتیبانی نمی‌کند.

افزونه python-gdb.py اطلاعات مفسر CPython را به GDB اضافه می‌کند. این افزونه به درون‌نگری پشته توابع پایتونی که در حال حاضر اجرا می‌شوند، کمک می‌کند. با داشتن یک شیء پایتون که با یک اشاره‌گر PyObject* نمایش داده می‌شود، این افزونه نوع و مقدار آن شیء را نمایان می‌کند.

توسعه‌دهندگانی که روی افزونه‌های CPython کار می‌کنند یا بخش‌هایی از CPython را که به زبان C نوشته شده‌اند دستکاری می‌کنند، می‌توانند از این سند برای یادگیری نحوه استفاده از افزونه python-gdb.py در GDB استفاده کنند.

توجه

این سند فرض می‌کند که شما با مبانی GDB و CPython C API آشنا هستید. این سند راهنمایی‌هایی را از devguide و ویکی پایتون گردآوری می‌کند.

پیش‌نیازها

شما باید داشته باشید:

  • GDB 7 یا جدیدتر. (برای نسخه‌های قدیمی‌تر GDB، به Misc/gdbinit در کد منبع پایتون 3.11 یا قدیمی‌تر مراجعه کنید.)

  • اطلاعات اشکال‌زدایی سازگار با GDB برای پایتون و هر افزونه‌ای که در حال اشکال‌زدایی آن هستید.

  • افزونه‌ی python-gdb.py.

این افزونه با پایتون ساخته می‌شود، اما ممکن است به‌صورت جداگانه توزیع شود یا اصلاً توزیع نشود. در ادامه، نکاتی برای چند سیستم رایج به‌عنوان مثال آورده‌ایم. توجه داشته باشید که حتی اگر دستورالعمل‌ها با سیستم شما مطابقت داشته باشند، ممکن است قدیمی باشند.

راه‌اندازی با پایتون ساخته‌شده از منبع

هنگامی که 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-debuginfo، python-dbg یا مشابه آن ارائه می‌دهند. برای مثال:

  • فدورا:

    sudo dnf install gdb
    sudo dnf debuginfo-install python3
    
  • اوبونتو:

    sudo apt install gdb python3-dbg
    

در چندین سیستم لینوکسی اخیر، GDB می‌تواند نمادهای اشکال‌زدایی را به‌صورت خودکار با استفاده از debuginfod بارگیری کند. با این حال، این کار افزونه‌ی python-gdb.py را نصب نمی‌کند؛ شما معمولاً باید بسته‌ی اطلاعات اشکال‌زدایی را به‌صورت جداگانه نصب کنید.

استفاده از ساخت اشکال‌زدایی و حالت توسعه

برای اشکال‌زدایی آسان‌تر، ممکن است بخواهید:

  • از یک ساخت اشکال‌زدایی پایتون استفاده کنید. (هنگام ساخت از منبع، از configure --with-pydebug استفاده کنید. در توزیع‌های لینوکس، بسته‌ای مانند python-debug یا python-dbg را در صورت وجود نصب و اجرا کنید.)

  • از حالت توسعه‌ی ران‌تایم (-X dev) استفاده کنید.

هر دو ادعاهای اضافی را فعال می‌کنند و برخی بهینه‌سازی‌ها را غیرفعال می‌کنند. گاهی اوقات این کار، اشکالی را که سعی در یافتن آن دارید پنهان می‌کند، اما در بیشتر موارد فرایند را آسان‌تر می‌کنند.

استفاده از افزونه‌ی python-gdb

هنگامی که افزونه بارگذاری می‌شود، دو قابلیت اصلی ارائه می‌دهد: چاپگرهای زیبا (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) برای int پایتون (PyLongObject*) نمایشی ارائه می‌دهد که از نمایش یک عدد صحیح معمولی در سطح ماشین قابل تشخیص نیست:

(gdb) p some_machine_integer
$3 = 42

(gdb) p some_python_integer
$4 = 42

ساختار داخلی را می‌توان با یک تبدیل نوع (cast) به 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__'

The pretty-printer for str instances defaults to using single-quotes (as does Python's repr for strings) whereas the standard printer for char * values uses double-quotes and contains a hexadecimal address:

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

بار دیگر، جزئیات پیاده‌سازی را می‌توان با یک تبدیل نوع (cast) به 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 را اضافه می‌کند، که کد منبع پایتون (در صورت وجود) برای فریم جاری در نخ انتخاب‌شده را فهرست می‌کند. خط جاری با «>» مشخص می‌شود:

(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 و برای فهرست کردن محدوده مشخصی از سطرها در کد منبع پایتون، از py-list START,END استفاده کنید.

py-up و py-down

دستورهای py-up و py-down مشابه دستورهای معمولی up و down در GDB هستند، اما تلاش می‌کنند در سطح فریم‌های 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

بنابراین، در بالای پشته‌ی پایتون هستیم.

شماره فریم‌ها با شماره‌هایی که توسط فرمان استاندارد backtrace در GDB نمایش داده می‌شوند، مطابقت دارند. این فرمان، فریم‌های C را که در حال اجرای کد پایتون نیستند، نادیده می‌گیرد.

بازگشت به پایین:

(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

و در پایین پشته‌ی پایتون قرار داریم.

توجه داشته باشید که در پایتون 3.12 و نسخه‌های جدیدتر، می‌توان از یک فریم پشته‌ی C برای چندین فریم پشته‌ی پایتون استفاده کرد. این بدان معناست که py-up و py-down ممکن است چندین فریم پایتون را همزمان جابه‌جا کنند. برای مثال:

(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 تلاش می‌کند یک ردگیری پشته در سطح پایتون از نخ جاری را نمایش دهد.

برای مثال:

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

شماره‌های فریم با شماره‌هایی که دستور استاندارد backtrace در GDB نمایش می‌دهد، مطابقت دارند.

py-print

فرمان py-print یک نام پایتون را جستجو می‌کند و سعی می‌کند آن را چاپ کند. این فرمان در متغیرهای محلی در نخ جاری، سپس در متغیرهای سراسری، و در نهایت در توکارها جستجو می‌کند:

(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 فعلی با چندین فریم پایتون متناظر باشد، py-print فقط اولین فریم را در نظر می‌گیرد.

py-locals

دستور py-locals همه‌ی متغیرهای محلی پایتون را در فریم پایتون جاری در نخ انتخاب‌شده جست‌وجو می‌کند و بازنمایی‌های آن‌ها را چاپ می‌کند:

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

اگر فریم C جاری با چندین فریم پایتون متناظر باشد، متغیرهای محلی همه‌ی آن‌ها نمایش داده خواهند شد:

(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، این کار به شما امکان می‌دهد ببینید هر نخ در سطح پایتون چه کاری انجام می‌دهد:

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