使用gdb调试运行时的程序小技巧

标签: 未分类 gdb pstack | 发表时间:2012-10-15 12:32 | 作者:士豪
出处:http://rdc.taobao.com/blog/cs

原创文章,欢迎转载。转载请注明:转载自淘宝核心系统团队博客,谢谢!
原文链接地址: 使用gdb调试运行时的程序小技巧

下面介绍我调试时经常遇到的三种问题,如果大家也有类似的问题交流一下解决方法:
情景1:在不中止程序服务的情况下,怎么调试正在运行时的程序
情景2:需要同时看几个变量的值或者批量查看多个core文件的堆栈信息怎么办
情景3:遇到需要查看、队列、链表、树、堆等数据结构里的变量怎么办

1. 情景1:在不中止程序服务的情况下,怎么调试正在运行时的程序
我们在生产环境或者测试环境,会遇到一些异常,我们需要知道程序中的变量或者内存的值来确定程序运行状态
之前听过@淘宝褚霸讲过用systemstap可以实现这种功能,但systamstap写起来复杂一些,
还有时候在低内核版本的操作系统上用stap之后,程序或者操作系统都有可能死掉。

看过多隆调试程序时用pstack(修改了pstack代码,用gdb实现的,详见http://blog.yufeng.info/archives/873)查看和修改一个正在
执行程序的全局变量,感觉很神奇,尝试用gdb实现这种功能:

保存下面代码到文件runstack.sh

    #!/bin/sh
    if test $# -ne 2; then
       echo "Usage: `basename $0 .sh` <process-id> cmd" 1>&2
       echo "For exampl: `basename $0 .sh` 1000 bt" 1>&2
       exit 1
    fi
    if test ! -r /proc/$1; then
       echo "Process $1 not found." 1>&2
       exit 1
    fi
    result=""
    GDB=${GDB:-/usr/bin/gdb}
    # Run GDB, strip out unwanted noise.
    result=`$GDB --quiet -nx /proc/$1/exe $1 <<EOF 2>&1
    $2
    EOF`
    echo "$result" | egrep -A 1000 -e "^\(gdb\)" | egrep -B 1000 -e "^\(gdb\)"
    

用于测试runstack.sh调试的c代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>

    typedef struct slist {
        struct slist *next;
        char         data[4096];
    } slist;

    slist input_list = {NULL, {'\0'}};
    int count = 0;

    static void stdin_read (int fd)
    {
        char buf[4096];
        int ret;

        memset(buf, 0, 4096);

        fprintf(stderr, "please input string:");

        while (ret = read(fd, buf, 4096)) {

            slist *node = calloc(1, sizeof(slist));
            memcpy(node->data, buf, ret);
            node->next = input_list.next;
            input_list.next = node;
            count ++;

            if (memcmp(buf, "quit", 4) == 0) {
                fprintf(stdout, "input quit:\n");
                return;
            }
            fprintf(stderr, "ret: %d, there is %d strings, current is %s\nplease input string:", ret, count, buf);
        }
    }

    int main()
    {
        fprintf(stderr, "main run!\n");

        stdin_read(STDIN_FILENO);

        slist *nlist;
        slist *list = input_list.next;
        while (list) {
            fprintf(stderr, "%s\n", list->data);
            nlist = list->next;
            free(list);
            list = nlist;
        }

        return 0;
    }
    

编译c代码:gcc -g -o read_input read_input.c
执行./read_input 我们开始使用runstack.sh来调试
使用方法:sh ./runstack.sh pid “command”

来试验一下:
[shihao@xxx]$ ps aux |grep read_input|grep -v grep
shihao 10933 0.0 0.0 3668 332 pts/4 S+ 09:41 0:00 ./read_input
10933是一个read_input程序的进程号

1)打印代码
sudo sh ./runstack.sh 10933 “list main”
结果
(gdb) 35 fprintf(stderr, “ret: %d, there is %d strings, current is %s\nplease input string:”, ret, count, buf);
36 }
37 }
38
39 int main()
40 {
41 fprintf(stderr, “main run!\n”);
42
43 stdin_read(STDIN_FILENO);
44
(gdb) quit

2)显示程序全局变量值
./runstack.sh 10933 “p count”
(gdb) $1 = 1
(gdb) quit

3)修改变量值
执行下面命令前
[shihao@tfs036097 gdb]$ runstack.sh 11190 “set count=100″
结果: (gdb) (gdb) quit

我们可以用上面命令看我们修改成功没有
[shihao@tfs036097 gdb]$ runstack.sh 11190 “p count”
(gdb) $1 = 100
(gdb) quit
全局变量count变成100了。

注:1)有一些程序经过操作系统优化过,直接用上面的方法可能有找不到符号表的情况

            result=`$GDB --quiet -nx /proc/$1/exe $1 <<EOF 2>&1
            $2
            EOF`
            

可以把上面的代码改成下面的试试,如果不行可能是其他原因

            BIN=`readlink -f /proc/$1/exe`
            result=`$GDB --quiet -nx $BIN $1 <<EOF 2>&1
            $2
            EOF`
            

