Gỡ lỗi tiện ích mở rộng C API và CPython Internals bằng GDB

Tài liệu này giải thích cách có thể sử dụng tiện ích mở rộng GDB của Python, python-gdb.py, với trình gỡ lỗi GDB để gỡ lỗi các tiện ích mở rộng CPython và chính trình thông dịch CPython.

Khi gỡ lỗi các sự cố cấp thấp như sự cố hoặc bế tắc, trình gỡ lỗi cấp thấp, chẳng hạn như GDB, rất hữu ích để chẩn đoán và khắc phục sự cố. Theo mặc định, GDB (hoặc bất kỳ giao diện người dùng nào của nó) không hỗ trợ thông tin cấp cao dành riêng cho trình thông dịch CPython.

Tiện ích mở rộng python-gdb.py thêm thông tin trình thông dịch CPython vào GDB. Tiện ích mở rộng giúp xem xét nội tâm các hàm Python hiện đang thực thi. Với một đối tượng Python được biểu thị bằng con trỏ PyObject*, phần mở rộng sẽ hiển thị loại và giá trị của đối tượng.

Các nhà phát triển đang làm việc trên tiện ích mở rộng CPython hoặc mày mò các phần của CPython được viết bằng C có thể sử dụng tài liệu này để tìm hiểu cách sử dụng tiện ích mở rộng python-gdb.py với GDB.

Ghi chú

Tài liệu này giả định rằng bạn đã quen thuộc với kiến thức cơ bản về GDB và CPython C API. Nó tổng hợp hướng dẫn từ devguidePython wiki.

Điều kiện tiên quyết

Bạn cần phải có:

  • GDB 7 trở lên. (Đối với các phiên bản cũ hơn của GDB, hãy xem Misc/gdbinit trong các nguồn của Python 3.11 trở về trước.)

  • thông tin gỡ lỗi tương thích với GDB cho Python và bất kỳ tiện ích mở rộng nào bạn đang gỡ lỗi.

  • Tiện ích mở rộng python-gdb.py.

Tiện ích mở rộng được xây dựng bằng Python nhưng có thể được phân phối riêng hoặc không được phân phối. Dưới đây, chúng tôi bao gồm các mẹo cho một số hệ thống phổ biến làm ví dụ. Lưu ý rằng ngay cả khi các hướng dẫn phù hợp với hệ thống của bạn thì chúng vẫn có thể lỗi thời.

Thiết lập với Python được xây dựng từ nguồn

Khi bạn xây dựng CPython từ nguồn, thông tin gỡ lỗi sẽ có sẵn và bản dựng sẽ thêm tệp python-gdb.py vào thư mục gốc của kho lưu trữ của bạn.

Để kích hoạt hỗ trợ, bạn phải thêm thư mục chứa python-gdb.py vào "auto-load-safe-path" của GDB. Nếu bạn chưa thực hiện việc này, các phiên bản gần đây của GDB sẽ in ra cảnh báo kèm theo hướng dẫn về cách thực hiện việc này.

Ghi chú

Nếu bạn không thấy hướng dẫn cho phiên bản GDB của mình, hãy đặt phần này vào tệp cấu hình của bạn (~/.gdbinit hoặc ~/.config/gdb/gdbinit):

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

Bạn cũng có thể thêm nhiều đường dẫn, cách nhau bằng :.

Thiết lập cho Python từ bản phân phối Linux

Hầu hết các hệ thống Linux đều cung cấp thông tin gỡ lỗi cho hệ thống Python trong gói có tên python-debuginfo, python-dbg hoặc tương tự. Ví dụ:

  • Fedora:

    sudo dnf cài đặt gdb
    sudo dnf debuginfo-cài đặt python3
    
  • Ubuntu:

    sudo apt cài đặt gdb python3-dbg
    

Trên một số hệ thống Linux gần đây, GDB có thể tự động tải xuống các biểu tượng gỡ lỗi bằng debuginfod. Tuy nhiên, thao tác này sẽ không cài đặt tiện ích mở rộng python-gdb.py; thông thường bạn cần phải cài đặt riêng gói thông tin gỡ lỗi.

Sử dụng chế độ Phát triển và xây dựng gỡ lỗi

