首页 > 其他分享 >GDB-2——GDB调试多线程

GDB-2——GDB调试多线程

时间:2022-11-08 11:57:25浏览次数:47  
标签:Thread thread locking worker GDB 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 命令查看当前线程调用堆栈来确定是主进程还是子进程。

 

标签:Thread,thread,locking,worker,GDB,gdb,线程,多线程,调试
From: https://www.cnblogs.com/hellokitty2/p/16869185.html

相关文章

  • busybox-date.c调试
    注释代码/*vi:setsw=4ts=4:*//**Minidateimplementationforbusybox**byMatthewGrant<[email protected]>**iso-formathandlingaddedbyRobert......
  • java 多线程买票案例
    packagecom.tedu.threadStudy;publicclassstudyTicket{publicstaticvoidmain(String[]args){YouThreadyouThread=newYouThread();T......
  • C#里的多线程,一网打尽thread,task,parallel
    C#里的多线程,一网打尽1.Syncawait2.Thread3.Threadpool4.Task5.Parallel Tasktask=newTask(()=>{});task.Start();Tasktask=Task.Run(()=>{};TaskFactoryta......
  • 多线程的创建
    1.继承Thread2.重写Thread的run()方法-->将此线程执行的操作声明在run()中3.创建Thread类的子类的对象4.调用此对象调用start 例如:classMyThread extendsThr......
  • 为可执行程序(sys/exe)生成公有调试符号
      自Win10推出以来,Windows的更新频度变为每年2次(赶上隔壁Ubuntu的更新节奏了)。每次更新,MS都会提出一堆新要求。对于2018年RS4的更新,MS要求所有提交到WU(WindowsUpdat......
  • 对<源码级调试WDF框架>一文进行补充
    MS曾在他的Github站点上提出过​​《源码级调试WDF框架》​​的方法,文中提到通过.srcfix命令使windbg源码服务器路径指向MS的源码服务器。这样当调试到wdf框架代码时,windbg......
  • 2种内核级反用户态调试方法
    0.前言  很久前写过一些应用层的反调试的文章,这类反调试方法的好处是易于实现,但缺点是很容易被绕过----保护因此失效。我们的危机公关手段是:将反调试功能放到内核中用驱......
  • java的多线程
    程序program:完成特定任务、用某种语言编写的一组指令的集合。即一段静态的代码,静态对象进程process:程序的一次执行过程,或是正在运行的一段程序。是一个动态的过程,有产生和......
  • windbg调试服务程序
       相比通过输出日志来跟踪程序运行状态,我更倾向使用调试器。虽然我早知调试服务很麻烦,总不会比调试驱动还麻烦吧?基于这个想法,我尝试了在win7上使用windbg调试服务并记......
  • 多线程详解
    1.多线程快速入门1.1进程与线程什么是进程?CPU从硬盘中读取一段程序到内存中,该执行程序的实例就叫做进程。一个程序如果被CPU多次读取到内存中,则变成多个独立的进程......