多进程调试

实际上,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

  1. 在父子进程中加一句while(pause) sleep(1);,让其暂停,然后后台运行程序,通过ps获取子进程的PID。(如下所示,ps显示的./fork有两个,orisun是进程的有效用户,第一个数字是进程ID,第二个数字是其父进程ID)
  2. 运行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)即可。

           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]

   查询正在调试的进程:info inferiors
   切换调试的进程: inferior <infer number>
   添加新的调试进程: add-inferior [-copies n] [-exec executable] ,可以用file executable来分配给inferior可执行文件。
   其他:remove-inferiors infno, detach inferior

2. GDB默认支持调试多线程,跟主线程,子线程block在create thread。
   查询线程:info threads
   切换调试线程:thread <thread number>

例程:

  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3.   
  4. void processA();  
  5. void processB();  
  6. void * processAworker(void *arg);  
  7.   
  8. int main(int argc, const char *argv[])  
  9. {  
  10.     int pid;  
  11.     pid = fork();  
  12.     if(pid != 0)  
  13.         processA();  
  14.     else  
  15.         processB();  
  16.     return 0;  
  17. }  
  18.   
  19. void processA()  
  20. {  
  21.     pid_t pid = getpid();  
  22.     char prefix[] = "ProcessA: ";  
  23.     char tprefix[] = "thread ";  
  24.     int tstatus;  
  25.     pthread_t pt;  
  26.   
  27.     printf("%s%lu %s\n", prefix, pid, "step1");  
  28.   
  29.     tstatus = pthread_create(&pt, NULL, processAworker, NULL);  
  30.     if( tstatus != 0 )  
  31.     {  
  32.         printf("ProcessA: Can not create new thread.");  
  33.     }  
  34.    
  35.     processAworker(NULL);  
  36.     sleep(1);  
  37. }  
  38.   
  39. void * processAworker(void *arg)  
  40. {  
  41.     pid_t pid = getpid();  
  42.     pthread_t tid = pthread_self();  
  43.     char prefix[] = "ProcessA: ";  
  44.     char tprefix[] = "thread ";  
  45.   
  46.     printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step2");  
  47.     printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step3");  
  48.   
  49.     return NULL;  
  50. }  
  51.   
  52. void processB()  
  53. {  
  54.     pid_t pid = getpid();  
  55.     char prefix[] = "ProcessB: ";  
  56.     printf("%s%lu %s\n", prefix, pid, "step1");  
  57.     printf("%s%lu %s\n", prefix, pid, "step2");  
  58.     printf("%s%lu %s\n", prefix, pid, "step3");  
  59. }  
输出:

[plain] view plain copy
  1. [cnwuwil@centos c-lab]$ ./test  
  2. ProcessA: 802 step1  
  3. ProcessB: 803 step1  
  4. ProcessB: 803 step2  
  5. ProcessB: 803 step3  
  6. ProcessA: 802 thread 3077555904 step2  
  7. ProcessA: 802 thread 3077555904 step3  
  8. ProcessA: 802 thread 3077553008 step2  
  9. ProcessA: 802 thread 3077553008 step3  
调试:
1. 调试主进程,block子进程。

[plain] view plain copy
  1. (gdb) set detach-on-fork off  
  2. (gdb) show detach-on-fork  
  3. Whether gdb will detach the child of a fork is off.  
  4. (gdb) catch fork  
  5. Catchpoint 1 (fork)  
  6. (gdb) r  
  7. [Thread debugging using libthread_db enabled]  
  8.   
  9. Catchpoint 1 (forked process 3475), 0x00110424 in __kernel_vsyscall ()  
  10. Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.47.el6.i686  
  11. (gdb) break test.c:14  
  12. Breakpoint 2 at 0x8048546: file test.c, line 14.  
  13. (gdb) cont  
  14. [New process 3475]  
  15. [Thread debugging using libthread_db enabled]  
  16.   
  17. Breakpoint 2, main (argc=1, argv=0xbffff364) at test.c:14  
  18. Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.47.el6.i686  
  19. (gdb) info inferiors  
  20.   Num  Description       Executable         
  21.   2    process 3475      /home/cnwuwil/labs/c-lab/test  
  22. * 1    process 3472      /home/cnwuwil/labs/c-lab/test  

