核心概念:GDB 中的线程表示
在 GDB 中,每个线程都有一个唯一的 全局线程 ID (Global Thread ID),这是一个数字,GDB 会根据操作系统提供的原生线程 ID(如 Linux 上的 LWP ID)为每个线程分配一个 特定于操作系统的线程 ID (OS Thread ID)。

info threads命令会同时显示这两种 ID,帮助你识别线程。- 默认情况下,GDB 只会对当前线程进行操作(
next,step,print等)。
基础线程命令
这些是调试多线程程序最常用、最核心的命令。
查看所有线程 (info threads)
这是查看程序中所有线程状态的第一步。
- 命令:
info threads(可以简写为info th) - 功能: 列出所有被 GDB 跟踪的线程,显示它们的 ID、优先级、是否在运行、以及当前所在的函数调用栈。
- 示例输出:
(gdb) info threads Id Target Id Frame * 1 Thread 0x7ffff7faa740 (LWP 12345) "my_program" main (argc=1, argv=0x7fffffffe5b8) at main.c:10 2 Thread 0x7ffff7faa740 (LWP 12347) "my_program" worker_func (data=0x0) at worker.c:25 3 Thread 0x7ffff7faa740 (LWP 12348) "my_program" worker_func (data=0x1) at worker.c:25 - 解读:
Id: GDB 内部的线程 ID( 号表示当前线程)。Target Id: 操作系统级别的线程 ID(如 LWP 12345)。Frame: 线程当前暂停位置的函数名和源码行号。
切换当前线程 (thread <id>)
当你需要调试一个非当前活动的线程时,需要切换到它。
- 命令:
thread <gdb_thread_id> - 功能: 将指定的线程设置为当前线程,之后所有的 GDB 命令(如
n,s,p,bt)都将作用于这个线程。 - 示例:
(gdb) thread 2 [Switching to thread 2 (Thread 0x7ffff7faa740 (LWP 12347))] #0 worker_func (data=0x0) at worker.c:25 25 while (1) { (gdb)n,s等命令将在线程 2 中执行。
(图片来源网络,侵删)
查看当前线程信息 (thread apply all info threads)
这个命令非常有用,它可以在不切换线程上下文的情况下,获取所有线程的详细信息。
- 命令:
thread apply all info threads - 功能: 对所有线程执行
info threads命令,并将输出集中显示,这比多次切换和查看要方便得多。 - 示例:
(gdb) thread apply all info threads Id Target Id Frame * 1 Thread 0x7ffff7faa740 (LWP 12345) "my_program" main (argc=1, argv=0x7fffffffe5b8) at main.c:10 2 Thread 0x7ffff7faa740 (LWP 12347) "my_program" worker_func (data=0x0) at worker.c:25 3 Thread 0x7ffff7faa740 (LWP 12348) "my_program" worker_func (data=0x1) at worker.c:25
注意, 号仍然在原来的线程上,但你已经看到了所有线程的信息。
控制与断点命令
在所有线程上执行命令 (thread apply all <command>)
这是一个强大的命令,可以让你对所有线程执行一个 GDB 命令。
- 命令:
thread apply all <command> - 功能: 对程序中的每一个线程都执行一次
<command>。 - 常用场景:
- 查看所有线程的调用栈:
(gdb) thread apply all bt
这会打印出所有线程的完整调用栈,对于分析死锁或程序卡住的位置非常有帮助。
(图片来源网络,侵删) - 打印所有线程的某个变量:
(gdb) thread apply all p my_var
- 查看所有线程的调用栈:
线程特定的断点 (break ... thread <id>)
你可以设置一个断点,但只对特定线程生效。
- 命令:
break <location> thread <gdb_thread_id> - 功能: 当指定的线程执行到
<location>时,程序才会暂停。 - 示例:
# 只在线程 2 执行到 worker_func 函数时暂停 (gdb) break worker_func thread 2
这对于调试线程特定的问题非常有用,可以避免其他线程干扰。
线组断点 (break ... thread groups all)
这是一个更高级的断点类型,当你设置一个线程组断点时,GDB 会在所有线程中设置一个内部断点,当任何一个线程触发该断点时,GDB 会暂停所有线程。
- 命令:
break <location> thread groups all - 功能: 当任意一个线程到达
<location>时,暂停整个进程的所有线程。 - 优点: 这能让你在断点处看到所有线程的完整状态,避免了“追击”问题(即一个线程触发断点后,其他线程继续运行,导致状态不一致)。
- 示例:
# 当任意一个线程进入 critical_section 函数时,暂停所有线程 (gdb) break critical_section thread groups all
高级与特定场景命令
设置线程调度模式 (set scheduler-locking)
在多线程调试中,当你单步执行一个线程时,你通常不希望其他线程干扰。set scheduler-locking 就是用来控制这个行为的。
-
命令:
set scheduler-locking on|off|step|release -
off(默认): 不锁定调度器,单步执行时,其他线程可能会抢占 CPU,导致程序行为难以预测。 -
on: 锁定调度器,除了当前正在调试的线程,其他所有线程都会被暂停,这是最常用的模式,可以让你专注于单个线程的逻辑。 -
step: 只在单步执行 (step,next) 时锁定调度器,当命令执行完毕,调度器立即解锁,允许其他线程运行。 -
release: 临时解锁调度器,让其他线程运行一次,然后自动重新锁定,这在某些需要其他线程运行一下才能复现问题的场景下很有用。 -
示例:
(gdb) set scheduler-locking on (gdb) next # 现在只有当前线程会执行下一步,其他线程被冻结
查看线程特定信息 (info threads 的增强)
info threads <regex>: 可以使用正则表达式来筛选显示的线程。info threads worker可以只显示名称中包含 "worker" 的线程。info threads <id> <id> ...: 可以指定只查看某些特定线程的 ID。
恢复线程执行 (continue [thread <id>])
c或continue: 恢复所有线程的执行。continue thread <id>: 只恢复指定的线程,其他所有线程保持暂停状态,这对于隔离调试非常有用。
实战演练示例
假设我们有一个简单的多线程程序 test_thread.c:
// test_thread.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
int shared_var = 0;
void* worker(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 5; i++) {
shared_var++;
printf("Worker %d: shared_var = %d\n", id, shared_var);
sleep(1);
}
return NULL;
}
int main() {
pthread_t t1, t2;
int id1 = 1, id2 = 2;
pthread_create(&t1, NULL, worker, &id1);
pthread_create(&t2, NULL, worker, &id2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
编译:
gcc -g -o test_thread test_thread.c -lpthread
调试过程:
-
启动 GDB:
gdb ./test_thread
-
设置断点:
(gdb) break worker Breakpoint 1 at 0x4006d2: file test_thread.c, line 8.
-
运行程序:
(gdb) run Starting program: /path/to/test_thread [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
-
程序在第一个线程进入
worker时暂停,查看所有线程状态:(gdb) info threads Id Target Id Frame * 1 Thread 0x7ffff7faa740 (LWP 12345) "test_thread" worker (arg=0x7fffffffdcbc) at test_thread.c:8 2 Thread 0x7ffff7faa740 (LWP 12348) "test_thread" 0x00007ffff7a2f9a5 in __clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:113
- 线程 1 正在执行
worker。 - 线程 2 还在
__clone系统调用中,表示它刚刚被创建,尚未开始执行worker函数。
- 线程 1 正在执行
-
使用
thread apply all查看所有线程的调用栈:(gdb) thread apply all bt #0 worker (arg=0x7fffffffdcbc) at test_thread.c:8 #1 0x00007ffff7dd0b75 in start_thread (arg=0x7ffff7faa740) at pthread_create.c:464 #2 0x00007ffff7a2f9a5 in __clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:113 [Current thread is 1 (Thread 0x7ffff7faa740 (LWP 12345))] #0 worker (arg=0x7fffffffdcbc) at test_thread.c:8 #1 0x00007ffff7dd0b75 in start_thread (arg=0x7ffff7faa740) at pthread_create.c:464 #2 0x00007ffff7a2f9a5 in __clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:113
-
继续执行,让第二个线程也启动:
(gdb) c Continuing. Worker 1: shared_var = 1 Worker 2: shared_var = 2 Worker 1: shared_var = 3
-
假设程序卡住了,我们想检查所有线程状态:
# 在另一个终端发送 SIGSTOP 信号给进程 # kill -STOP <pid> # 在 GDB 中 (gdb) thread apply all bt # 这会显示两个线程都在 `sleep(1)` 调用中
-
锁定调度器,专注调试一个线程:
(gdb) set scheduler-locking on (gdb) thread 1 [Switching to thread 1 (Thread 0x7ffff7faa740 (LWP 12345))] #0 worker (arg=0x7fffffffdcbc) at test_thread.c:10 10 sleep(1); (gdb) next Worker 1: shared_var = 4 11 }
即使
sleep(1)结束,线程 2 也不会运行,因为调度器被锁定了。
快速参考表
| 命令 | 缩写 | 功能描述 |
|---|---|---|
info threads |
info th |
列出所有线程及其状态。 |
thread <id> |
th <id> |
切换当前活动线程。 |
thread apply all <cmd> |
th apply all <cmd> |
对所有线程执行 <cmd> (如 bt, p var)。 |
break <loc> thread <id> |
b <loc> th <id> |
设置仅对特定线程有效的断点。 |
break <loc> thread groups all |
b <loc> th gr all |
设置线程组断点,任一线程触发则暂停所有线程。 |
set scheduler-locking on |
set sch on |
锁定调度器,仅当前线程运行。 |
set scheduler-locking off |
set sch off |
解锁调度器,所有线程可运行。 |
continue thread <id> |
c th <id> |
仅恢复指定线程的执行。 |
set print thread-events on |
set p th-events on |
显示线程的创建和退出事件。 |
掌握这些命令,你就能高效地应对各种多线程调试挑战,祝你调试顺利!
