GDB 调试多线程程序的总结
gdb 中step、next与finish的区别
step 就是单步执行,遇到子函数就进入并且继续单步执行;在其他调试其中相当于step-into命令,作用是移动到下一个可执行的代码行。如果当前行是一个函数调用,则调试器进入函数并停止在函数体的第一行。step可以帮助初步揭开代码位置的谜团,例如:函数调用和函数本身可能在不同的文件中。
next 是在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行,而是将子函数整个执行完再停止,也就是把子函数整个作为一步。在其他调试器中相当于step-over,作用是在同一个调用栈层中移动到下一个可执行的代码行。调试器不会进入函数体。如果当前行是函数的最后一行,则,next将进入下一个栈层,并在调用函数的下一行停止。
finish 就是但单步执行到子函数内时,用step out就可以执行完子函数余下部分,并返回到上一层函数。在其他调试器中相当于step-out,作用是在栈中前进到到下一层,并在调用函数的下一行停止。
多线程的gdb调试
假设现在有一个主线程创建了一个子线程。
gdb调试时,设置断点,单步调试到pthread_create处的时候,这时候会创建子线程,会出现如下信息
[New Thread 0x7ffff6fd1700 (LWP 6376)]
默认情况下,gdb只跟踪主线程,新创建的线程都被阻塞在pthread_create函数处。
info threads
可以调试的所有线程,gdb会为每个线程分配一个ID,这个ID和线程ID不同,ID号一般从1开始。
如下,表示当前有两个线程1和2,*表示跟踪主线程1
(gdb) info threads
Id Target Id Frame
2 Thread 0x7ffff6fd1700 (LWP 6376) "test" 0x00007ffff70d0851 in clone ()
from /lib64/libc.so.6
* 1 Thread 0x7ffff7fee740 (LWP 6375) "test" main (argc=1, argv=0x7fffffffe2d8) at test.cpp:31
thread ID
切换当前调试的线程为指定ID号,ID是gdb分配的序号,不是线程TID。
set scheduler-locking off|on
on锁定其他线程,只有当前选择调试的线程执行,off表示不锁定任何线程,当运行到断点处,将所有的线程都暂停下来,直到指定某个线程继续执行,如果在当前线程下使用continue的话会启动所有线程(GDB默认)。
多线程调试控制指令
thread apply ID1 ID2 ...IDn gdb_command
指定多个线程执行gdb中的command指令
thread apply all command
指定所有线程执行gdb中的command指令
non-stop模式
上面说过一个线程中断在一个断点上,其他所有的线程都会被freeze。新版本的GDB中,引入了non-stop模式,在这个模式下:
-
当某个或多个线程在一个断点上,其他线程仍会并行运行
-
你可以选择某个被中断的线程,只让他运行。
-
non-stop模式表示不停止模式,除了断点有关的进程会被停下来,其他线程会继续执行。
设置non-stop模式,打开gdb后,在开始r之前,首先连续输入下面的指令
set target-async 1
set pagination off
set non-stop on
总结调试多线程的命令
info threads
显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程
thread ID(1,2,3…)
切换当前调试的线程为指定ID的线程
break thread_test.c:123 thread all
(例:在相应函数的位置设置断点break pthread_run1) 在所有线程中相应的行上设置断点
thread apply ID1 ID2 command
让一个或者多个线程执行GDB命令command
thread apply all command
让所有被调试线程执行GDB命令command
set scheduler-locking
选项 command 设置线程是以什么方式来执行命令
set scheduler-locking off
不锁定任何线程,也就是所有线程都执行,这是默认值
set scheduler-locking on
只有当前被调试程序会执行
set scheduler-locking on step
在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行
线程池调试技巧
调试进程池和线程池中的程序一个不错的方法,是将池中的个数减少至1,观察是否正确,然后逐步增加线程数量,调试线程的同步是否正确