GDB-2——GDB调试多线程
一、简介
前一博文实际上已经介绍了多线程的调试方法,这节专门进行一下总结。
二、调试多线程
1. 使用 gdb 将程序跑起来,然后按 Ctrl + C 将程序中断下来,使用 info threads 命令查看当前进程有多少线程。
2. 使用 thread <线程编号> 可以切换到对应的线程,然后使用 bt 命令可以查看对应线程从顶到底层的函数调用,以及上层调用下层对应的源码中的位置;可以使用 frame <栈函数编号> 切换到当前函数调用堆栈的任何一层函数调用中去,然后分析该函数执行逻辑;使用 print 等命令输出各种变量和表达式值。
标号为1的线程是主线程,可以切到标号为1的线程上,然后 bt 打印出的调用栈就可以看到main()函数。对每个线程都进行这样的分析之后,基本上就可以搞清楚整个程序运行中的执行逻辑了。对不熟悉的C/C++项目搞一轮比较有帮助。
3. 调试时控制线程切换
在调试多线程程序时,有时候我们希望执行流一直在某个线程执行,而不是切换到其他线程。gdb 提供了一个在调试时将程序执行流锁定在当前调试线程的命令选项 scheduler-locking 选项,它有三个值,分别是 on、step 和 off,使用方法是 set scheduler-locking on/step/off。
(1) set scheduler-locking on 可以用来锁定当前线程,只观察这个线程的运行情况。当锁定这个线程时,其他线程就处于了暂停状态,也就是说你在当前线程执行 next、step、until、finish、return 命令时,其他线程是不会运行的。需要注意的是,你在使用 set scheduler-locking on/step 选项时要确认下当前线程是否是你期望锁定的线程,如果不是,可以使用 thread <线程编号> 切换到你需要的线程,然后再进行锁定。
(2) set scheduler-locking step 也是用来锁定当前线程,当且仅当使用 next 或 step 命令做单步调试时会锁定当前线程,如果使用 until、finish、return 等线程内的非单步调试命令,其他线程还是有机会运行的。相比较 on 选项值,step 选项值给为单步调试提供了更加精细化的控制,因为通常我们只希望在单步调试时,不希望其他线程对当前调试的各个变量值造成影响。
(3) set scheduler-locking off 用于关闭锁定当前线程。
4. 断点是全局的,无论哪个线程执行到断点位置,所有线程都会停住。
5. 实验
(1) 实验程序
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 int g = 0; 6 7 void* worker_thread_1(void* p) 8 { 9 while (true) { 10 g = 100; 11 printf("worker_thread_1\n"); 12 usleep(300000); 13 } 14 return NULL; 15 } 16 17 void* worker_thread_2(void* p) 18 { 19 while (true) 20 { 21 g = -100; 22 printf("worker_thread_2\n"); 23 usleep(500000); 24 } 25 return NULL; 26 } 27 28 int main() 29 { 30 pthread_t thread_id_1; 31 pthread_create(&thread_id_1, NULL, worker_thread_1, NULL); 32 pthread_t thread_id_2; 33 pthread_create(&thread_id_2, NULL, worker_thread_2, NULL); 34 while (true) { 35 g = -1; 36 printf("g=%d\n", g); 37 g = -2; 38 printf("g=%d\n", g); 39 g = -3; 40 printf("g=%d\n", g); 41 g = -4; 42 printf("g=%d\n", g); 43 usleep(1000000); 44 } 45 return 0; 46 }
(2) 实验示范
lvm:~/origin_tmp/3.gdb$ g++ -g -o thread thread.cpp -lpthread lvm:~/origin_tmp/3.gdb$ gdb thread ... Reading symbols from thread...done. (gdb) b 10 Breakpoint 1 at 0x4006d2: file thread.cpp, line 10. (gdb) b 35 Breakpoint 2 at 0x40076b: file thread.cpp, line 35. (gdb) r Starting program: /origin_tmp/3.gdb/thread [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff77ef700 (LWP 219338)] [New Thread 0x7ffff6fee700 (LWP 219339)] worker_thread_2 [Switching to Thread 0x7ffff77ef700 (LWP 219338)] Thread 2 "thread" hit Breakpoint 1, worker_thread_1 (p=0x0) at thread.cpp:10 10 g = 100; (gdb) until 43 worker_thread_2 //线程2在运行 [Switching to Thread 0x7ffff7fd9700 (LWP 219334)] Thread 1 "thread" hit Breakpoint 2, main () at thread.cpp:35 35 g = -1; (gdb) until 43 worker_thread_2 //线程2在运行 worker_thread_1 //线程1在运行 g=-1 //主线程在运行 g=-2 g=-3 g=-4 main () at thread.cpp:43 43 usleep(1000000); (gdb) set scheduler-locking on //设置 locking on (gdb) until 43 Thread 1 "thread" hit Breakpoint 2, main () at thread.cpp:35 35 g = -1; (gdb) until 43 //可以看到locking on后只有主线程运行 g=-1 g=-2 g=-3 g=-4 main () at thread.cpp:43 43 usleep(1000000); (gdb) until 43 //可以看到locking on后只有主线程运行 Thread 1 "thread" hit Breakpoint 2, main () at thread.cpp:35 35 g = -1; (gdb) set scheduler-locking step //设置locking step (gdb) until 43 worker_thread_2 [Switching to Thread 0x7ffff77ef700 (LWP 219338)] Thread 2 "thread" hit Breakpoint 1, worker_thread_1 (p=0x0) at thread.cpp:10 10 g = 100; (gdb) until 43 //locking step下,until还是多线程可以运行 g=100 g=-2 g=-3 g=-4 worker_thread_2 worker_thread_1 Thread 2 "thread" hit Breakpoint 1, worker_thread_1 (p=0x0) at thread.cpp:10 10 g = 100; (gdb) info threads //切到主线程 Id Target Id Frame 1 Thread 0x7ffff7fd9700 (LWP 219334) "thread" 0x00007ffff78bc38d in nanosleep () at ../sysdeps/unix/syscall-template.S:84 * 2 Thread 0x7ffff77ef700 (LWP 219338) "thread" worker_thread_1 (p=0x0) at thread.cpp:10 3 Thread 0x7ffff6fee700 (LWP 219339) "thread" 0x00007ffff78bc38d in nanosleep () at ../sysdeps/unix/syscall-template.S:84 (gdb) thread 1 [Switching to thread 1 (Thread 0x7ffff7fd9700 (LWP 219334))] #0 0x00007ffff78bc38d in nanosleep () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. (gdb) n //在locking step下单步运行情况下只有主线运行 main () at thread.cpp:34 34 while (true) { (gdb) n Thread 1 "thread" hit Breakpoint 2, main () at thread.cpp:35 35 g = -1; (gdb) n 36 printf("g=%d\n", g); (gdb) n g=-1 37 g = -2; (gdb) n 38 printf("g=%d\n", g); (gdb) n g=-2 39 g = -3; (gdb) n 40 printf("g=%d\n", g); (gdb) n g=-3 41 g = -4; (gdb) n 42 printf("g=%d\n", g); (gdb) n g=-4 43 usleep(1000000); (gdb) n n 34 while (true) {
三、调试多进程
在实际的应用中,有一类应用会通过 Linux 函数 fork 出新的子进程。例如网络传输中的创建一个新的进程来处理该连接上的客户端,新产生的进程与原进程互为父子关系。那么如何用 gdb 调试这样父子进程呢,有两种方法:
(1) 方法1: 开两个 shell 终端分别对父子进程进行调试。用 gdb 先调试父进程,等子进程被 fork 出来后,重新开启一个 Shell 窗口,使用 gdb attach 到子进程上去。
(2) set follow-fork <parent/child> 来设置是当一个进程 fork 出新的子进程时,gdb 是继续调试父进程还是子进程,默认是父进程。使用 how follow-fork mode 来查看当前的调试模式。
默认是fork之后attach到父进程的,但是如果想在 fork 之后 gdb 去 attach 子进程,我们可以在程序 run 之前在 gdb 中设置 set follow-fork child 来实现。可以通过 Ctrl +C 将程序中断下来,然后使用 bt 命令查看当前线程调用堆栈来确定是主进程还是子进程。
posted on 2022-11-08 11:46 Hello-World3 阅读(3761) 评论(0) 编辑 收藏 举报