技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 系统运维 --> 使用gdb调试运行时的程序小技巧

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

浏览:3727次  出处信息

   下面介绍我调试时经常遇到的三种问题,如果大家也有类似的问题交流一下解决方法:

   情景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

建议继续学习:

  1. 调试工具之GDB    (阅读:13588)
  2. gdb的基本工作原理是什么?    (阅读:10525)
  3. GDB中应该知道的几个调试方法    (阅读:5462)
  4. 使用GDB调试多进程程序    (阅读:4930)
  5. GDB常用指令说明    (阅读:3076)
  6. GDB的两个技巧    (阅读:2584)
  7. GDB 进行程序调试笔记    (阅读:2333)
  8. 更简单的重现PHP Core的调用栈    (阅读:2072)
  9. 用GDB排查Python程序故障    (阅读:1140)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1