Để gỡ lỗi dễ dàng hơn, bạn có thể muốn:

  • Sử dụng debug build của Python. (Khi xây dựng từ nguồn, hãy sử dụng configure --with-pydebug. Trên các bản phân phối Linux, hãy cài đặt và chạy gói như python-debug hoặc python-dbg, nếu có.)

  • Sử dụng thời gian chạy development mode (-X dev).

Cả hai đều cho phép xác nhận bổ sung và vô hiệu hóa một số tối ưu hóa. Đôi khi điều này che giấu lỗi mà bạn đang cố gắng tìm kiếm, nhưng trong hầu hết các trường hợp, chúng giúp quá trình này trở nên dễ dàng hơn.

Sử dụng tiện ích mở rộng python-gdb

Khi tiện ích mở rộng được tải, nó cung cấp hai tính năng chính: máy in đẹp cho các giá trị Python và các lệnh bổ sung.

Máy in đẹp

Đây là giao diện của backtrace GDB (cắt ngắn) khi tiện ích mở rộng này được bật:

#0 0x000000000041a6b1 trong PyObject_Malloc (nbytes=Không thể truy cập bộ nhớ tại địa chỉ 0x7fffff7fefe8
) tại Đối tượng/obmalloc.c:748
#1 0x000000000041b7c0 trong _PyObject_DebugMallocApi (id=111 'o', nbytes=24) tại Objects/obmalloc.c:1445
#2 0x000000000041b717 trong _PyObject_DebugMalloc (nbytes=24) tại Objects/obmalloc.c:1412
#3 0x000000000044060a trong _PyUnicode_New (length=11) tại Objects/unicodeobject.c:346
#4 0x00000000004466aa trong PyUnicodeUCS2_DecodeUTF8Stateful (s=0x5c2b8d "__lltrace__", size=11, error=0x0, used=
    0x0) tại Đối tượng/unicodeobject.c:2531
#5 0x0000000000446647 trong PyUnicodeUCS2_DecodeUTF8 (s=0x5c2b8d "__lltrace__", size=11, error=0x0)
    tại Đối tượng/unicodeobject.c:2495
#6 0x0000000000440d1b trong PyUnicodeUCS2_FromStringAndSize (u=0x5c2b8d "__lltrace__", size=11)
    tại Đối tượng/unicodeobject.c:551
#7 0x0000000000440d94 trong PyUnicodeUCS2_FromString (u=0x5c2b8d "__lltrace__") tại Objects/unicodeobject.c:569
#8 0x0000000000584abd trong PyDict_GetItemString (v=
    {'Yuck': <type at remote 0xad4730>, '__buildins__': <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__") tại Objects/dictobject.c:2171

Lưu ý cách đối số từ điển cho PyDict_GetItemString được hiển thị dưới dạng repr() của nó, thay vì một con trỏ PyObject * mờ đục.

Tiện ích mở rộng hoạt động bằng cách cung cấp quy trình in tùy chỉnh cho các giá trị thuộc loại PyObject *. Nếu bạn cần truy cập các chi tiết cấp thấp hơn của một đối tượng, hãy truyền giá trị tới một con trỏ thuộc loại thích hợp. Ví dụ:

(gdb) p toàn cầu
$1 = {'__buildins__': <mô-đun tại 0x7ffff7fb1868 từ xa>, '__name__':
'__main__', 'ctypes': <mô-đun ở xa 0x7ffff7f14360>, '__doc__': Không,
'__gói__': Không có}

(gdb) p *(PyDictObject*)toàn cầu
$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 = '__buildins__', me_value = <mô-đun ở xa 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 = <mô-đun ở xa 0x7ffff7f14360>},
{me_hash = -8518757509529533123, me_key = '__doc__', me_value = Không},
{me_hash = 0, me_key = 0x0, me_value = 0x0}, {
  me_hash = 6614918939584953775, me_key = '__package__', me_value = Không có}}}

Lưu ý rằng máy in đẹp không thực sự gọi repr(). Đối với các loại cơ bản, họ cố gắng kết hợp chặt chẽ kết quả của nó.

Một lĩnh vực có thể gây nhầm lẫn là máy in tùy chỉnh cho một số loại trông rất giống máy in tích hợp của GDB dành cho các loại tiêu chuẩn. Ví dụ: máy in đẹp cho Python int (PyLongObject*) đưa ra một biểu diễn không thể phân biệt được với một trong các số nguyên cấp máy thông thường

(gdb) p some_machine_integer
$3 = 42

(gdb) p some_python_integer
4$ = 42