2. 切换到子进程:

[plain] view plain copy
  1. (gdb) inferior 2  
  2. [Switching to inferior 2 [process 3475] (/home/cnwuwil/labs/c-lab/test)]  
  3. [Switching to thread 2 (Thread 0xb7fe86c0 (LWP 3475))]  
  4. #0  0x00110424 in ?? ()  
  5. (gdb) info inferiors  
  6.   Num  Description       Executable         
  7. * 2    process 3475      /home/cnwuwil/labs/c-lab/test  
  8.   1    process 3472      /home/cnwuwil/labs/c-lab/test  
  9. (gdb) inferior 1  
  10. [Switching to inferior 1 [process 3472] (/home/cnwuwil/labs/c-lab/test)]  
  11. [Switching to thread 1 (Thread 0xb7fe86c0 (LWP 3472))]  
  12. #0  main (argc=1, argv=0xbffff364) at test.c:14  
  13. (gdb) info inferiors  
  14.   Num  Description       Executable         
  15.   2    process 3475      /home/cnwuwil/labs/c-lab/test  
  16. * 1    process 3472      /home/cnwuwil/labs/c-lab/test  

3. 设断点继续调试主进程,主进程产生两个子线程:

[plain] view plain copy
  1. (gdb) break test.c:50  
  2. Breakpoint 3 at 0x804867d: file test.c, line 50. (2 locations)  
  3. (gdb) cont  
  4. ProcessA: 3472 step1  
  5. [New Thread 0xb7fe7b70 (LWP 3562)]  
  6. ProcessA: 3472 thread 3086911168 step2  
  7.   
  8. Breakpoint 3, processAworker (arg=0x0) at test.c:50  
  9. (gdb) info inferiors  
  10.   Num  Description       Executable         
  11.   2    process 3475      /home/cnwuwil/labs/c-lab/test  
  12. * 1    process 3472      /home/cnwuwil/labs/c-lab/test  
  13. (gdb) info threads  
  14.   3 Thread 0xb7fe7b70 (LWP 3562)  0x00110424 in __kernel_vsyscall ()  
  15.   2 Thread 0xb7fe86c0 (LWP 3475)  0x00110424 in ?? ()  
  16. * 1 Thread 0xb7fe86c0 (LWP 3472)  processAworker (arg=0x0) at test.c:50  

4. 切换到主进程中的子线程,注意:线程2为前面产生的子进程

[plain] view plain copy
  1. (gdb) thread 3  
  2. [Switching to thread 3 (Thread 0xb7fe7b70 (LWP 3562))]#0  0x00110424 in __kernel_vsyscall ()  
  3. (gdb) cont  
  4. ProcessA: 3472 thread 3086911168 step3  
  5. ProcessA: 3472 thread 3086908272 step2  
  6. [Switching to Thread 0xb7fe7b70 (LWP 3562)]  
  7.   
  8. Breakpoint 3, processAworker (arg=0x0) at test.c:50  
  9. (gdb) info threads  
  10. * 3 Thread 0xb7fe7b70 (LWP 3562)  processAworker (arg=0x0) at test.c:50  
  11.   2 Thread 0xb7fe86c0 (LWP 3475)  0x00110424 in ?? ()  
  12.   1 Thread 0xb7fe86c0 (LWP 3472)  0x00110424 in __kernel_vsyscall ()  
  13. (gdb) thread 1 






















