GDB子进程

本文介绍使用GDB调试多进程程序的方法,包括follow-fork-mode、attach子进程和GDBwrapper等技巧。适用于需要深入理解GDB多进程调试特性的开发者。

在子进程中sleep,然后attach上去。
gdb --pid=123456
ps出子进程的id,gdb attach 进程号.
http://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/index.html
实际上,GDB 没有对多进程程序调试提供直接支持。例如,使用GDB调试某个进程,如果该进程fork了子进程,GDB会继续调试该进程,子进程会不受干扰地运行下去。如果你事先在子进程代码里设定了断点,子进程会收到SIGTRAP信号并终止。那么该如何调试子进程呢?其实我们可以利用GDB的特点或者其他一些辅助手段来达到目的。此外,GDB 也在较新内核上加入一些多进程调试支持。

接下来我们详细介绍几种方法,分别是 follow-fork-mode 方法,attach 子进程方法和 GDB wrapper 方法。

follow-fork-mode

在2.5.60版Linux内核及以后,GDB对使用fork/vfork创建子进程的程序提供了follow-fork-mode选项来支持多进程调试。

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)状态。
注意,最好使用GDB 6.6或以上版本,如果你使用的是GDB6.4,就只有follow-fork-mode模式。

follow-fork-mode/detach-on-fork的使用还是比较简单的,但由于其系统内核/gdb版本限制,我们只能在符合要求的系统上才能使用。而且,由于follow-fork-mode的调试必然是从父进程开始的,对于fork多次,以至于出现孙进程或曾孙进程的系统,例如上图3进程系统,调试起来并不方便。

Attach子进程

众所周知,GDB有附着(attach)到正在运行的进程的功能,即attach <pid>命令。因此我们可以利用该命令attach到子进程然后进行调试。

例如我们要调试某个进程RIM_Oracle_Agent.9i,首先得到该进程的pid

[root@tivf09 tianq]# ps -ef|grep RIM_Oracle_Agent.9i
nobody    6722  6721  0 05:57 ?        00:00:00 RIM_Oracle_Agent.9i
root      7541 27816  0 06:10 pts/3    00:00:00 grep -i rim_oracle_agent.9i


通过pstree可以看到,这是一个三进程系统,oserv是RIM_Oracle_prog的父进程,RIM_Oracle_prog又是RIM_Oracle_Agent.9i的父进程。

[root@tivf09 root]# pstree -H 6722
现在就可以调试了。一个新的问题是,子进程一直在运行,attach上去后都不知道运行到哪里了。有没有办法解决呢?

一个办法是,在要调试的子进程初始代码中,比如main函数开始处,加入一段特殊代码,使子进程在某个条件成立时便循环睡眠等待,attach到进程后在该代码段后设上断点,再把成立的条件取消,使代码可以继续执行下去。

至于这段代码所采用的条件,看你的偏好了。比如我们可以检查一个指定的环境变量的值,或者检查一个特定的文件存不存在。以文件为例,其形式可以如下:

void debug_wait(char *tag_file)
{
    while(1)
    {
        if (tag_file存在)
            睡眠一段时间;
        else
            break;
    }
}


当attach到进程后,在该段代码之后设上断点,再把该文件删除就OK了。当然你也可以采用其他的条件或形式,只要这个条件可以设置/检测即可。

Attach进程方法还是很方便的,它能够应付各种各样复杂的进程系统,比如孙子/曾孙进程,比如守护进程(daemon process),唯一需要的就是加入一小段代码。

 

### 如何使用GDB调试Linux进程 #### 编译带有调试信息的程序 为了使 GDB 能够提供详细的调试信息,在编译 C/C++ 程序时应加入 `-g` 参数,这会使得编译器在目标文件中嵌入额外的调试数据。例如: ```bash gcc -g test.c -o test ``` 此命令将源码 `test.c` 编译成可执行文件 `test` 并包含完整的调试支持[^3]。 #### 启动GDB并加载待调试程序 通过指定要调试的应用名称来启动 GDB。如果希望立即开始调试,则可以在启动 GDB 的时候就附带应用名及其参数列表;如果不急于立刻运行被测对象,也可以先进入交互模式再做进一步操作: ```bash gdb ./test ``` 一旦进入 GDB 控制台之后就可以利用各种指令来进行断点设置、变量查看等动作了。 #### 设置断点与单步执行 当准备完毕后可以考虑在哪里暂停下来仔细观察状态变化情况——即设定断点位置。假设想要让程序停在一个特定函数处等待人工干预的话,那么只需输入如下所示语句即可完成相应配置工作: ```bash break main ``` 上述代码会在名为main()的地方建立一个停止点以便后续分析之用。另外还有一种方式叫做“条件性中断”,它允许开发者定义某些逻辑表达式作为触发依据从而实现更加精准化的控制效果。 对于简单的逐步跟踪需求来说,“step” 或者 “next” 命令将会非常有用。“step” 可以深入到子调用内部继续探索下去而不会跳过任何细节部分;相反地,“next” 则只会停留在当前层面上按顺序依次走过每一行有效代码直到遇到返回为止[^2]。 #### 处理多进程环境下的特殊情况 针对存在父子关系链路结构复杂场景中的问题排查任务而言,GDB 提供了一些专门用于管理此类状况的功能特性。比如可以通过下面这条指令获取所有已知活动单元编号清单: ```bash info inferiors ``` 紧接着便能借助于切换上下文的方法快速定位至感兴趣的那一个实例上去开展下一步的研究计划: ```bash inferior N ``` 这里N代表的就是之前查询所得结果集中某一行所对应的ID号。除此之外还有诸如attach/detach之类的高级技巧可供选择,它们分别对应着主动连接外部正在独立运作的目标实体以及解除关联的动作描述[^1]。 #### 实际案例演练 考虑到理论知识总是显得有些抽象难懂,不妨给出一段具体的Python脚本例子来做更直观的感受吧! 设想有一个简单父-子双线程架构的小工具需要我们去探究其行为特征... ```python import os print(f"Parent PID {os.getpid()}") def child(): pid = os.fork() if not pid: print(f'Child process, PID={os.getpid()}, PPID={os.getppid()}') child() while True: pass ``` 现在按照前面介绍过的流程一步步走下去就能轻松掌握整个过程啦!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值