Cấu trúc bên trong có thể được tiết lộ khi chuyển sang PyLongObject*:

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

Một sự nhầm lẫn tương tự có thể xảy ra với loại str, trong đó đầu ra trông rất giống máy in tích hợp của gdb cho char *:

(gdb) p ptr_to_python_str
$6 = '__buildins__'

Máy in đẹp cho các phiên bản str mặc định sử dụng dấu ngoặc đơn (cũng như repr của Python cho chuỗi) trong khi máy in tiêu chuẩn cho các giá trị char * sử dụng dấu ngoặc kép và chứa địa chỉ thập lục phân:

(gdb) p ptr_to_char_star
$7 = 0x6d72c0 "xin chào thế giới"

Một lần nữa, chi tiết triển khai có thể được tiết lộ khi chuyển sang PyUnicodeObject*:

(gdb) p *(PyUnicodeObject*)$6
$8 = {ob_base = {ob_refcnt = 33, ob_type = 0x3dad3a95a0}, chiều dài = 12,
str = 0x7ffff2128500, hash = 7065186196740147912, trạng thái = 1, defenc = 0x0}

py-list

Tiện ích mở rộng thêm lệnh py-list, liệt kê mã nguồn Python (nếu có) cho khung hiện tại trong chuỗi đã chọn. Dòng hiện tại được đánh dấu bằng dấu ">":

(gdb) danh sách py
 901 nếu tùy chọn.profile:
 902 tùy chọn.profile = Sai
 903 hồ sơ_me()
 904 trở lại
 905
>906 u = UI()
 907 nếu không được bạn.bỏ cuộc:
 908 thử:
 909 gtk.main()
 910 ngoại trừ ngắt bàn phím:
 911 # properly thoát khi bàn phím bị gián đoạn...

Sử dụng py-list START để liệt kê ở một số dòng khác trong nguồn Python và py-list START,END để liệt kê một phạm vi dòng cụ thể trong nguồn Python.

py-uppy-down

Các lệnh py-uppy-down tương tự như các lệnh updown thông thường của GDB, nhưng hãy cố gắng di chuyển ở cấp độ khung CPython, thay vì khung C.

GDB không phải lúc nào cũng có thể đọc thông tin khung liên quan, tùy thuộc vào mức độ tối ưu hóa mà CPython được biên dịch. Trong nội bộ, các lệnh tìm kiếm các khung C đang thực thi chức năng đánh giá khung mặc định (nghĩa là vòng lặp trình thông dịch mã byte lõi trong CPython) và tra cứu giá trị của PyFrameObject * có liên quan.

Chúng phát ra số khung (ở cấp độ C) trong luồng.

Ví dụ:

(gdb) py-up
#37 Khung 0x9420b04, cho tệp /usr/lib/python2.6/site-packages/
gnome_sudoku/main.py, dòng 906, trong start_game ()
    u = UI()
(gdb) py-up
#40 Khung 0x948e82c, cho tệp /usr/lib/python2.6/site-packages/
gnome_sudoku/gnome_sudoku.py, dòng 22, trong start_game(main=<module at remote 0xb771b7f4>)
    main.start_game()
(gdb) py-up
Không thể tìm thấy khung python cũ hơn

vì vậy chúng tôi đang ở trên cùng của ngăn xếp Python.

Số khung tương ứng với số khung được hiển thị bằng lệnh backtrace tiêu chuẩn của GDB. Lệnh bỏ qua các khung C không thực thi mã Python.

Quay trở lại:

(gdb) py-down
#37 Khung 0x9420b04, cho tệp /usr/lib/python2.6/site-packages/gnome_sudoku/main.py, dòng 906, trong start_game ()
    u = UI()
(gdb) py-down
#34 (không thể đọc thông tin khung python)
(gdb) py-down
#23 (không thể đọc thông tin khung python)
(gdb) py-down
#19 (không thể đọc thông tin khung python)
(gdb) py-down
#14 Khung 0x99262ac, cho tệp /usr/lib/python2.6/site-packages/gnome_sudoku/game_selector.py, dòng 201, trong run_swallowed_dialog (self=<NewOrSavedGameSelector(new_game_model=<gtk.ListStore at remote 0x98fab44>, puzzle=None, save_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 ở điều khiển từ xa 0x984b474>, 'gsd.hints': 0, 'timer.active_time': <float ở điều khiển từ xa 0x984b494>, 'timer.total_time': <float ở điều khiển từ xa 0x984b464>}], hộp thoại=<gtk.Dialog ở điều khiển từ xa 0x98faaa4>, save_game_model=<gtk.ListStore ở điều khiển từ xa 0x98fad24>, sudoku_maker=<SudokuMaker(terminated=False, play=[], batch_siz...(cắt ngắn)
            én.run_dialog(self.dialog)
(gdb) py-down
#11 Khung 0x9aead74, dành cho tệp /usr/lib/python2.6/site-packages/gnome_sudoku/dialog_swallower.py, dòng 48, trong run_dialog (self=<SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, main_page=0) tại remote 0x98fa6e4>, d=<gtk.Dialog ở xa 0x98faaa4>)
            gtk.main()
(gdb) py-down
#8 (không thể đọc thông tin khung python)
(gdb) py-down
Không thể tìm thấy khung python mới hơn

và chúng tôi đang ở cuối ngăn xếp Python.

Lưu ý rằng trong Python 3.12 trở lên, cùng một khung ngăn xếp C có thể được sử dụng cho nhiều khung ngăn xếp Python. Điều này có nghĩa là py-uppy-down có thể di chuyển nhiều khung Python cùng một lúc. Ví dụ:

(gdb) py-up
#6 Khung 0x7ffff7fb62b0, cho tệp /tmp/rec.py, dòng 5, trong recursive_function (n=0)
   thời gian.ngủ(5)
#6 Khung 0x7ffff7fb6240, cho tệp /tmp/rec.py, dòng 7, trong recursive_function (n=1)
   hàm_đệ quy(n-1)
#6 Khung 0x7ffff7fb61d0, cho tệp /tmp/rec.py, dòng 7, trong recursive_function (n=2)
   hàm_đệ quy(n-1)
#6 Khung 0x7ffff7fb6160, cho tệp /tmp/rec.py, dòng 7, trong recursive_function (n=3)
   hàm_đệ quy(n-1)
#6 Khung 0x7ffff7fb60f0, cho tệp /tmp/rec.py, dòng 7, trong recursive_function (n=4)
   hàm_đệ quy(n-1)
#6 Khung 0x7ffff7fb6080, cho tệp /tmp/rec.py, dòng 7, trong recursive_function (n=5)
   hàm_đệ quy(n-1)
#6 Khung 0x7ffff7fb6020, cho tệp /tmp/rec.py, dòng 9, trong <module> ()
   đệ quy_function(5)
(gdb) py-up
Không thể tìm thấy khung python cũ hơn

py-bt

Lệnh py-bt cố gắng hiển thị dấu vết ngược ở cấp độ Python của luồng hiện tại.

Ví dụ:

(gdb) py-bt
#8 (không thể đọc thông tin khung python)
#11 Khung 0x9aead74, dành cho tệp /usr/lib/python2.6/site-packages/gnome_sudoku/dialog_swallower.py, dòng 48, trong run_dialog (self=<SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>, main_page=0) tại remote 0x98fa6e4>, d=<gtk.Dialog ở xa 0x98faaa4>)
            gtk.main()
#14 Khung 0x99262ac, cho tệp /usr/lib/python2.6/site-packages/gnome_sudoku/game_selector.py, dòng 201, trong run_swallowed_dialog (self=<NewOrSavedGameSelector(new_game_model=<gtk.ListStore at remote 0x98fab44>, puzzle=None, save_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 ở điều khiển từ xa 0x984b474>, 'gsd.hints': 0, 'timer.active_time': <float ở điều khiển từ xa 0x984b494>, 'timer.total_time': <float ở điều khiển từ xa 0x984b464>}], hộp thoại=<gtk.Dialog ở điều khiển từ xa 0x98faaa4>, save_game_model=<gtk.ListStore ở điều khiển từ xa 0x98fad24>, sudoku_maker=<SudokuMaker(terminated=False, play=[], batch_siz...(cắt ngắn)
            én.run_dialog(self.dialog)
#19 (không thể đọc thông tin khung python)
#23 (không thể đọc thông tin khung python)
#34 (không thể đọc thông tin khung python)
#37 Khung 0x9420b04, cho tệp /usr/lib/python2.6/site-packages/gnome_sudoku/main.py, dòng 906, trong start_game ()
    u = UI()
