• 2006-01-08

    用GDB调试多进程程序 - [技术前沿]

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://bigwhite.blogbus.com/logs/1801699.html

    有一段时间没有写技术方面的东西了^_^。众所周知,GDB是Unix/Linux下调试程序的龙头老大,GDB功能强大,我们在平时多使用其一些最基本的功能,而且一般调试的都是单进程的程序。最近一个项目中的问题让我接触如何使用GDB调试多进程程序,更确切的是说调试调用fork的多进程程序。

    使用GDB最好的文档就是其名为'Debugging with GDB'的参考手册。手册中有一小章节提到了如何调试多进程程序。一般情况下,如果被gdb调试的程序中调用fork派生出一个新的子进程,这时gdb调试的仍然还是父进程,其子进程的执行不被理会。如果之前你在子进程的执行routine上设置了断点,那么当子进程执行到那个断点时,子进程会因为收到一个SIGTRAP信号而自行终止,除非你在子进程中拦截了该信号。

    那么使用GDB该如何调试多进程程序呢?在其参考手册中提供了一种通用方法,这里说说(GDB在某些平台上如HP-UX,还提供了更简便的方法,不过不具备通用性,这里不说):

    [测试程序]
    我们先看看我们的测试程序:
    /* in eg1.c */

    int wib(int no1, int no2)
    {
            int result, diff;
            diff = no1 - no2;
            result = no1 / diff;
            return result;
    }

    int main()
    {
            pid_t   pid;

            pid = fork();
            if (pid <0) {
                    printf("fork err\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上,调试该进程。

    [调试过程]
    我觉上面的调试原理的思路已经很清晰了,剩下的就是如何操作的问题了。我们来实践一次吧!
    我所使用的环境是Solaris OS 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.

    Breakpoint 1, 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.

    Breakpoint 1, 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.

    Breakpoint 1, 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

    至此,我们调试完毕。

    上面仅仅是一个简单的多进程程序,在我们平时开发的多进程程序远远比这个复杂,但是调试基本原理是不变,有一些技巧则需要我们在实践中慢慢摸索。


    收藏到:Del.icio.us




    引用地址: