Linux 應用程式除錯技術/死鎖
外觀
尋找死鎖意味著重建執行緒和資源(互斥量、訊號量、條件變數等)之間的依賴關係圖——誰擁有什麼,誰想要獲取什麼。典型的死鎖看起來像該圖中的迴圈。這項任務很繁瑣,因為我們正在尋找的一些引數已經被編譯器最佳化到暫存器中。
以下是對 x86_64 死鎖的分析。在這個平臺上,暫存器 r8 包含第一個引數:互斥量的地址
(gdb) thread apply all bt
...
Thread 4 (Thread 0x419bc940 (LWP 12275)):
#0 0x0000003684c0d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003684c08e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003684c08cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a50 in thread1 (threadid=0x1) at deadlock.c:66
#4 0x0000003684c0673d in start_thread () from /lib64/libpthread.so.0
#5 0x00000036840d3d1d in clone () from /lib64/libc.so.6
Thread 3 (Thread 0x421bd940 (LWP 12276)):
#0 0x0000003684c0d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003684c08e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003684c08cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400c07 in thread2 (threadid=0x2) at deadlock.c:111
#4 0x0000003684c0673d in start_thread () from /lib64/libpthread.so.0
#5 0x00000036840d3d1d in clone () from /lib64/libc.so.6
...
(gdb) thread 4
[Switching to thread 4 (Thread 0x419bc940 (LWP 12275))]#2 0x0000003684c08cdc in pthread_mutex_lock ()
from /lib64/libpthread.so.0
(gdb) frame 2
#2 0x0000003684c08cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
(gdb) info reg
...
r8 0x6015a0 6296992
...
(gdb) p *(pthread_mutex_t*)0x6015a0
$3 = {
__data = {
__lock = 2,
__count = 0,
__owner = 12276, <== LWP 12276 is Thread 3
__nusers = 1,
__kind = 0, <== non-recursive
__spins = 0,
__list = {
__prev = 0x0,
__next = 0x0
}
},
__size = "\002\000\000\000\000\000\000\000\364/\000\000\001", '\000' <repeats 26 times>,
__align = 2
}
(gdb) thread 3
[Switching to thread 3 (Thread 0x421bd940 (LWP 12276))]#0 0x0000003684c0d4c4 in __lll_lock_wait ()
from /lib64/libpthread.so.0
(gdb) bt
#0 0x0000003684c0d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003684c08e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003684c08cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400c07 in thread2 (threadid=0x2) at deadlock.c:111
#4 0x0000003684c0673d in start_thread () from /lib64/libpthread.so.0
#5 0x00000036840d3d1d in clone () from /lib64/libc.so.6
(gdb) info reg
...
r8 0x6015e0 6297056
...
(gdb) p *(pthread_mutex_t*)0x6015e0
$4 = {
__data = {
__lock = 2,
__count = 0,
__owner = 12275, <=== Thread 4
__nusers = 1,
__kind = 0,
__spins = 0,
__list = {
__prev = 0x0,
__next = 0x0
}
},
__size = "\002\000\000\000\000\000\000\000\363/\000\000\001", '\000' <repeats 26 times>,
__align = 2
}
執行緒 3 和 4 正在爭奪兩個互斥量。
注意:如果 gdb 無法找到符號 pthread_mutex_t,因為它沒有載入 pthreadtypes.h 的符號表,你仍然可以按如下方式列印結構的各個成員
(gdb) print *((int*)(0x6015e0))
$4 = 2
(gdb) print *((int*)(0x6015e0)+1)
$5 = 0
(gdb) print *((int*)(0x6015e0)+2)
$6 = 12275
一個 std::mutex 有類似的結構,其中 __owner 是 LWP
(gdb) p mtx
$1 = (std::mutex &) @0x7fffffffe5b0: {<std::__mutex_base> = {_M_mutex = {__data = {__lock = 1, __count = 0, __owner = 256829, __nusers = 1, __kind = 0, __spins = 0,
__elision = 0, __list = {__prev = 0x0, __next = 0x0}}, __size = "\001\000\000\000\000\000\000\000=\353\003\000\001", '\000' <repeats 26 times>,
__align = 1}}, <No data fields>}
(gdb) info threads
Id Target Id Frame
...
* 3 Thread 0x7ffff77ff640 (LWP 256829) "a.out" operator() (nLoops=1000000, __closure=0x55555556f770) at lockfree1.cpp:64
可以構建一個插入庫來 自動化死鎖分析。需要插入大量 API,即使那樣,也有一些情況庫無法注意到。例如,一個不涉及任何使用者空間鎖定機制的死鎖兩個執行緒的創造性方法是讓每個執行緒加入另一個執行緒。因此,插入工具的診斷功能有限。
還有許多其他工具可用
- gdb-automatic-deadlock-detector - 指令碼向 GDB 添加了新的命令“blocked”。此命令分析所有執行緒並顯示哪些執行緒正在等待其他執行緒。它還顯示了執行緒之間的死鎖。
- 使用者空間 lockdep。正在進行的工作。
- Locksmith。基本。
- Valgrind Helgrind。沒有像其他 Valgrind 工具那樣遭受嚴重的減速(特別是記憶體分析工具),但在 amd64 平臺上無法長期執行。
pthreads對某些同步機制有一些內建支援(例如PTHREAD_MUTEX_ERRORCHECK互斥量)。
此外,還有一個用於健壯互斥量的 POSIX 互斥量構造屬性(一個可恢復的互斥量,由其程序已死亡的執行緒持有):錯誤返回程式碼表示先前的所有者已死亡;獲取鎖意味著承擔處理任何清理的責任。
在啟用了 lockdep 的核心上,按 Alt+SysRq+d 會轉儲有關核心已知的所有鎖的資訊。