#40 Khung 0x948e82c, cho tệp /usr/lib/python2.6/site-packages/gnome_sudoku/gnome_sudoku.py, dòng 22, trong start_game (main=<module at remote 0xb771b7f4>)
    main.start_game()

Số khung tương ứng với số khung được hiển thị bằng lệnh backtrace tiêu chuẩn của GDB.

py-print

Lệnh py-print tra cứu tên Python và cố gắng in nó. Nó tìm kiếm cục bộ trong luồng hiện tại, sau đó là toàn cầu, rồi cuối cùng là nội trang

(gdb) tự in py
local 'self' = <SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>,
main_page=0) tại 0x98fa6e4 từ xa>
(gdb) py-print __name__
toàn cầu '__name__' = 'gnome_sudoku.dialog_swallower'
(gdb) py-print len
dựng sẵn 'len' = <hàm dựng sẵn len>
(gdb) py-print Scarlet_pimpernel
không tìm thấy 'scarlet_pimpernel'

Nếu khung C hiện tại tương ứng với nhiều khung Python, py-print chỉ xem xét khung đầu tiên.

py-locals

Lệnh py-locals tra cứu tất cả các cục bộ Python trong khung Python hiện tại trong luồng đã chọn và in các biểu diễn của chúng

(gdb) người dân địa phương
self = <SwappableArea(running=<gtk.Dialog at remote 0x98faaa4>,
main_page=0) tại 0x98fa6e4 từ xa>
d = <gtk.Dialog tại 0x98faaa4 từ xa>

Nếu khung C hiện tại tương ứng với nhiều khung Python, các khung hình cục bộ từ tất cả chúng sẽ được hiển thị

(gdb) người dân địa phương
Người dân địa phương cho recursive_function
n = 0
Người dân địa phương cho recursive_function
n = 1
Người dân địa phương cho recursive_function
n = 2
Người dân địa phương cho recursive_function
n = 3
Người dân địa phương cho recursive_function
n = 4
Người dân địa phương cho recursive_function
n = 5
Người dân địa phương cho <mô-đun>

Sử dụng với lệnh GDB

Các lệnh mở rộng bổ sung cho các lệnh tích hợp của GDB. Ví dụ: bạn có thể sử dụng số khung được hiển thị bởi py-bt bằng lệnh frame để chuyển đến một khung cụ thể trong chuỗi đã chọn, như sau:

(gdb) py-bt
(đầu ra bị cắt)
#68 Khung 0xaa4560, cho tệp Lib/test/regrtest.py, dòng 1548, trong <module> ()
        chính()
(gdb) khung 68
#68 0x00000000004cd1e6 trong PyEval_EvalFrameEx (f=Frame 0xaa4560, đối với tệp Lib/test/regrtest.py, dòng 1548, trong <module> (), Throwflag=0) tại Python/ceval.c:2665
2665 x = call_function(&sp, oparg);
(gdb) danh sách py
1543 # Run các thử nghiệm trong trình quản lý bối cảnh tạm thời thay đổi CWD thành
1544 # temporary và thư mục có thể ghi. Nếu không thể tạo hoặc
1545 # change CWD, CWD ban đầu sẽ được sử dụng. Z009zz ban đầu là
1546 # available từ test_support.SAVEDCWD.
1547 với test_support.temp_cwd(TESTCWD, Quiet=True):
>1548 chính()

Lệnh info threads sẽ cung cấp cho bạn danh sách các luồng trong quy trình và bạn có thể sử dụng lệnh thread để chọn một luồng khác:

(gdb) chủ đề thông tin
  105 Chủ đề 0x7fffefa18710 (LWP 10260) sem_wait () tại ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
  104 Chủ đề 0x7fffdf5fe710 (LWP 10259) sem_wait () tại ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
* 1 Thread 0x7ffff7fe2700 (LWP 10145) 0x00000038e46d73e3 trong select () tại ../sysdeps/unix/syscall-template.S:82

Bạn có thể sử dụng thread apply all COMMAND hoặc (viết tắt là t a a COMMAND) để chạy lệnh trên tất cả các luồng. Với py-bt, điều này cho phép bạn xem mọi luồng đang làm gì ở cấp độ Python

(gdb) t a py-bt

Chủ đề 105 (Chủ đề 0x7fffefa18710 (LWP 10260)):
#5 Khung 0x7fffd00019d0, cho tệp /home/david/coding/python-svn/Lib/threading.py, dòng 155, trong _acquire_restore (self=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) tại điều khiển từ xa 0xd7ff40>, count_owner=(1, 140737213728528), count=1, owner=140737213728528)
        self.__block.acquire()
