有时会遇到一种很特殊的调试需求,
对当前正在运行的其它进程进行调试(正是我今天遇到的情形)。这种情况有可能发生在那些无法直接在调试器中运行的进程身上,例如有的进程 只能在系统启动时运行。另外如果需要对进程产生的子进程进行调试的话,也只能采用这种方式。GDB可以对正在执行的程序进行调度,它允许开发人员中断程序 并查看其状态,之后还能让这个程序正常地继续执行。
GDB提供了两种方式来调试正在运行的进程:一种是在GDB命令行上指定进程的PID,另一种是在GDB中使用“attach”命令。例如,开发人员可以先启动debugme程序,让其开始等待用户的输入。示例如下:
接下去在另一个虚拟控制台中用下面的命令查出该进程对应的进程号:
得到进程的PID后,就可以使用GDB对其进行调试了:
在上面的输出信息中,以Attaching to program开始的行表明GDB已经成功地附加在PID为555的进程上了。另外一种连接到其它进程的方法是先用file命令加载调试时所需的符号表,然后再通过“attaché”命令进行连接:
如果想知道程序现在运行到了哪里,同样可以使用“backtrace”命令。当然也可以使用“step”命令对程序进行单步调试。 在完成调试之后,不要忘记用detach命令断开连接,让被调试的进程可以继续正常运行。 |
面临问题:一般情况下,如果被gdb调试的程序中调用fork派生出一个新的子进程,这时gdb调试的仍然还是父进程,其子进程的执行不被理会。如果之前你在子进程的执行routine上设置了断点,那么当子进程执行到那个断点时,子进程会因为收到一个SIGTRAP信号而自行终止,除非你在子进程中拦截了该信号。
那么使用GDB该如何调试多进程程序呢?
测试程序]
我们先看看我们的测试程序:
gcc eg1.c -o eg1 -g
/* in eg1.c */
intwib(int no1, int no2)
{
int result, diff;
diff = no1 - no2;
result = no1 / diff;
return result;
}
intmain()
{
pid_t pid;
pid = fork();
if (pid <0) {
printf("forkerr/n");
exit(-1);
} else if (pid == 0) {
/* in child process */
sleep(60);------------------ (!)
int value = 10;
int div = 6;
int total = 0;
int i = 0;
int result = 0;
for (i = 0; i < 10; i++) {
result =wib(value, div);
total += result;
div++;
value--;
}
printf("%d wibed by %d equals%d/n", value, div, total);
exit(0);
} else {
/* in parent process */
sleep(4);
wait(-1);
exit(0);
}
}
该测试程序中子进程运行过程中会在wib函数中出现一个'除0'异常。现在我们就要调试该子进程。
[调试原理]
不知道大家发现没有,在(!)处在我们的测试程序在父进程fork后,子进程调用sleep睡了60秒。这就是关键,这个sleep本来是不该存在于子进程代码中的,而是而了使用GDB调试后加入的,它是我们调试的一个关键点。为什么要让子进程刚刚运行就开始sleep呢?因为我们要在子进程睡眠期间,利用shell命令获取其process id,然后再利用gdb调试外部进程的方法attach到该process id上,调试该进程。
[调试过程]
我觉上面的调试原理的思路已经很清晰了,剩下的就是如何操作的问题了。我们来实践一次吧!
我所使用的环境是SolarisOS 9.0/GCC 3.2/GDB 6.1。
GDB调试程序的前提条件就是你编译程序时必须加入调试符号信息,即使用'-g'编译选项。首先编译我们的源程序'gcc -g -o eg1 eg1.c'。编译好之后,我们就有了我们的调试目标eg1。由于我们在调试过程中需要多个工具配合,所以你最好多打开几个终端窗口,另外一点需要注意的是最好在eg1的working directory下执行gdb程序,否则gdb回提示'No symbol table is loaded'。你还得手工load symbol table。好了,下面我们就'按部就班'的开始调试我们的eg1。
执行eg1:
eg1 & --- 让eg1后台运行吧。
查找进程id:
ps -fu YOUR_USER_NAME
运行gdb:
gdb
(gdb) attach xxxxx --- xxxxx为利用ps命令获得的子进程process id
(gdb)stop---这点很重要,你需要先暂停那个子进程,然后设置一些断点和一些Watch
(gdb) break 37 --在result =wib(value, div);这行设置一个断点,可以使用list命令察看源代码
Breakpoint 1 at 0x10808: file eg1.c, line 37.
(gdb) continue 继续执行程序,直至下一中断或者程序结束
Continuing.
Breakpoint1, main () at eg1.c:37
37 result =wib(value, div);
(gdb) step 执行一行源代码而且进入函数内部
wib (no1=10, no2=6) at eg1.c:13
13 diff = no1 - no2;
(gdb) continue
Continuing.
Breakpoint1, main () at eg1.c:37
37 result =wib(value, div);
(gdb) step
wib (no1=9, no2=7) at eg1.c:13
13 diff = no1 - no2;
(gdb) continue
Continuing.
Breakpoint1, main () at eg1.c:37
37 result =wib(value, div);
(gdb) step
wib (no1=8, no2=8) at eg1.c:13
13 diff = no1 - no2;
(gdb) next
14 result = no1 / diff;
(gdb) print diff
$6 = 0 ------- 除数为0,我们找到罪魁祸首了。
(gdb) next
Program received signal SIGFPE, Arithmetic exception.
0xff29d830 in .div() from /usr/lib/libc.so.1
至此,我们调试完毕。
上面仅仅是一个简单的多进程程序,在我们平时开发的多进程程序远远比这个复杂,但是调试基本原理是不变,有一些技巧则需要我们在实践中慢慢摸索
实战:
root@ubuntu:/home/ygm/tmp# ./eg1 &
[1] 2597
root@ubuntu:/home/ygm/tmp# ps -ef | grep eg1
root 2597 2167 0 06:09 pts/0 00:00:00 ./eg1
root 2598 2597 0 06:09 pts/0 00:00:00 ./eg1
root 2600 2167 0 06:09 pts/0 00:00:00 grep --color=auto eg1
root@ubuntu:/home/ygm/tmp# gdb
GNU gdb (Ubuntu/Linaro 7.4-2012.02-0ubuntu2) 7.4-2012.02
Copyright (C) 2012 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 "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>.
(gdb) attach 2598 (将GDB附加在PID为2598 的进程上)
Attaching to process 2598
Reading symbols from /home/ygm/tmp/eg1...done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/x86_64-linux-gnu/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00007f305d47fda0 in nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) stop (让进程暂停)
(gdb) l
5 diff = no1 - no2;
6 result = no1 / diff;
7 return result;
8 }
9
10 int main()
11 {
12 pid_t pid;
13
14 pid = fork();
(gdb) l
15 if (pid <0) {
16 printf("forkerr/n");
17 exit(-1);
18 } else if (pid == 0) {
19 /* in child process */
20 sleep(60);
21
22 int value = 10;
23 int div = 6;
24 int total = 0;
(gdb) l
25 int i = 0;
26 int result = 0;
27
28 for (i = 0; i < 10; i++) {
29 result =wib(value, div);
30 total += result;
31 div++;
32 value--;
33 }
34
(gdb) b 29
Breakpoint 1 at 0x4006af: file eg1.c, line 29.
(gdb) continue
Continuing.
Breakpoint 1, main () at eg1.c:29
29 result =wib(value, div);
(gdb) step (进入函数中执行下调命令)
wib (no1=10, no2=6) at eg1.c:5
5 diff = no1 - no2;
(gdb) continue
Continuing.
Breakpoint 1, main () at eg1.c:29
29 result =wib(value, div);
(gdb) step
wib (no1=9, no2=7) at eg1.c:5
5 diff = no1 - no2;
(gdb) continue
Continuing.
Breakpoint 1, main () at eg1.c:29
29 result =wib(value, div);
(gdb) step
wib (no1=8, no2=8) at eg1.c:5
5 diff = no1 - no2;
(gdb) next
6 result = no1 / diff;
(gdb) p diff
$1 = 0
(gdb)