菜鸟科技网

GDB线程命令有哪些?如何调试多线程?

核心概念:GDB 中的线程表示

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

GDB线程命令有哪些?如何调试多线程?-图1
(图片来源网络,侵删)
  • 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 中执行。

    GDB线程命令有哪些?如何调试多线程?-图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线程命令有哪些?如何调试多线程?-图3
      (图片来源网络,侵删)
    • 打印所有线程的某个变量:
      (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>])

  • ccontinue: 恢复所有线程的执行。
  • 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

调试过程:

  1. 启动 GDB:

    gdb ./test_thread
  2. 设置断点:

    (gdb) break worker
    Breakpoint 1 at 0x4006d2: file test_thread.c, line 8.
  3. 运行程序:

    (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".
  4. 程序在第一个线程进入 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 函数。
  5. 使用 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
  6. 继续执行,让第二个线程也启动:

    (gdb) c
    Continuing.
    Worker 1: shared_var = 1
    Worker 2: shared_var = 2
    Worker 1: shared_var = 3
  7. 假设程序卡住了,我们想检查所有线程状态:

    # 在另一个终端发送 SIGSTOP 信号给进程
    # kill -STOP <pid>
    # 在 GDB 中
    (gdb) thread apply all bt
    # 这会显示两个线程都在 `sleep(1)` 调用中
  8. 锁定调度器,专注调试一个线程:

    (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 显示线程的创建和退出事件。

掌握这些命令,你就能高效地应对各种多线程调试挑战,祝你调试顺利!

分享:
扫描分享到社交APP
上一篇
下一篇