2)需要有查看和修改运行的进程的权限
2. 情景2:需要同时看几个变量的值或者批量查看多个core文件的信息怎么办
1)多个变量的情景
我们同时看一下count和input_list里面的值和堆栈信息,我们可以写一个script.gdb
$ cat script.gdb

    p input_list
    p count
    bt
    f 1
    p buf
    

执行 runstack.sh 10933 “source script.gdb”
(gdb) $1 = {next = 0x597c020, data = ” }
$2 = 2
#0 0x0000003fa4ec5f00 in __read_nocancel () from /lib64/libc.so.6
#1 0x00000000004007c7 in stdin_read (fd=0) at read_input.c:23
#2 0×0000000000400803 in main () at read_input.c:43
#1 0x00000000004007c7 in stdin_read (fd=0) at read_input.c:23
23 while (ret = read(fd, buf, 4096)) {
$3 = “12345\n”, ”
(gdb) quit
这样就可以同时做多个操作
2)批处理查看core的情况
有的时候会出现很多core文件,我们想知道哪些core文件是因为相同的原因,哪些是不相同的,看一个两个的时候还比较轻松
$ ls core.*
core.12281 core.12282 core.12283 core.12284 core.12286 core.12287 core.12288 core.12311 core.12313 core.12314
像上面有很多core文件,一个一个用gdb去执行bt去看core在哪里有点麻烦,我们想有把所有的core文件的堆栈和变量信息打印出来
我对runstack稍作修改就可以实现我们的需求,我们起名叫corestack.sh

    #!/bin/sh

    if test $# -ne 3; then
        echo "Usage: `basename $0 .sh` program core cmd" 1>&2
        echo "For example: `basename $0 .sh` ./main core.1111 bt" 1>&2
        exit 1
    fi

    if test ! -r $1; then
        echo "Process $1 not found." 1>&2
        exit 1
    fi

    result=""
    GDB=${GDB:-/usr/bin/gdb}
    # Run GDB, strip out unwanted noise.
    result=`$GDB --quiet -nx $1 $2 <<EOF 2>&1
    $3
    EOF`
    echo "$result" | egrep -A 1000 -e "^\(gdb\)" | egrep -B 1000 -e "^\(gdb\)"
    

我们可以这样执行:
./corestack.sh ./read_input core.12281 “bt”
执行结果:
(gdb) #0 0x0000003fa4e30265 in raise (sig=)
at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x0000003fa4e31d10 in abort () at abort.c:88
#2 0x0000003fa4e296e6 in __assert_fail (assertion=,
file=, line=,
function=) at assert.c:78
#3 0x00000000004008ba in main () at read_input.c:55
(gdb) quit
查看多个core文件堆栈信息的准备工作差不多了,我们写个脚本就可以把所有的core文件堆栈打印出来了

执行以下:for i in `ls core.*`;do ./corestack.sh ./read_input $i “bt”; done
(gdb) #0 0x0000003fa4e30265 in raise (sig=)
at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x0000003fa4e31d10 in abort () at abort.c:88
#2 0x0000003fa4e296e6 in __assert_fail (assertion=,
file=, line=,
function=) at assert.c:78
#3 0x00000000004008ba in main () at read_input.c:55
(gdb) quit
……
(gdb) #0 0x0000003fa4e30265 in raise (sig=)
at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x0000003fa4e31d10 in abort () at abort.c:88
#2 0x0000003fa4e296e6 in __assert_fail (assertion=,
file=, line=,
function=) at assert.c:78
#3 0x00000000004008ba in main () at read_input.c:55
(gdb) quit
ok, 我们看到了所有core文件的堆栈。

3. 情景3:遇到需要查看、队列、链表、树、堆等数据结构里的变量怎么办?
下面介绍链表怎么处理,对其他数据结构感兴趣的同学可以自己尝试编写一些gdb脚本(麻烦@周哲士豪一下我,我也学习学习),
希望我们可以实现一个gdb调试工具箱

gdb是支持编写的脚本的 http://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html
我们写个plist.gdb,用while循环来遍历链表
$ cat plist.gdb

    set $list=&input_list

    while($list)
        p *$list
        set $list=$list->next
    end
    

我们执行一下:runstack.sh 13434 “source plist.gdb”
(gdb) $1 = {next = 0x3d61040, data = ” }
$2 = {next = 0x3d60030, data = “123456\n”, ” }
$3 = {next = 0x3d5f020, data = “12345\n”, ” }
$4 = {next = 0x3d5e010, data = “1234\n”, ” }
$5 = {next = 0×0, data = “123\n”, ” }
(gdb) quit

实际上我们可以把plist写成自定义函数,执行gdb的时候会在当前目下查找.gdbinit文件加载到gdb:
$ cat .gdbinit

    define plist

        set $list=$arg0

        while($list)
            p *$list
            set $list=$list->next
        end
    end
    

