コンテンツにスキップ

最終更新:2026-10-12

GDB マルチスレッド All-Stop デバッグ

gdbでマルチスレッドプログラムをデバッグするとき、スレッドはどの時点でも実行されうるため、特定のスレッドをデバッグしようとすると(例:next)、他のスレッドへの切り替えが発生し、現在のスレッド内で次の行に進みたいという期待に反してしまいます。

Thread 2 "arrow-compute-n" hit Breakpoint 1, arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5ba840, batch=...)
    at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
399         size_t thread_index = thread_indexer_();
(gdb) n
[Switching to Thread 0x7ffff2c08700 (LWP 29121)]

Thread 3 "arrow-compute-n" hit Breakpoint 1, arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
    at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
399         size_t thread_index = thread_indexer_();

このとき、私たちはどうすればよいのでしょうか?

それが今回紹介するスケジューラロック(scheduler-locking)です。GDBはすべてのスレッドをロックステップで実行することはできません。スレッドのスケジューリングはデバッグ対象のOSに依存するため(GDBが制御するものではありません)、現在のスレッドがステップ実行を完了する間に、他のスレッドが複数の文を実行している可能性があります。さらに、一般にプログラムが停止したとき、他のスレッドは文の途中で停止しており、明確な文の境界で停止しているわけではありません。実行再開やステップ実行の後、プログラムが別のスレッドで止まっていることに気づくことさえあります。これは、最初のスレッドが要求した操作を完了する前に、他のスレッドがブレークポイント、シグナル、または例外に遭遇したときに常に発生しうることです。

そして、gdbが提供するscheduler-lockingを使うことで、スケジューラをロックして1つのスレッドのみの実行を許可し、GDBのデフォルトの動作を変更することができます。

デフォルトではscheduler-lockingの値はreplayです。

(gdb) show scheduler-locking
Mode for locking scheduler during execution is "replay".

リプレイモードでの動作はonに似ており、記録モード中または通常の実行中の動作はオフに似ています。これがデフォルトモードです。

  • on

例えばt 2を実行するとスレッド2に切り替わりますが、このときonを設定すると、現在のスレッドのみがデバッグされます。

  • off

onの逆で、ロックされず、どのスレッドもいつでも実行できます。

  • step

ステップ実行時の動作はonに似ており、それ以外の場合はオフに似ています。stepを実行すると、現在のスレッド以外のスレッドには実行の機会が一切与えられませんが、continue、until、finishのようなコマンドを使用すると、それらは完全に実行できます。

例えば:next、stepのようなコマンドは、想定したプロセス内でデバッグを行い、他のスレッドはhang状態になります。continueのようなコマンドは、すべてのスレッドを実行させます。

サンプル

stepを設定したとき、continueがスレッド間で切り替えを行う様子が見て取れます。

(gdb) info threads
  Id   Target Id                                           Frame 
  1    Thread 0x7ffff7e88780 (LWP 31464) "arrow-compute-n" 0x00007ffff37e3de2 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
  2    Thread 0x7ffff3409700 (LWP 31494) "arrow-compute-n" arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
    at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
* 3    Thread 0x7ffff2c08700 (LWP 31495) "arrow-compute-n" arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
    at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
(gdb) c
Continuing.
[Switching to Thread 0x7ffff3409700 (LWP 31494)]

Thread 2 "arrow-compute-n" hit Breakpoint 1, arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
    at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
399         size_t thread_index = thread_indexer_();
(gdb) info threads
  Id   Target Id                                           Frame 
  1    Thread 0x7ffff7e88780 (LWP 31464) "arrow-compute-n" 0x00007ffff37e3de2 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
* 2    Thread 0x7ffff3409700 (LWP 31494) "arrow-compute-n" arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
    at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
  3    Thread 0x7ffff2c08700 (LWP 31495) "arrow-compute-n" 0x00007ffff605a372 in arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
    at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399

さらにcontinueすると、次のブレークポイントに入ります:

(gdb) info threads
  Id   Target Id                                           Frame 
  1    Thread 0x7ffff7e88780 (LWP 31464) "arrow-compute-n" 0x00007ffff37e3a35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
  2    Thread 0x7ffff3409700 (LWP 31494) "arrow-compute-n" 0x00007ffff5781b50 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
   from /code/arrow/cpp/build/debug/libarrow.so.200
* 3    Thread 0x7ffff2c08700 (LWP 31495) "arrow-compute-n" 0x00007ffff5781b50 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
   from /code/arrow/cpp/build/debug/libarrow.so.200
(gdb) c
Continuing.
[Switching to Thread 0x7ffff3409700 (LWP 31494)]

Thread 2 "arrow-compute-n" hit Breakpoint 3, 0x00007ffff5781b50 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
   from /code/arrow/cpp/build/debug/libarrow.so.200
(gdb) info threads
  Id   Target Id                                           Frame 
  1    Thread 0x7ffff7e88780 (LWP 31464) "arrow-compute-n" 0x00007ffff37e3a35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
* 2    Thread 0x7ffff3409700 (LWP 31494) "arrow-compute-n" 0x00007ffff5781b50 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
   from /code/arrow/cpp/build/debug/libarrow.so.200
  3    Thread 0x7ffff2c08700 (LWP 31495) "arrow-compute-n" 0x00007ffff5781b56 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
   from /code/arrow/cpp/build/debug/libarrow.so.200

スレッド間の切り替えが見て取れます。スレッド内に留まりたい場合は、直接nextを実行すればよく、例えば:

(gdb) t 2
[Switching to thread 2 (Thread 0x7ffff3409700 (LWP 32396))]
#0  0x00007ffff605ae0d in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch (this=0x5bbf90, thread_index=1, batch=...) at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:487
487         if (!batch.length) {
(gdb) n
490         std::lock_guard<std::mutex> guard(build_side_mutex_);
(gdb) n
491         build_accumulator_.InsertBatch(std::move(batch));

print操作を実行して関数呼び出しが引き起こされると、プログラムは停止し、期待通りの結果が出力されません。

(gdb) p batch.ToString()
[Switching to Thread 0x7ffff2c08700 (LWP 32397)]

Thread 3 "arrow-compute-n" hit Breakpoint 1, arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5ba840, batch=...)
    at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
399         size_t thread_index = thread_indexer_();
The program stopped in another thread while making a function call from GDB.
Evaluation of the expression containing the function
(arrow::compute::ExecBatch::ToString() const) will be abandoned.
When the function is done executing, GDB will silently stop.

このときonを設定すると、printができるようになります!

(gdb) set scheduler-locking on
(gdb) p batch.ToString()
$2 = "ExecBatch\n    # Rows: 2\n    0: Array[null,4]\n    1: Array[true,false]\n"

https://sourceware.org/gdb/current/onlinedocs/gdb.html/All_002dStop-Mode.html

関連トピック

コメント