gdb調試運行時的程序小技巧


使用gdb調試運行時的程序小技巧

標簽:  未分類  gdb  pstack | 發表時間:2012-10-15 04: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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM