gdbを使ったデッドロック(DeadLock)の解析



デバッガにはロードモジュールをそのまま、そのレベルでデバッグするabsolute debugger(adb)とソースレベルのエントリを利用してデバッグを行なう symbolic debugger(dbx/gdb)の2種類があります。

ソースコードが存在するプログラムのデバッグにはシンボリックデバッガが適しています。

スポンサードリンク

マルチスレッドプログラミングのDeakLockは、本当に厄介なものです。

運が良ければ、GDBからデッドロックの情報を取得する可能性があります。

前提条件

デッドロックを生成させる

ミューテックス(mutex)はリエントラント(再入可能)ではありません。

再入しようとするとデッドロックが発生することを利用して、作成したコードは次のとおりです。

#include <iostream>
#include <map>
#include <string>
#include <chrono>
#include <thread>
#include <mutex>

std::mutex gMutex;

int Reenter(){
   std::lock_guard<std::mutex> lLock(gMutex);
   return 10;	
}

int Callback()
{
   std::lock_guard<std::mutex> lLock(gMutex); 
   return Reenter();
}

int main(int argc, char**argv) {
    // Prints hello message...
    std::cout << "Hello CMake World!" << std::endl;
    std::thread lThread(Callback);
    lThread.join();
    std::cout << "sub thread is gone!" << std::endl;
    return 0;
}

コンパイル&実行手順

GDB解析可能なコンパイル方法は次のとおりです。

g ++ deadlock.cpp -ggdb -lpthread -std = c ++ 11 -o deadlock

実行方法は次のようになります。

hoge@:~/$ ./deadlock &
Hello CMake World!

これによりデッドロックが発生しました。

gdbを使用して不具合解析をする

pidを取得します。

hoge@:~/$ ps -a | grep deadlock
 3590 pts/4    00:00:00 deadlock

別の端末を使用(もしくは「&」をつけた場合は同じ端末を使用)してpidに接続します。

hoge@:~/$ sudo gdb -q deadlock 3590
[sudo] password for hoge:
Reading symbols from deadlock...done.
Attaching to program: /home/hoge/deadlock, process 3590
[New LWP 3591]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00007f1be31f7d2d in __GI___pthread_timedjoin_ex (threadid=139757737469696, thread_return=0x0, abstime=0x0, block=) at pthread_join_common.c:89
89      pthread_join_common.c: No such file or directory.
(gdb)

[New LWP]の「LMP」とは「軽量プロセス」ともいいます。

もともとはUNIXにおいて、プロセスよりもさらに小さいプログラムの実行単位としてLWPが導入されました。

ここで、pthread_join_common.c:89 で、止まっていることがわかります。

次にバックトレース(呼び出し履歴の表示)を表示します。

backtraceコマンドを使用すると現在のスレッドの呼び出し履歴が確認できます。

(gdb) bt
#0  0x00007f1be31f7d2d in __GI___pthread_timedjoin_ex (threadid=139757737469696, thread_return=0x0, abstime=0x0, block=<optimized out>) at pthread_join_common.c:89
#1  0x00007f1be2f23933 in std::thread::join() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x000055e67534516d in main (argc=1, argv=0x7ffe62213c08) at deadlock.cpp:26
(gdb)

全スレッドの情報を出力するには次のようなコマンドを打ちます。

(gdb) info threads
  Id   Target Id         Frame
* 1    Thread 0x7f1be360c740 (LWP 3590) "deadlock" 0x00007f1be31f7d2d in __GI___pthread_timedjoin_ex (threadid=139757737469696, thread_return=0x0,
    abstime=0x0, block=<optimized out>) at pthread_join_common.c:89
  2    Thread 0x7f1be24be700 (LWP 3591) "deadlock" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

mutex情報を出力するには次のようなコマンドを打ちます。

(gdb) p gMutex
$1 = {<std::__mutex_base> = {_M_mutex = pthread_mutex_t = {Type = Normal, Status = Acquired, possibly with waiters, Owner ID = 3591, Robust = No, Shared = No,
      Protocol = None}}, <No data fields>}

スレッドを切り替えるには次のようなコマンドを打ちます。

(gdb) thread 2
[Switching to thread 2 (Thread 0x7f1be24be700 (LWP 3591))]
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135     ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.

threadのスタックを確認する次のようなコマンドを打ちます。

(gdb) bt
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007f1be31f9023 in __GI___pthread_mutex_lock (mutex=0x55e675547140 <gMutex>) at ../nptl/pthread_mutex_lock.c:78
#2  0x000055e675344fff in __gthread_mutex_lock (__mutex=0x55e675547140 <gMutex>) at /usr/include/x86_64-linux-gnu/c++/7/bits/gthr-default.h:748
#3  0x000055e675345300 in std::mutex::lock (this=0x55e675547140 <gMutex>) at /usr/include/c++/7/bits/std_mutex.h:103
#4  0x000055e67534535c in std::lock_guard<std::mutex>::lock_guard (this=0x7f1be24bdd90, __m=...) at /usr/include/c++/7/bits/std_mutex.h:162
#5  0x000055e675345062 in Reenter () at deadlock.cpp:13
#6  0x000055e6753450c0 in Callback () at deadlock.cpp:20
#7  0x000055e6753455c1 in std::__invoke_impl<int, int (*)()> (__f=@0x55e67693c288: 0x55e675345090 <Callback()>) at /usr/include/c++/7/bits/invoke.h:60
#8  0x000055e6753453cf in std::__invoke<int (*)()> (__fn=@0x55e67693c288: 0x55e675345090 <Callback()>) at /usr/include/c++/7/bits/invoke.h:96
#9  0x000055e675345a94 in std::thread::_Invoker<std::tuple<int (*)()> >::_M_invoke<0ul> (this=0x55e67693c288) at /usr/include/c++/7/thread:234
#10 0x000055e675345a52 in std::thread::_Invoker<std::tuple<int (*)()> >::operator() (this=0x55e67693c288) at /usr/include/c++/7/thread:243
#11 0x000055e675345a22 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<int (*)()> > >::_M_run (this=0x55e67693c280)
    at /usr/include/c++/7/thread:186
#12 0x00007f1be2f236df in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#13 0x00007f1be31f66db in start_thread (arg=0x7f1be24be700) at pthread_create.c:463
#14 0x00007f1be297ea3f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb)

deadlock.cpp:13の「std::lock_guard lLock(gMutex);」で待ちが発生していることが分かります。

スポンサードリンク

オススメ書籍

人気の高い書籍および参考文献を紹介します。

スポンサードリンク