实际上,GDB 没有对多进程程序调试提供直接支持。例如,使用GDB调试某个进程,如果该进程fork了子进程,GDB会继续调试该进程,子进程会不受干扰地运行下去。如果你事先在子进程代码里设定了断点,子进程会收到SIGTRAP信号并终止。那么该如何调试子进程呢?其实我们可以利用GDB的特点或者其他一些辅助手段来达到目的。此外,GDB 也在较新内核上加入一些多进程调试支持。
本文介绍的方法能让你把断点设在子进程中,单步查看子进程的运行情况。但问题,如果我想在一次调试中同时在父子进程中设置断点,单步执行该怎么做呢?
1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 #include<sys/ipc.h> 6 #include<sys/shm.h> 7 #include<sys/types.h> 8 #include<fcntl.h> 9 #include<sys/stat.h> 10 11 main(){ 12 int pause=1; //让进程暂停,以便获得pid 13 char *arr; 14 int shmid; 15 if((shmid=shmget(2012,4,IPC_CREAT|0666))==-1){ //父进程中创建共享内存 16 perror("shmget"); 17 exit(1); 18 } 19 pid_t pid; 20 if((pid=fork())<0){ 21 perror("fork"); 22 exit(1); 23 } 24 if(pid==0){ 25 while(pause) sleep(1); 26 if((arr=shmat(shmid,NULL,0))==(void*)-1){ //子进程中把共享内存映射到本,进行修改 27 perror("shmat"); 28 exit(1); 29 } 30 arr[0]='a';arr[1]='b';arr[2]='c';arr[3]='\0'; 31 if(shmdt(arr)==-1){ 32 perror("shmdt"); 33 exit(1); 34 } 35 } 36 else{ 37 while(pause) sleep(1); 38 //sleep(1); 39 if((arr=shmat(shmid,NULL,0))==(void*)-1){ //子进程中把共享内存映射到本,读取其内容 40 perror("shmat"); 41 exit(1); 42 } 43 printf("%s\n",arr); 44 if(shmdt(arr)==-1){ 45 perror("shmdt"); 46 exit(1); 47 } 48 } 49 }
方法一:attach pid
ubuntu默认情况下你在gdb中使用attach id是权限不够的,所以你需要:sudo chmod +s /usr/bin/gdb
- 在父子进程中加一句while(pause) sleep(1);,让其暂停,然后后台运行程序,通过ps获取子进程的PID。(如下所示,ps显示的./fork有两个,orisun是进程的有效用户,第一个数字是进程ID,第二个数字是其父进程ID)
- 运行gdb,attach 子进程ID
orisun@zcypc:~$ ./fork & [1] 13294 orisun@zcypc:~$ ps -ef|grep fork 102 790 1 0 09:00 ? 00:00:01 dbus-daemon --system --fork --activation=upstart orisun 1546 1 0 09:00 ? 00:00:03 //bin/dbus-daemon --fork --print-pid 5 --print-address 7 --session orisun 12584 1 0 20:23 ? 00:00:12 gedit /home/orisun/fork.c orisun 13294 13239 0 20:53 pts/0 00:00:00 ./fork orisun 13295 13294 0 20:53 pts/0 00:00:00 ./fork orisun 13297 13239 0 20:54 pts/0 00:00:00 grep --color=auto fork orisun@zcypc:~$ gdb GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2 Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. (gdb) attach 13295 Attaching to process 13295 Reading symbols from /home/orisun/fork...done. Reading symbols from /lib/i386-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.13.so...done. done. Loaded symbols for /lib/i386-linux-gnu/libc.so.6 Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done. Loaded symbols for /lib/ld-linux.so.2 0x00bd8416 in __kernel_vsyscall () (gdb) backtrace #0 0x00bd8416 in __kernel_vsyscall () #1 0x0049bfc0 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:82 #2 0x0049bde2 in __sleep (seconds=<value optimized out>) at ../sysdeps/unix/sysv/linux/sleep.c:138 #3 0x08048595 in main () at fork.c:25 (gdb) up 3 #3 0x08048595 in main () at fork.c:25 25 while(pause) sleep(1); (gdb) list 20 if((pid=fork())<0){ 21 perror("fork"); 22 exit(1); 23 } 24 if(pid==0){ 25 while(pause) sleep(1); 26 if((arr=shmat(shmid,NULL,0))==(void*)-1){ //子进程中把共享内存映射到本,进行修改 27 perror("shmat"); 28 exit(1); 29 } (gdb) break 31 Breakpoint 1 at 0x8048600: file fork.c, line 31. (gdb) set pause=0 (gdb) continue Continuing. Breakpoint 1, main () at fork.c:31 31 if(shmdt(arr)==-1){ (gdb) p arr $1 = 0xb77fb000 "abc" (gdb)
方法二:follow-fork-mode
不需要专门加while(pause) sleep(1);这种代码了。
follow-fork-mode的用法为:
set follow-fork-mode [parent|child]
* parent: fork之后继续调试父进程,子进程不受影响。
* child: fork之后调试子进程,父进程不受影响。
因此如果需要调试子进程,在启动gdb后:
(gdb) set follow-fork-mode child
并在子进程代码设置断点。
此外还有detach-on-fork参数,指示GDB在fork之后是否断开(detach)某个进程的调试,或者都交由GDB控制:
set detach-on-fork [on|off]
* on: 断开调试follow-fork-mode指定的进程。
* off: gdb将控制父进程和子进程。follow-fork-mode指定的进程将被调试,另一个进程置于暂停(suspended)状态。
orisun@zcypc:~$ gdb ./fork GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2 Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/orisun/fork...done. (gdb) set follow-fork-mode child (gdb) set detach-on-fork off (gdb) break 31 Breakpoint 1 at 0x80485e3: file fork.c, line 31. (gdb) r Starting program: /home/orisun/fork [New process 13534] [Switching to process 13534] Breakpoint 1, main () at fork.c:31 31 if(shmdt(arr)==-1){ (gdb) p arr $1 = 0xb7ffd000 "abc" (gdb) c Continuing. Program exited normally. (gdb)
222222222222222222222222222222222222222222222222222222
222222222222222222222222222222222222222222222222222222
1. 默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序。只需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)即可。
child
parent
child
2. GDB默认支持调试多线程,跟主线程,子线程block在create thread。
例程:
- #include <stdio.h>
- #include <pthread.h>
- void processA();
- void processB();
- void * processAworker(void *arg);
- int main(int argc, const char *argv[])
- {
- int pid;
- pid = fork();
- if(pid != 0)
- processA();
- else
- processB();
- return 0;
- }
- void processA()
- {
- pid_t pid = getpid();
- char prefix[] = "ProcessA: ";
- char tprefix[] = "thread ";
- int tstatus;
- pthread_t pt;
- printf("%s%lu %s\n", prefix, pid, "step1");
- tstatus = pthread_create(&pt, NULL, processAworker, NULL);
- if( tstatus != 0 )
- {
- printf("ProcessA: Can not create new thread.");
- }
- processAworker(NULL);
- sleep(1);
- }
- void * processAworker(void *arg)
- {
- pid_t pid = getpid();
- pthread_t tid = pthread_self();
- char prefix[] = "ProcessA: ";
- char tprefix[] = "thread ";
- printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step2");
- printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step3");
- return NULL;
- }
- void processB()
- {
- pid_t pid = getpid();
- char prefix[] = "ProcessB: ";
- printf("%s%lu %s\n", prefix, pid, "step1");
- printf("%s%lu %s\n", prefix, pid, "step2");
- printf("%s%lu %s\n", prefix, pid, "step3");
- }
- [cnwuwil@centos c-lab]$ ./test
- ProcessA: 802 step1
- ProcessB: 803 step1
- ProcessB: 803 step2
- ProcessB: 803 step3
- ProcessA: 802 thread 3077555904 step2
- ProcessA: 802 thread 3077555904 step3
- ProcessA: 802 thread 3077553008 step2
- ProcessA: 802 thread 3077553008 step3
1. 调试主进程,block子进程。
- (gdb) set detach-on-fork off
- (gdb) show detach-on-fork
- Whether gdb will detach the child of a fork is off.
- (gdb) catch fork
- Catchpoint 1 (fork)
- (gdb) r
- [Thread debugging using libthread_db enabled]
- Catchpoint 1 (forked process 3475), 0x00110424 in __kernel_vsyscall ()
- Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.47.el6.i686
- (gdb) break test.c:14
- Breakpoint 2 at 0x8048546: file test.c, line 14.
- (gdb) cont
- [New process 3475]
- [Thread debugging using libthread_db enabled]
- Breakpoint 2, main (argc=1, argv=0xbffff364) at test.c:14
- Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.47.el6.i686
- (gdb) info inferiors
- Num Description Executable
- 2 process 3475 /home/cnwuwil/labs/c-lab/test
- * 1 process 3472 /home/cnwuwil/labs/c-lab/test
2. 切换到子进程:
- (gdb) inferior 2
- [Switching to inferior 2 [process 3475] (/home/cnwuwil/labs/c-lab/test)]
- [Switching to thread 2 (Thread 0xb7fe86c0 (LWP 3475))]
- #0 0x00110424 in ?? ()
- (gdb) info inferiors
- Num Description Executable
- * 2 process 3475 /home/cnwuwil/labs/c-lab/test
- 1 process 3472 /home/cnwuwil/labs/c-lab/test
- (gdb) inferior 1
- [Switching to inferior 1 [process 3472] (/home/cnwuwil/labs/c-lab/test)]
- [Switching to thread 1 (Thread 0xb7fe86c0 (LWP 3472))]
- #0 main (argc=1, argv=0xbffff364) at test.c:14
- (gdb) info inferiors
- Num Description Executable
- 2 process 3475 /home/cnwuwil/labs/c-lab/test
- * 1 process 3472 /home/cnwuwil/labs/c-lab/test
3. 设断点继续调试主进程,主进程产生两个子线程:
- (gdb) break test.c:50
- Breakpoint 3 at 0x804867d: file test.c, line 50. (2 locations)
- (gdb) cont
- ProcessA: 3472 step1
- [New Thread 0xb7fe7b70 (LWP 3562)]
- ProcessA: 3472 thread 3086911168 step2
- Breakpoint 3, processAworker (arg=0x0) at test.c:50
- (gdb) info inferiors
- Num Description Executable
- 2 process 3475 /home/cnwuwil/labs/c-lab/test
- * 1 process 3472 /home/cnwuwil/labs/c-lab/test
- (gdb) info threads
- 3 Thread 0xb7fe7b70 (LWP 3562) 0x00110424 in __kernel_vsyscall ()
- 2 Thread 0xb7fe86c0 (LWP 3475) 0x00110424 in ?? ()
- * 1 Thread 0xb7fe86c0 (LWP 3472) processAworker (arg=0x0) at test.c:50
4. 切换到主进程中的子线程,注意:线程2为前面产生的子进程
- (gdb) thread 3
- [Switching to thread 3 (Thread 0xb7fe7b70 (LWP 3562))]#0 0x00110424 in __kernel_vsyscall ()
- (gdb) cont
- ProcessA: 3472 thread 3086911168 step3
- ProcessA: 3472 thread 3086908272 step2
- [Switching to Thread 0xb7fe7b70 (LWP 3562)]
- Breakpoint 3, processAworker (arg=0x0) at test.c:50
- (gdb) info threads
- * 3 Thread 0xb7fe7b70 (LWP 3562) processAworker (arg=0x0) at test.c:50
- 2 Thread 0xb7fe86c0 (LWP 3475) 0x00110424 in ?? ()
- 1 Thread 0xb7fe86c0 (LWP 3472) 0x00110424 in __kernel_vsyscall ()
- (gdb) thread 1