环境
系统:CentOs 7.4版本
编译器:gcc 4.8.5版本
debug:gdb 7.6.1版本
文本编辑器:VScode or VIM
准备工作
默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序。只需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)即可。
follow-fork-mode | detach-on-fork | 说明 |
parent | on | 只调试主进程(GDB默认) |
child | on | 只调试子进程 |
parent | off | 同时调试两个进程,gdb跟主进程,子进程block在fork位置 |
child | off | 同时调试两个进程,gdb跟子进程,主进程block在fork位置 |
设置方法:set follow-fork-mode [parent|child] set detach-on-fork [on|off]
调试多进程
测试代码:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
void test()
{
printf("this is a test\n");
}
int main()
{
pid_t id;
id = fork();
if(id == 0)
{
test();
exit(EXIT_SUCCESS);
}
else
{
printf("this is main\n");
}
return 0;
}
运行结果:
[wanghe@localhost ~]$ ./test_fork.exe
this is main
this is a test
下面开始调试,常用的命令如下:
查询正在调试的进程:info inferiors
切换调试的进程: inferior <infer number>
添加新的调试进程: add-inferior [-copies n] [-exec executable] ,可以用file executable来分配给inferior 可执行文件。
其他:remove-inferiors infno, detach inferior
首先我们需要调试两个进程,先看程序的默认模式:
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) show detach-on-fork
Whether gdb will detach the child of a fork is on.
可以看出默认模式是parent和on,由于我们需要调试两个进程,所以只需要设置detach-on-fork 为off,gdb先跟着主进程。
(gdb) set detach-on-fork off
(gdb) show detach-on-fork
Whether gdb will detach the child of a fork is off.
接着通过list或l指令来查看fork的位置:
(gdb) list
5
6 void test()
7 {
8 printf("this is a test\n");
9 }
10
11 int main()
12 {
13 pid_t id;
14 id = fork();
所以把断点设置在15行,通过b 15命令,然后通过info inferiors命令查看程序中的进程:
(gdb) run
Starting program: /home/wanghe/test_fork.exe
[New process 13978]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
Breakpoint 1, main () at test_fork.c:15
15 if(id == 0)
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
(gdb) info inferiors
Num Description Executable
2 process 13978 /home/wanghe/test_fork.exe
* 1 process 13974 /home/wanghe/test_fork.exe
可以看出有两个进程,主进程标号是1,子进程标号是2。
(gdb) n
22 printf("this is main\n");
通过n指令可以看到主进程的执行情况。
通过inferior + 编号可以切换进程调试。
(gdb) inferior 2
[Switching to inferior 2 [process 13978] (/home/wanghe/test_fork.exe)]
[Switching to thread 2 (process 13978)]
#0 0x00007ffff7ad30c2 in fork () from /lib64/libc.so.6
(gdb) info inferiors
Num Description Executable
* 2 process 13978 /home/wanghe/test_fork.exe
1 process 13974 /home/wanghe/test_fork.exe
切换到子进程。
(gdb) n
Single stepping until exit from function fork,
which has no line number information.
Breakpoint 1, main () at test_fork.c:15
15 if(id == 0)
(gdb) n
17 test();
(gdb) n
this is a test
18 exit(EXIT_SUCCESS);
然后各个进程分别可以执行完毕。
调试多线程
set scheduler-locking off | on off是所有线程都运行,on是只有当前线程会执行
调试多线程和调试多进程的情况相似。
测试代码:
#include<stdio.h>
#include<pthread.h>
void *thread1(void *arg)
{
printf("%s\n",(char*)arg);
}
void *thread2(void *arg)
{
printf("%s\n",(char*)arg);
}
void *thread3(void *arg)
{
printf("%s\n",(char*)arg);
}
int main()
{
pthread_t t1,t2,t3;
pthread_create(&t1,NULL,thread1,"thread1");
pthread_create(&t2,NULL,thread2,"thread2");
pthread_create(&t3,NULL,thread3,"thread3");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
return 0;
}
首先通过list指令看:
(gdb) list
13
14 void *thread3(void *arg)
15 {
16 printf("%s\n",(char*)arg);
17 }
18
19 int main()
20 {
21 pthread_t t1,t2,t3;
22 pthread_create(&t1,NULL,thread1,"thread1");
第一次创建线程是在22行,所以断点设置在22行:
(gdb) b 22
Breakpoint 1 at 0x400643: file test_thread.c, line 22.
(gdb) run
Starting program: /home/wanghe/test_thread.exe
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, main () at test_thread.c:22
22 pthread_create(&t1,NULL,thread1,"thread1");
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7ffff7fe0740 (LWP 15251) "test_thread.exe" main ()
at test_thread.c:22
这时候可以看出只有一个主线程线程,继续同过n指令执行。
(gdb) n
[New Thread 0x7ffff77f1700 (LWP 15274)]
23 pthread_create(&t2,NULL,thread2,"thread2");
(gdb) info threads
Id Target Id Frame
2 Thread 0x7ffff77f1700 (LWP 15274) "test_thread.exe" 0x00007ffff78efff1 in clone () from /lib64/libc.so.6
* 1 Thread 0x7ffff7fe0740 (LWP 15251) "test_thread.exe" main ()
at test_thread.c:23
发现多了一个子线程。
通过thread + 线程编号进行切换线程。
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff77f1700 (LWP 15274))]
#0 0x00007ffff78efff1 in clone () from /lib64/libc.so.6
(gdb) info threads
Id Target Id Frame
* 2 Thread 0x7ffff77f1700 (LWP 15274) "test_thread.exe" 0x00007ffff78efff1 in clone () from /lib64/libc.so.6
1 Thread 0x7ffff7fe0740 (LWP 15251) "test_thread.exe" main ()
at test_thread.c:23
通过n指令继续执行。
(gdb) n
Single stepping until exit from function clone,
which has no line number information.
0x00007ffff7bc6d10 in start_thread () from /lib64/libpthread.so.0
(gdb) n
Single stepping until exit from function start_thread,
which has no line number information.
[New Thread 0x7ffff6ff0700 (LWP 15283)]
thread2
[New Thread 0x7ffff67ef700 (LWP 15284)]
[Thread 0x7ffff6ff0700 (LWP 15283) exited]
thread3
thread1
[Thread 0x7ffff67ef700 (LWP 15284) exited]
[Thread 0x7ffff77f1700 (LWP 15274) exited]
No unwaited-for children left.
发现三个子线程都结束了,说明是并发执行,同时程序就剩一个主线程。
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7fe0740 (LWP 15251) "test_thread.exe" 0x00007ffff7bc7f47 in pthread_join () from /lib64/libpthread.so.0
The current thread <Thread ID 2> has terminated. See `help thread'.
测试代码
通过下面的测试代码,既包含进程,每个进程又有一个子线程,输出的顺序不是1 2 3 4,现在要求通过调试使得输出顺序是1 2 3 4。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
void *compute()//子进程的子线程
{
sleep(1);
printf("4.子进程的子线程\n");
}
void *main_compute()//主进程的子线程
{
sleep(1);
printf("2.父进程的子线程\n");
}
int main( int argc , char *argv[] )
{
pid_t fork_id;
fork_id = fork();
if(fork_id == 0) //子进程
{
sleep(1);
printf("3.这里是子进程\n");
pthread_t id;
pthread_create(&id,NULL,compute,NULL);
pthread_join(id,NULL);
exit(EXIT_SUCCESS);
}
else //父进程
{
sleep(1);
printf("1.这里是父进程\n");
pthread_t id;
pthread_create(&id,NULL,main_compute,NULL);
pthread_join(id,NULL);
}
return 0;
}
运行结果:
[wanghe@localhost ~]$ ./test.exe
1.这里是父进程
3.这里是子进程
2.父进程的子线程
4.子进程的子线程
如果使main_compute函数中的sleep函数的参数改成2s,就会调整并发执行结果的顺序,如下:
[wanghe@localhost ~]$ ./test.exe
1.这里是父进程
3.这里是子进程
4.子进程的子线程
2.父进程的子线程
并发执行的结果不是1 2 3 4,所以接下来通过gdb来尝试。
首先通过list找到第一个fork位置。
(gdb) list
12
13 void *main_compute()//主进程的子线程
14 {
15 sleep(1);
16 printf("2.父进程的子线程\n");
17 }
18 int main( int argc , char *argv[] )
19 {
20 pid_t fork_id;
21 fork_id = fork();
设置断点为b 21,然后设置为同时调试两个进程,gdb默认跟主进程。
(gdb) b 21
Breakpoint 1 at 0x400700: file test.c, line 21.
(gdb) set detach-on-fork off
(gdb) run
Starting program: /home/wanghe/test.exe
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, main (argc=1, argv=0x7fffffffe008) at test.c:21
21 fork_id = fork();
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
这时候查看进程:
(gdb) info inferiors
Num Description Executable
* 1 process 15607 /home/wanghe/test.exe
(gdb) n
[New process 15611]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
22 if(fork_id == 0) //子进程
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
(gdb) info inferiors
Num Description Executable
2 process 15611 /home/wanghe/test.exe
* 1 process 15607 /home/wanghe/test.exe
刚开始只有一个进程,执行fork()函数后变成了两个进程,接着查看线程,发现有两个线程,分别对应两个进程。
(gdb) info threads
Id Target Id Frame
2 Thread 0x7ffff7fe0740 (LWP 15611) "test.exe" 0x00007ffff78b70c2 in fork
() from /lib64/libc.so.6
* 1 Thread 0x7ffff7fe0740 (LWP 15607) "test.exe" main (argc=1,
argv=0x7fffffffe008) at test.c:22
继续执行
(gdb) n
33 sleep(1);
(gdb) n
34 printf("1.这里是父进程\n");
(gdb) n
1.这里是父进程
36 pthread_create(&id,NULL,main_compute,NULL);
已经打印出了1,接着主进程的主线程会开辟一个子线程。
(gdb) info threads
Id Target Id Frame
3 Thread 0x7ffff77f1700 (LWP 15718) "test.exe" 0x00007ffff78efff1 in clone
() from /lib64/libc.so.6
2 Thread 0x7ffff7fe0740 (LWP 15611) "test.exe" 0x00007ffff78b70c2 in fork
() from /lib64/libc.so.6
* 1 Thread 0x7ffff7fe0740 (LWP 15607) "test.exe" main (argc=1,
argv=0x7fffffffe008) at test.c:37
3号线程就是新的子线程,现在转到子线程执行,通过thread 3命令:
(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff77f1700 (LWP 15718))]
#0 0x00007ffff78efff1 in clone () from /lib64/libc.so.6
(gdb) n
Single stepping until exit from function clone,
which has no line number information.
0x00007ffff7bc6d10 in start_thread () from /lib64/libpthread.so.0
(gdb) n
Single stepping until exit from function start_thread,
which has no line number information.
2.父进程的子线程
[Thread 0x7ffff77f1700 (LWP 15718) exited]
No unwaited-for children left.
这时已经输出了2,接着转到2号进程,也就是子进程:
(gdb) inferior 2
[Switching to inferior 2 [process 15611] (/home/wanghe/test.exe)]
[Switching to thread 2 (Thread 0x7ffff7fe0740 (LWP 15611))]
#0 0x00007ffff78b70c2 in fork () from /lib64/libc.so.6
(gdb) n
Single stepping until exit from function fork,
which has no line number information.
main (argc=1, argv=0x7fffffffe008) at test.c:22
22 if(fork_id == 0) //子进程
(gdb) n
24 sleep(1);
(gdb) n
25 printf("3.这里是子进程\n");
(gdb) n
3.这里是子进程
27 pthread_create(&id,NULL,compute,NULL);
连续执行后可以看出输出了3。
输入n继续执行,这时候查看线程:
(gdb) info threads
Id Target Id Frame
4 Thread 0x7ffff77f1700 (LWP 15727) "test.exe" 0x00007ffff78efff1 in clone
() from /lib64/libc.so.6
* 2 Thread 0x7ffff7fe0740 (LWP 15611) "test.exe" main (argc=1,
argv=0x7fffffffe008) at test.c:28
1 Thread 0x7ffff7fe0740 (LWP 15607) "test.exe" main (argc=1,
argv=0x7fffffffe008) at test.c:39
4号线程就是子进程的主线程开辟的子线程,通过thread 4继续执行:
(gdb) n
Single stepping until exit from function clone,
which has no line number information.
0x00007ffff7bc6d10 in start_thread () from /lib64/libpthread.so.0
(gdb) n
Single stepping until exit from function start_thread,
which has no line number information.
4.子进程的子线程
[Thread 0x7ffff77f1700 (LWP 15727) exited]
No unwaited-for children left.
至此,1 2 3 4按照顺序输出完毕,是不是很有意思呢。