来源 https://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/index.htmlhtml
GDB 是 linux 系统上经常使用的 c/c++ 调试工具,功能十分强大。对于较为复杂的系统,好比多进程系统,如何使用 GDB 调试呢?考虑下面这个三进程系统:linux
Proc2 是 Proc1 的子进程,Proc3 又是 Proc2 的子进程。如何使用 GDB 调试 proc2 或者 proc3 呢?c++
实际上,GDB 没有对多进程程序调试提供直接支持。例如,使用GDB调试某个进程,若是该进程fork了子进程,GDB会继续调试该进程,子进程会不受干扰地运行下去。若是你事先在子进程代码里设定了断点,子进程会收到SIGTRAP信号并终止。那么该如何调试子进程呢?其实咱们能够利用GDB的特色或者其余一些辅助手段来达到目的。此外,GDB 也在较新内核上加入一些多进程调试支持。shell
接下来咱们详细介绍几种方法,分别是 follow-fork-mode 方法,attach 子进程方法和 GDB wrapper 方法。安全
在2.5.60版Linux内核及之后,GDB对使用fork/vfork建立子进程的程序提供了follow-fork-mode选项来支持多进程调试。服务器
follow-fork-mode的用法为:oracle
set follow-fork-mode [parent|child]
app
所以若是须要调试子进程,在启动gdb后:函数
1
|
(gdb) set follow-fork-mode child
|
并在子进程代码设置断点。工具
此外还有detach-on-fork参数,指示GDB在fork以后是否断开(detach)某个进程的调试,或者都交由GDB控制:
set detach-on-fork [on|off]
注意,最好使用GDB 6.6或以上版本,若是你使用的是GDB6.4,就只有follow-fork-mode模式。
follow-fork-mode/detach-on-fork的使用仍是比较简单的,但因为其系统内核/gdb版本限制,咱们只能在符合要求的系统上才能使用。并且,因为follow-fork-mode的调试必然是从父进程开始的,对于fork屡次,以致于出现孙进程或曾孙进程的系统,例如上图3进程系统,调试起来并不方便。
众所周知,GDB有附着(attach)到正在运行的进程的功能,即attach <pid>命令。所以咱们能够利用该命令attach到子进程而后进行调试。
例如咱们要调试某个进程RIM_Oracle_Agent.9i,首先获得该进程的pid
1
2
3
|
[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的父进程。
1
|
[root@tivf09 root]# pstree -H 6722
|
启动GDB,attach到该进程
如今就能够调试了。一个新的问题是,子进程一直在运行,attach上去后都不知道运行到哪里了。有没有办法解决呢?
一个办法是,在要调试的子进程初始代码中,好比main函数开始处,加入一段特殊代码,使子进程在某个条件成立时便循环睡眠等待,attach到进程后在该代码段后设上断点,再把成立的条件取消,使代码能够继续执行下去。
至于这段代码所采用的条件,看你的偏好了。好比咱们能够检查一个指定的环境变量的值,或者检查一个特定的文件存不存在。以文件为例,其形式能够以下:
1
2
3
4
5
6
7
8
9
10
|
void debug_wait(char *tag_file)
{
while(1)
{
if (tag_file存在)
睡眠一段时间;
else
break;
}
}
|
当attach到进程后,在该段代码以后设上断点,再把该文件删除就OK了。固然你也能够采用其余的条件或形式,只要这个条件能够设置/检测便可。
Attach进程方法仍是很方便的,它可以应付各类各样复杂的进程系统,好比孙子/曾孙进程,好比守护进程(daemon process),惟一须要的就是加入一小段代码。
不少时候,父进程 fork 出子进程,子进程会紧接着调用 exec族函数来执行新的代码。对于这种状况,咱们也可使用gdb wrapper 方法。它的优势是不用添加额外代码。
其基本原理是以gdb调用待执行代码做为一个新的总体来被exec函数执行,使得待执行代码始终处于gdb的控制中,这样咱们天然可以调试该子进程代码。
仍是上面那个例子,RIM_Oracle_prog fork出子进程后将紧接着执行RIM_Oracle_Agent.9i的二进制代码文件。咱们将该文件重命名为RIM_Oracle_Agent.9i.binary,并新建一个名为RIM_Oracle_Agent.9i的shell脚本文件,其内容以下:
1
2
3
4
|
[root@tivf09 bin]# mv RIM_Oracle_Agent.9i RIM_Oracle_Agent.9i.binary
[root@tivf09 bin]# cat RIM_Oracle_Agent.9i
#!/bin/sh
gdb RIM_Oracle_Agent.binary
|
当fork的子进程执行名为RIM_Oracle_Agent.9i的文件时,gdb会被首先启动,使得要调试的代码处于gdb控制之下。
新的问题来了。子进程是在gdb的控制下了,但仍是不能调试:如何与gdb交互呢?咱们必须以某种方式启动gdb,以便能在某个窗口/终端与gdb交互。具体来讲,可使用xterm生成这个窗口。
xterm是X window系统下的模拟终端程序。好比咱们在Linux桌面环境GNOME中敲入xterm命令:
就会跳出一个终端窗口:
若是你是在一台远程linux服务器上调试,那么可使用VNC(Virtual Network Computing) viewer从本地机器链接到服务器上使用xterm。在此以前,须要在你的本地机器上安装VNC viewer,在服务器上安装并启动VNC server。大多数linux发行版都预装了vnc-server软件包,因此咱们能够直接运行vncserver命令。注意,第一次运行vncserver时会提示输入密码,用做VNC viewer从客户端链接时的密码。能够在VNC server机器上使用vncpasswd命令修改密码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
[root@tivf09 root]# vncserver
New 'tivf09:1 (root)' desktop is tivf09:1
Starting applications specified in /root/.vnc/xstartup
Log file is /root/.vnc/tivf09:1.log
[root@tivf09 root]#
[root@tivf09 root]# ps -ef|grep -i vnc
root 19609 1 0 Jun05 ? 00:08:46 Xvnc :1 -desktop tivf09:1 (root)
-httpd /usr/share/vnc/classes -auth /root/.Xauthority -geometry 1024x768
-depth 16 -rfbwait 30000 -rfbauth /root/.vnc/passwd -rfbport 5901 -pn
root 19627 1 0 Jun05 ? 00:00:00 vncconfig -iconic
root 12714 10599 0 01:23 pts/0 00:00:00 grep -i vnc
[root@tivf09 root]#
|
Vncserver是一个Perl脚本,用来启动Xvnc(X VNC server)。X client应用,好比xterm,VNC viewer都是和它通讯的。如上所示,咱们可使用的DISPLAY值为tivf09:1。如今就能够从本地机器使用VNC viewer链接过去:
输入密码:
登陆成功,界面和服务器本地桌面上同样:
下面咱们来修改RIM_Oracle_Agent.9i脚本,使它看起来像下面这样:
1
2
|
#!/bin/sh
export DISPLAY=tivf09:1.0; xterm -e gdb RIM_Oracle_Agent.binary
|
若是你的程序在exec的时候还传入了参数,能够改为:
1
2
|
#!/bin/sh
export DISPLAY=tivf09:1.0; xterm -e gdb --args RIM_Oracle_Agent.binary $@
|
最后加上执行权限
1
|
[root@tivf09 bin]# chmod 755 RIM_Oracle_Agent.9i
|
如今就能够调试了。运行启动子进程的程序:
1
2
3
4
5
6
7
8
9
10
11
12
|
[root@tivf09 root]# wrimtest -l 9i_linux
Resource Type : RIM
Resource Label : 9i_linux
Host Name : tivf09
User Name : mdstatus
Vendor : Oracle
Database : rim
Database Home : /data/oracle9i/920
Server ID : rim
Instance Home :
Instance Name :
Opening Regular Session...
|
程序停住了。从VNC viewer中能够看到,一个新的gdb xterm窗口在服务器端打开了
1
2
3
4
|
[root@tivf09 root]# ps -ef|grep gdb
nobody 24312 24311 0 04:30 ? 00:00:00 xterm -e gdb RIM_Oracle_Agent.binary
nobody 24314 24312 0 04:30 pts/2 00:00:00 gdb RIM_Oracle_Agent.binary
root 24326 10599 0 04:30 pts/0 00:00:00 grep gdb
|
运行的正是要调试的程序。设置好断点,开始调试吧!
注意,下面的错误通常是权限的问题,使用 xhost 命令来修改权限:
1
2
3
|
[root@tivf09 bin]# export DISPLAY=tivf09:1.0
[root@tivf09 bin]# xhost +
access control disabled, clients can connect from any host
|
xhost + 禁止了访问控制,从任何机器均可以链接过来。考虑到安全问题,你也可使用xhost + <你的机器名>。
上述三种方法各有特色和优劣,所以适应于不一样的场合和环境: