gdb調試段錯誤及使用


在編程調試中,經常出現段錯誤,此時可用gdb調試。具體方法為注冊段錯誤信號處理函數,在處理函數中啟動gdb。
具體代碼如下:

void segv_handler(int no) 
{
char buf[512];
char cmd[512];
FILE *file;

snprintf(buf, sizeof(buf), "/proc/%d/cmdline", getpid());
if(!(file = fopen(buf, "r")))
{ 
exit(EXIT_FAILURE);
} 
if(!fgets(buf, sizeof(buf), file))
{ 
eixt(EXIT_FAILURE);
} 
if(buf[strlen(buf) - 1 ] == '\n')
{ 
buf[strlen(buf) -1] = '\0';
} 
snprintf(cmd, sizeof(cmd), "gdb %s %d", buf, getpid());
system(cmd);
}

注冊函數:

signal(SIGSEGV, segv_handler);

下面轉自一些總結:
作為一名程序猿,日常開發中解決各種bug是不可避免的。對於簡單的bug通過日志分析,或者增加打印信息就能很快定位到原因並解決。但是對於某些比較復雜的情況,想要定位到bug往往十分困難。查閱了很多資料,經過不斷嘗試,我發現gdb調試能夠起到很大的幫助。下面我將對使用gdb的一些常用技巧和實例做下總結。
下面總結下比較關鍵的幾個用法:
一、啟動GDB調試
使用gdb調試首先在編譯程序時加上-g參數:$ gcc –g –o foo foo.c
啟動gdb調試有多種方法,可以根據不同的場景選擇合適的方式,這也是gdb比較好用的地方。
1. 程序沒有運行時,gdb +<program> 直接用gdb運行程序;
2. 程序運行中的gdb調試有兩種方式:
a.ps查看程序的PID,gdb + <program> + PID ,自動掛接到已運行的程序;
b.ps產看程序的PID,gdb + <program>運行gdb后,用attach + PID指令掛接到程序, 並用detach來取消掛接的進程。
3. 程序已經死掉后,gdb +<program> + core文件進行調試,core文件是程序非法執行后產生的“核心轉儲”文件。有些情況下core不能生成,需要用ulimit -c unlimited指令先設置系統環境。
二、針對第二種gdb啟動方式,可以有如下實現方式。
程序發生段錯誤,但是該異常發生有一定的隨機性,為了捕獲異常並進行gdb調試,在程序中捕獲SIGSEGV信號並進行如下處理,這樣當程序運行出現段錯誤時直接進入gdb調試環境。

三、gdb調試常用指令

關於gdb調試的常用指令及介紹可以參考這里,http://blog.csdn.net/liwf616/article/details/46833107
Gdb環境下直接按下回車表示執行上一條命令
1. break 設置斷點,
break10 設置斷點,在源程序第10行
breakfunc 設置斷點,在func函數入口處
infobreak 查看斷點信息
2. run 運行程序,可簡寫為r
3. next 單步跟蹤,函數調用當作一條簡單語句執行,可簡寫為n
step 單步跟蹤,函數調進入被調用函數體內,可簡寫為s
stepi 或si單步跟蹤一條機器指令
nexti 或ni單步跟蹤一條機器指令
4. continue 繼續運行程序,可簡寫為c
5. print 打印變量、字符串、表達式等的值,可簡寫為p
p count 打印count的值
p cou1+cou2+cou3 打印表達式值
6. bt 查看函數堆棧
7. finish 退出函數
8. quit 退出GDB
9. shell 不退出GDB就使用shell命令
10. make <make-args>不退出GDB就重新編譯程序
11. set args指定運行時參數。(如:set args10 20 30 40 50)
showargs查看設置好的運行參數。
12. path <dir>設定程序的運行路徑。
showpaths 查看程序的運行路徑。
13. set environment varname [=value] 設置環境變量。如:setenv USER=hchen
showenvironment [varname] 查看環境變量。
14. 其他
cd<dir> 相當於shell的cd命令。
pwd 顯示當前的所在目錄。
infoterminal 顯示程序用到的終端模式。
tty指寫輸入輸出的終端設備。如:tty /dev/ttyb
until在一個循環體內單步跟蹤時,該命令運行程序到退出循環體。簡寫u
四、多線程調試
用gdb調試的好處是可以查看程序運行時的堆棧等信息,可以很直觀的發現問題。
info threads //顯示當前可調試的所有線程,每個線程前面有一個gdb為其分配的ID;
thread ID //切換到該ID對應的線程;
bt //顯示該線程的堆棧信息
thread apply all bt //顯示所有線程堆棧信息
break fun.c:123 thread ID //設置指定線程的斷點
set scheduler-locking off|on|step //off表示不鎖定任何線程,所有線程都執行;on只有當前線程執行;step在單步時除了next過一個函數外,只有當前線程執行

五、示例

當本目錄文件wang大小超過2*1024時可能出現段錯誤(超過很大時,小時短時間不會出現),會進入segv_handler(),但不會觸發gdb。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
#include <execinfo.h>

#define SYSLOG_LINE_BUFFER_SIZE  2*1024
#define BUFFER_SIZE  4*1024
#define FILEN "wang"

char bbuf[BUFFER_SIZE]={0};

void m_syslog_dbg(char *format, ...)
{
    va_list ptr;
    char buf[SYSLOG_LINE_BUFFER_SIZE] = {0};

    openlog("log", 0, LOG_DAEMON);
    // put log
    va_start(ptr, format);
    vsprintf(buf, format, ptr);
    va_end(ptr);
    syslog(LOG_DEBUG, "%s", buf);
    closelog();

    return;
}

void segv_handler(int no) 
{   
    printf("seg....\n");
#if 1
    void * array[10]; /* 25 層,太夠了 : ),你也可以自己設定個其他值 */
    char **strings;

    int nSize = backtrace(array, sizeof(array)/sizeof(array[0]));
for (int i=nSize-1; i>=0; i--){ /* 頭尾幾個地址不必輸出,看官要是好奇,輸出來看看就知道了 */

        /* 修正array使其指向正在執行的代碼 */ 
//      printf("SIGSEGV catched when running code at %x\n", (char*)array[i] - 1);
        printf("SIGSEGV catched when running code at %x\n", array[i]);

    }
#if 1
    strings = backtrace_symbols(array, nSize);
    printf("backtrace...%d\n", nSize);
    if(strings == NULL){
        printf("strings NULL\n");
        exit(EXIT_FAILURE);
    }

    for(int i=nSize-1; i>=0; i--){
        printf("%s\n", strings[i]);
    }

    free(strings);
#endif

#endif
#if 0
    char buf[128];
    char cmd[128];
    FILE *file;
    pid_t pid = getpid();   

    snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid);
    if((file = fopen(buf, "r")) == NULL){
        exit(EXIT_FAILURE);
    }
    if(fgets(buf, sizeof(buf), file) == NULL ){
        exit(EXIT_FAILURE);
    }

    if(buf[strlen(buf)-1] == '\n'){
        buf[strlen(buf)-1] = '\0';
    } 

    snprintf(cmd, sizeof(cmd), "/usr/bin/gdb %s %d", buf, pid);
    printf("receive signo %d-->%s\n", no, cmd);
//  system(cmd);
//  while(1);
#endif
}
int main(void )
{

    signal(SIGSEGV, segv_handler);

    struct stat st;
    int ret = 0;
    ret = stat(FILEN, &st);
    if(ret != 0){
        perror("stat");
        return -1;
    }

    int fd = open(FILEN, O_RDONLY);
    if(fd < 0){
        printf("open error\n");
        return -1;
    }


    //read(fd, bbuf, st.st_size);
    ret = read(fd, bbuf, sizeof(bbuf));
    printf("file size = %d, %d\n", ret, getpid());
    m_syslog_dbg("recevice [%s]\n", bbuf);

    printf("the end\n");
    while(1){
        sleep(1);
        printf("running...\n");
    }

    close(fd);
    return 0;
}

 

參考:

1. backtrace、backtrace_symbols、backtrace_symbols_fd-support for application self-debugging

2. linux/unix 段錯誤捕獲_轉

3. C/C++捕獲段錯誤,打印出錯的具體位置(精確到哪一行)_轉

4. gdb調試段錯誤及使用


免責聲明!

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



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