这样就可以用plist命令遍历list的值
$ runstack.sh 13434 “plist &input_list”
(gdb) $1 = {next = 0x3d61040, data = ” }
$2 = {next = 0x3d60030, data = “123456\n”, ” }
$3 = {next = 0x3d5f020, data = “12345\n”, ” }
$4 = {next = 0x3d5e010, data = “1234\n”, ” }
$5 = {next = 0×0, data = “123\n”, ” }
(gdb) quit

参考资料:
霸爷的博客:http://blog.yufeng.info/archives/873
gdb从脚本加载命令:http://blog.lifeibo.com/?p=380
gdb官方文档:http://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html

相关 [gdb 调试 运行时] 推荐:

使用gdb调试运行时的程序小技巧

- - 淘宝核心系统团队博客
转载请注明:转载自淘宝核心系统团队博客,谢谢. 原文链接地址: 使用gdb调试运行时的程序小技巧. 下面介绍我调试时经常遇到的三种问题,如果大家也有类似的问题交流一下解决方法:. 情景1:在不中止程序服务的情况下,怎么调试正在运行时的程序. 情景2:需要同时看几个变量的值或者批量查看多个core文件的堆栈信息怎么办.

gdb调试工具

- - CSDN博客系统运维推荐文章
查看帮助一是man 命令,二是进入 www.gnu.org,找到gdb的帮助文档(更详细). gcc -Wall -g main.c -o main,只有这样才能产生调试信息,包括core的调试信息.     run(r)  运行,执行到断点,重新用r,表示重新开始执行.     list(l)  列出源代码,l 2,l main,l 2,16(数字表示行数).

Linux下gdb调试

- - CSDN博客移动开发推荐文章
关于gdb的其他客套话不多说,直接进入正题. 列出产生执行文件源代码的一部分. 执行一行源代码但不进入函数内部. 执行一行源代码而且进入函数内部. 监视一个变量的值,一旦值有变化程序停住. 1.新建一个源文件vi yrp.cc,源代码如下:. 2.生成可执行文件 g++ -g -o yrp yrp.cc  注意必须使用-g参数,编译会加入调试信息,否则无法调试执行文件..

[转]GDB调试多线程

- - 小彰
GDB 多线程调试基本命令 实现简介 以及一个问题的解决. 一直对GDB多线程调试接触不多,最近因为工作有了一些接触,简单作点记录吧. 先介绍一下GDB多线程调试的基本命令. 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID. 切换当前调试的线程为指定ID的线程.

Linux下进行GDB调试

- - CSDN博客推荐文章
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具. 一般来说,GDB主要帮助自己完成下面四个方面的功能;. 1:启动你的程序,可以按照你的自定义的要求随心所欲的运行程序. 2:可以让被调试的程序在你所指定的调制的断点处停住(断点可以是条件表达式). 3:当程序被停住时,可以检查你的程序中发生的事.

GDB学习之道:GDB调试精粹及使用实例

- - CSDN博客系统运维推荐文章
要想运行准备调试的程序,可使用run命令,在它后面可以跟随发给该程序的任何参数,包括标准输入和标准输出说明符(<和>)和外壳通配符(*、. 如果你使用不带参数的run命令,gdb就再次使用你给予前一条run命令的参数,这是很有用的. 利用set args 命令就可以修改发送给程序的参数,而使用show args 命令就可以查看其缺省参数的列表.

使用 gdb 调试 Python 进程

- yinseny - python.cn(jobs, news)
有时我们会想调试一个正在运行的Python进程,或者一个Python进程的coredump. 例如现在遇到一个mod_wsgi的进程僵死了,不接受请求,想看看究竟是运行到哪行Python代码呢. 这时就需要祭出gdb这个神器了. 确认你的gdb版本是>=7,gdb从版本7开始支持对Python的debug.

用gdb+nm调试php c extension程序

- ndv - 淘宝核心系统团队博客
最近在写Beanstalkd的php c extension客户端程序,写程序离不开调试,下面把调试中碰到的问题和解决方法和大家分享一下. .so写好了是给php脚本调用的,如果php脚本执行崩掉了,.so也只能在进程中饮恨而终,这时候php脚本调试经常用的echo, print_r, var_dump都派不上用场了.

GDB调试精粹及使用实例

- - CSDN博客推荐文章
当出现EXE_BAD_ACCESS, SIGABRT 及其他Crash时可以尝试用:. 要想运行准备调试的程序,可使用run命令,在它后面可以跟随发给该程序的任何参数,包括标准输入和标准输出说明符(<和>)和外壳通配符(*、. 如果你使用不带参数的run命令,gdb就再次使用你给予前一条run命令的参数,这是很有用的.

[C++] gdb高级调试方法

- - CSDN博客推荐文章
1,启动gdb的时候自动执行脚本. 注意:your_script里面只能有gdb命令. 不过我们都知道gdb命令里面有个shell指令,所以实际上这里可以做任何事. 在gdb里面有个attach指令,可以调试运行中的进程. 结合上面的-x参数,实际上我们可以写一个脚本,自动的attach到我们关心的进程上面,然后自动的做很多事情:比如设置trace命令并且continue.