<think>好的,我现在需要帮用户解决在PyCharm中进行多进程调试的问题。首先,用户提到的是多进程调试,而他们之前可能已经了解过远程调试和多线程调试,这在引用[1]和[2]里提到过。不过多进程和多线程不同,多进程涉及到多个独立的Python解释器实例,所以调试起来更复杂。 首先,我得回忆一下PyCharm的调试功能是否原生支持多进程。根据引用[3],用户在使用DataLoader时遇到卡死,可能是因为多进程的问题,比如num_workers设置大于0。这说明PyCharm在默认情况下可能无法正确处理多进程的子进程调试。 接下来,我需要整理可能的解决方法。根据经验,通常有以下几种方式: 1. **子进程调试配置**:在PyCharm中手动为每个子进程配置调试器。这需要用户为每个子进程设置远程调试或附加到进程,但步骤比较繁琐,特别是当子进程动态生成时。 2. **使用远程调试模式**:像引用[1]提到的远程调试方法,可能需要在每个子进程中启动调试服务器,然后PyCharm连接到这些服务器。不过配置起来比较复杂,尤其是需要多个端口。 3. **代码嵌入调试器**:在代码中添加调试器启动的代码,比如使用pydevd库,这样每个子进程启动时会自动连接调试器。这种方法可能更灵活,但需要用户修改代码。 然后,我需要检查引用内容是否有相关信息。引用[3]提到在DataLoader中使用num_workers>0会导致问题,解决方法可能涉及设置环境变量或调整参数,比如将num_workers设为0,但这只是规避问题,而不是真正的调试方法。用户需要的是如何在多进程开启的情况下进行调试。 接下来,我需要将解决方法分步骤整理出来。首先,可能的基础方法是配置子进程的调试,但需要用户手动操作。然后,更有效的方法可能是使用代码嵌入的方式,例如使用pydevd库,在每个子进程中启动调试连接。 另外,需要注意PyCharm版本是否支持多进程调试,或者是否有插件可用。但根据现有信息,可能PyCharm本身没有直接的多进程调试支持,需要借助第三方库或配置多个调试会话。 最后,还需要考虑用户的实际使用场景,比如他们是否在使用PyTorch的DataLoader,或者其他多进程库,不同的情况可能需要不同的处理方式。例如,在PyTorch中,设置num_workers=0可以避免多进程问题,但用户可能需要在保持多进程的情况下调试,这时候就需要更复杂的配置。 总结下来,步骤可能包括: 1. 配置子进程调试:为每个子进程设置运行配置,手动附加调试器。 2. 使用远程调试:在每个子进程中启动调试服务器,配置PyCharm连接。 3. 代码嵌入调试器:添加pydevd代码,使子进程自动连接调试器。 4. 注意事项:比如环境变量设置、版本兼容性等。 需要确保每一步都清晰,并且用户能够按照步骤操作。同时,要提醒用户可能的复杂性和替代方案,比如暂时关闭多进程进行调试。</think>在PyCharm中进行多进程调试需特殊配置,以下是分步操作方法: ### 一、配置基础调试环境 1. **启用子进程调试支持** - 打开PyCharm设置,进入`Build, Execution, Deployment > Python Debugger` - 勾选`Gevent compatible`和`Attach to subprocess automatically while debugging`[^3] - 此配置可使调试器尝试附加到新建子进程 ### 二、多进程调试具体实现方法 2. **手动附加调试器** ```python import pydevd_pycharm def worker(): pydevd_pycharm.settrace('localhost', port=12345, stdoutToServer=True, stderrToServer=True) # 子进程主动连接调试器 # 子进程代码... ``` - 父进程配置常规调试配置 - 子进程配置独立调试会话(需创建多个调试配置) 3. **远程调试模式** - 在子进程代码中插入远程调试启动代码: ```python if __name__ == '__main__': import pydevd pydevd.settrace('host.docker.internal', port=57891, suspend=False) ``` - 需为每个子进程配置不同的调试端口 ### 三、PyTorch特殊场景调试 4. **DataLoader多进程调试** ```python import os os.environ['CUDA_LAUNCH_BLOCKING'] = "1" # 禁用CUDA异步操作 dataloader = DataLoader(..., num_workers=0) # 临时关闭多进程 ``` - 调试完成后恢复原参数 ### 四、调试操作流程 1. 主进程设置断点并启动调试 2. 当子进程启动时,PyCharm会弹出新调试窗口 3. 通过`Debugger > Threads & Variables`切换不同进程上下文
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值