#8 Khung 0x7fffac001640, dành cho tệp /home/david/coding/python-svn/Lib/threading.py, dòng 269, đang chờ (self=<_Condition(_Condition__lock=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock tại điều khiển từ xa 0x858770>, _RLock__count=1) tại điều khiển từ xa 0xd7ff40>, Acacqui=<phương pháp thực thể tại điều khiển từ xa 0xd80260>, _is_owned=<phương pháp thực thể tại điều khiển từ xa 0xd80160>, _release_save=<phương pháp thực thể tại điều khiển từ xa 0xd803e0>, phát hành=<phương thức thực thể tại từ xa 0xd802e0>, _acquire_restore=<phương pháp thực thể tại điều khiển từ xa 0xd7ee60>, _Verbose__verbose=False, _Condition__waiters=[]) tại điều khiển từ xa 0xd7fd10>, timeout=None, Waiter=<thread.lock at remote 0x858a90>, save_state=(1, 140737213728528))
            self._acquire_restore(saved_state)
#12 Khung 0x7fffb8001a10, cho tệp /home/david/coding/python-svn/Lib/test/lock_tests.py, dòng 348, trong f ()
            cond.wait()
#16 Khung 0x7fffb8001c40, dành cho tệp /home/david/coding/python-svn/Lib/test/lock_tests.py, dòng 37, trong tác vụ (tid=140737213728528)
                f()

Chủ đề 104 (Chủ đề 0x7fffdf5fe710 (LWP 10259)):
#5 Khung 0x7fffe4001580, cho tệp /home/david/coding/python-svn/Lib/threading.py, dòng 155, trong _acquire_restore (self=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock at remote 0x858770>, _RLock__count=1) tại điều khiển từ xa 0xd7ff40>, count_owner=(1, 140736940992272), count=1, owner=140736940992272)
        self.__block.acquire()
#8 Khung 0x7fffc8002090, dành cho tệp /home/david/coding/python-svn/Lib/threading.py, dòng 269, đang chờ (self=<_Condition(_Condition__lock=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=<thread.lock tại điều khiển từ xa 0x858770>, _RLock__count=1) tại điều khiển từ xa 0xd7ff40>, Acacqui=<phương pháp thực thể tại điều khiển từ xa 0xd80260>, _is_owned=<phương pháp thực thể tại điều khiển từ xa 0xd80160>, _release_save=<phương pháp thực thể tại điều khiển từ xa 0xd803e0>, phát hành=<phương thức thực thể tại từ xa 0xd802e0>, _acquire_restore=<phương pháp thực thể tại điều khiển từ xa 0xd7ee60>, _Verbose__verbose=False, _Condition__waiters=[]) tại điều khiển từ xa 0xd7fd10>, timeout=None, Waiter=<thread.lock at remote 0x858860>, save_state=(1, 140736940992272))
            self._acquire_restore(saved_state)
#12 Khung 0x7fffac001c90, cho tệp /home/david/coding/python-svn/Lib/test/lock_tests.py, dòng 348, trong f ()
            cond.wait()
#16 Khung 0x7fffac0011c0, cho tệp /home/david/coding/python-svn/Lib/test/lock_tests.py, dòng 37, trong tác vụ (tid=140736940992272)
                f()

Chủ đề 1 (Chủ đề 0x7ffff7fe2700 (LWP 10145)):
#5 Frame 0xcb5380, cho tệp /home/david/coding/python-svn/Lib/test/lock_tests.py, dòng 16, trong _wait ()
    thời gian.ngủ (0,01)
#8 Khung 0x7fffd00024a0, cho tệp /home/david/coding/python-svn/Lib/test/lock_tests.py, dòng 378, trong _check_notify (self=<ConditionTests(_testMethodName='test_notify', _resultForDoCleanups=<TestResult(_origin_stdout=<cStringIO.StringO ở từ xa 0xc191e0>, Skipped=[], _mirrorOutput=False, testRun=39, buffer=False, _origin_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>,expectedSuccesses=[], failed=[], ShouldStop=False, failedfast=False) tại 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>: <phương thức thực thể tại điều khiển từ xa 0xd751e0>, <gõ tại điều khiển từ xa 0x7f19e0...(cắt ngắn)
        _đợi()