1. 程式人生 > >Linux基礎 30分鐘GDB除錯快速突破

Linux基礎 30分鐘GDB除錯快速突破

引言 Linus心靈雞湯

  在*nix開發中有道卡叫gdb除錯,不管你怎麼搞. 它依然在那絲毫不會鬆動.今天致敬一個 活著的傳奇 Linus Torvalds

 

  Unix 始於上個世紀60年代,在70年代得到了迅猛的發展,

這時候的李納斯還躺在祖父公寓的搖籃裡睡大覺,如果不是後來 Unix 王國自亂陣腳,

出現陣營分裂和法律糾紛,可能 Linux 系統根本都不會出現。真實的情況是,

Unix 浪費了大把的時間和機會,似乎就是為了等待這個大鼻子、頭髮紛亂的芬蘭小子長大,然後一決高下。

  李納斯贏得了自己的時間,他一刻不停的磨練自己的技藝,在清晨的微光中練習演算法,

在赫爾辛基的雪山上編譯程式碼,隨時隨地補充的糧草和武器。

二十一年之後,李納斯撫著雪亮的刀鋒上路了,他要去追尋屬於程式設計師的最高榮耀。[

  I simply know better than you, that's why I'm your god.

                          - -  Linus Torvalds

]

前言  gdb 開始除錯開始上手

1. 開啟core, 採集程式崩潰的狀態

  首先你跟著我做開啟core崩潰狀態採集. 可以通過 ulimit -c 檢視 如果是0表示沒有開啟. 開啟按照下面操作

複製程式碼

su root

vi /etc/profile
Shift + G
i
# No core files by default 0, unlimited is oo
ulimit -S -c unlimited > /dev/null 2>&1
wq!

source /etc/profile

複製程式碼

上面shell 操作是 在 /etc/profile 最後一行新增 上面設定全域性開啟 core檔案除錯,大小不限. 最後 立即生效.

再跟著我做, 因為生成的core檔案同名會覆蓋. 這裡為其加上一個 core命名規則, 讓其變成 [core.pid] 格式.

複製程式碼

su root

vi /etc/sysctl.conf
Shift + G
i

# open, add core.pid 
kernel.core_pattern = ./core_%t_%p_%e
kernel.core_uses_pid = 1

wq!

sysctl -p /etc/sysctl.conf

複製程式碼

在 /etc/sysctl.conf 檔案中新增系統配置. 後面立即啟用. 最後是下面狀態表示core啟用都搞好了.

(上面是ubuntu 15.10 環境中, 後面測試用的是centos 6.4)

2. 簡單接觸 GDB , 開始除錯 r n p

第一個演示程式碼 heoo.c

複製程式碼

#include <stdio.h>

int g_var = 0;

static int _add(int a, int b) {
    printf("_add callad, a:%d, b:%d\n", a, b);
    return a+b;
}

int main(void) {
    int n = 1;
    
    printf("one n=%d, g_var=%d\n", n, g_var);
    ++n;
    --n;
    
    g_var += 20;
    g_var -= 10;
    n = _add(1, g_var);
    printf("two n=%d, g_var=%d\n", n, g_var);
    
    return 0;
}

複製程式碼

我們下面從圖說起.(如果用視訊說更好,文字和圖意義在於查詢方便.更簡約)

第一個命令 gdb heoo.out 表示 gdb載入 heoo.out 開始除錯. 如果需要使用gdb除錯的話編譯的時候 gcc 需要加上 -g命令.

其中l命令表示 檢視載入原始碼內容. 下面將演示如何加斷點.

r 表示除錯的程式開始執行.

p 命令表示 列印值. n表示過程除錯, 到下一步. 不管子過程如何都不進入. 直接一次跳過.

上面用的s 表示單步除錯, 遇到子函式,會進入函式內部除錯.

總結一下 . l 檢視原始碼 , b 加斷點, r 開始執行除錯, n 下一步, s下一步但是會進入子函式. p 輸出資料.

到這裡gdb 基本會用了. 是不是也很容易. 直白. 小程式碼可以隨便除錯了.

看到這裡基礎知識普及完畢了. 後面可以不看了. 有機會再看. 好那我們接著扯.

正文 第一部分 gdb其它開發中用的命令

開始扯一點, linux總是敲命令操作, 也很不安全. 有時候暈了. 寫這樣編譯命令.

gcc -g -Wall -o heoo.c heoo.out

非常恐怖, heoo.c 程式碼刪除了. heoo.out => heoo.c 先建立後生成失敗退出. 原先的內容被抹掉了. 哈哈. 伺服器開發, 經驗不足, 熟練度不夠.自己都怕自己.

1.  gdb 其它常用命令用法 c q b info

首先看 用到的除錯檔案 houge.c

複製程式碼

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/*
 * arr 只能是陣列
 * 返回當前陣列長度
 */
#define LEN(arr) (sizeof(arr)/sizeof(*arr))

// 簡單陣列列印函式
static void _parrs(int a[], int len) {
    int i = -1;
    puts("當前陣列內容值如下:");

    while(++i < len) 
        printf("%d ", a[i]);    
    putchar('\n');
}

// 簡單包裝巨集, arr必須是陣列
#define PARRS(arr) \
    _parrs(arr, LEN(arr))

#define _INT_OLD (23)

/*
 * 主函式,簡單測試
 * 測試 core檔案, 
 * 測試 巨集除錯
 * 測試 堆疊記憶體資訊
 */
int main(void) {
    int i;
    int a[_INT_OLD];
    int* ptr = NULL;    

    // 來個隨機數填充值吧
    srand((unsigned)time(NULL));
    for(i=0; i<LEN(a); ++i)
        a[i] = rand()%222;
    
    PARRS(a);

    //全員加double, 包含一個錯誤方便測試
    for(i=1; i<=LEN(a); ++i)
        a[i] <<= 1;
    PARRS(a);

    // 為了錯,強制錯
    *ptr = 0;

    return 0;
}

複製程式碼

同樣需要仔細看下面圖中使用的命令. 首先對前言部分加深一些. 看下面

這個圖是前言的補充, c跳過直到下一個斷點處, q表示程式退出.

在 houge.c 中我們開始除錯. 一執行段錯誤, 出現了我們的 core.pid 檔案

通過 gdb houge.out core.27047 開始除錯. 馬上定位出來了錯誤原因.

2. 除錯 記憶體堆疊資訊

剛開始 print a , 在main中當做陣列處理.列印的資訊多. 後面在_add函式中, a就是個形引數組地址.

主要看 info args  檢視當前函式引數值

info locals 看當前函式棧上值資訊, info registers 表示檢視暫存器值.

後面檢視記憶體資訊 需要記得東西多一些. 先看圖

x /23dw a 意思是  檢視 從a地址開始 23個 4位元組 有符號十進位制數 輸出.

關於x 更加詳細見下面

複製程式碼

用gdb檢視記憶體格式: 
    x /nfu ptr

說明
x 是 examine 的縮寫
n表示要顯示的記憶體單元的個數

f表示顯示方式, 可取如下值
x 按十六進位制格式顯示變數。
d 按十進位制格式顯示變數。
u 按十進位制格式顯示無符號整型。
o 按八進位制格式顯示變數。
t 按二進位制格式顯示變數。
a 按十六進位制格式顯示變數。
i 指令地址格式
c 按字元格式顯示變數。
f 按浮點數格式顯示變數。

u表示一個地址單元的長度
b表示單位元組,
h表示雙位元組,
w表示四位元組,
g表示八位元組

Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char) and s(string).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes)

ptr 表示從那個地址開始

複製程式碼

這個命令常用於監測記憶體變化.除錯中特別常用.

3. gdb 設定條件斷點

很簡單 b 17 if i == 8. 在17行設定一個斷點,並且只有i==8的時候才會觸發.

4. gdb 刪除斷點

gdb 刪除有d 後面跟斷點索引1,2,3..

clear 行數或名稱. 刪除哪一行斷點. 看下面演示

到這裡 介紹的gdb除錯技巧基本都夠用了. 感覺用圖形ide,例如vs除錯也就用到這些了.

估計gdb除錯突破20min過去了.夠用了.  後面可以不用看了.

正文 第二部分 gdb 多執行緒多程序除錯

  到這裡實戰中用的機會少了, 也就老鳥會用上些. 這部分可以除錯,不好除錯. 一般一調估計小半天就走了. 好,那我們處理最後10min.

1. 首先對上面正文第一部分加深 gdb除錯巨集

首先看上面命令

  macro expand 巨集(引數) => 得到巨集匯出內容.

  info macro 巨集名 => 巨集定義內容

如果你需要用到上面gdb功能, 檢視和匯出巨集的話.還需要gcc 支援,生成的時候加上 -ggdb3如下

gcc -Wall -ggdb3 -o houge.out houge.c

就可以使用了. 擴充套件一下 對於 gcc 編譯的有個過程叫做 預編譯 gcc -E -o *.i *.c.

這時候處理多數巨集,直接展開, 也可以檢視最後結果. 也算也是一個黑科技.

2. 開始多執行緒除錯

首先看測試用例 dasheng.c

複製程式碼

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 宣告一個都用的量
static int _old;

// 執行緒跑的函式
static void* _run(void* arg) {
    int piyo = 10;    
    int n = *(int*)arg;
    int i;
    
    //設定執行緒分離
    pthread_detach(pthread_self());
    
    for(i=0; i<n; ++i) {
        printf("n=%d, i=%d\n", n, i);
        ++_old;
        printf("n=%d, piyo = %d, _old=%d\n", n, piyo, _old);
    }

    return NULL;
}

#define _INT_PTX (3)

int main(void) {
    int i, rt, j;
    pthread_t tx[_INT_PTX];

    puts("main beign");    

    for(i=0; i<_INT_PTX; ++i) {
        // &i 是有問題的, 但是這裡為了測試, 可以亂搞
        rt = pthread_create(tx+i, NULL, _run, &i);
        if(rt < 0) {
            printf("pthread_create create error! rt = %d, i=%d\n", rt, i);
            break;
        }
    }

    //CPU忙等待
    for(j=0; j<1000000000; ++j)
        ;        
    puts("end");    

    return 0;
}

複製程式碼

編譯命令

gcc -Wall -g -o dasheng.out dasheng.c -lpthread

那先看下面測試圖

上面 info threads 檢視所有執行的執行緒資訊. *表示當前除錯的執行緒.

後面 l _run 表示檢視 _run附近程式碼. 當然還有 l 16 檢視16行附近檔案內容.

gdb多執行緒切換 測試如下

 thread 3表示切換到第三個執行緒, info threads 第一列id 就是 thread 切換的id.

上面測試執行緒 就算你切換到 thread 3. 其它執行緒還是在跑的. 我們用下面命令 只讓待除錯的執行緒跑. 其它執行緒阻塞.

set scheduler-locking on  開始多執行緒單獨除錯. 不用了 設定 set scheduler-locking off 關閉. 又會回到你除錯這個, 其它執行緒不阻塞.

總結 多執行緒除錯常用就這三個實用命令

info threads

thread id

set scheduler-locking on/off

分別是檢視,切換,設定同步除錯.到這裡多執行緒除錯基本完畢了.

3. 開始gdb 多程序除錯

首先看 liaobude.c 測試程式碼

複製程式碼

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// 宣告一個都用的量
static int _old;

// 執行緒跑的函式
static void _run(int n) {
    int piyo = 10;    
    int i;
    
    ++n;    
    for(i=0; i<n; ++i) {
        printf("n=%d, i=%d\n", n, i);
        ++_old;
        printf("n=%d, piyo = %d, _old=%d\n", n, piyo, _old);
    }
}

#define _INT_PTX (3)

int main(void) {
    int i;
    pid_t rt;

    puts("main beign");    

    for(i=0; i<_INT_PTX; ++i) {
        // &i 是有問題的, 但是這裡為了測試, 可以亂搞
        rt = fork();
        if(rt < 0) {
            printf("fork clone error! rt = %d, i=%d\n", rt, i);
            break;
        }
        if(rt == 0) {
            _run(i);
            exit(EXIT_FAILURE);
        }
    }

    //等待子程序結束
  for(;;) {
    rt = waitpid(-1, NULL, WNOHANG);
    if(rt>=0 || errno==EINTR)
      continue;
    break;
  }   
    
    puts("end");    

    // 這裡繼續等待 
    for(i=0; i<190; ++i){
        printf("等待 有緣人[%d]!\n", i);
        sleep(1);
    }    

    return 0;
}

複製程式碼

編譯命令

gcc -Wall -g -o liaobude.out liaobude.c

 其實對多程序除錯, 先介紹一個 常用的, 除錯正在執行的程式. 首先讓 ./liaobude.out 跑起來.

 再通過 ps -ef 找到需要除錯的程序. 複製程序檔案描述符pid.

這時候啟動gdb.

attach pid

gdb就把pid那個程序載入進來了. 載入的程序會阻塞到當前正在執行的地方. 直到使用命令控制. 這個功能還是非常猛的.

最後介紹 程序除錯的有關命令(需要最新的gdb才會支援). 多程序的除錯思路和多執行緒除錯流程很相似.

複製程式碼

GDB可以同時除錯多個程式。
只需要設定follow-fork-mode(預設值:parent)和detach-on-fork(預設值:on)即可。

   設定方法:set follow-fork-mode [parent|child]   set detach-on-fork [on|off]

   查詢正在除錯的程序:info inferiors
   切換除錯的程序: inferior <infer number>
    

複製程式碼

具體的意思有

set follow-fork-mode [parent|child]   set detach-on-fork [on|off]

 parent                   on               只調試主程序(gdb預設)  child                      on               只調試子程序  parent                   off              同時除錯兩個程序,gdb跟主程序,子程序block在fork位置  child                      off              同時除錯兩個程序,gdb跟子程序,主程序block在fork位置

使用方式和執行緒除錯思路是一樣的. 就是gdb 的命令換了字元. 工作中多程序除錯遇到少. 

遇到了很少用gdb除錯. 會用下面2種除錯好辦法

2) 寫單元測試

3) 打日誌檢測日誌,分析

到這裡 gdb30分鐘內容講解完畢. 多試試寫寫練一練, gdb基本突破沒有問題.

後記

  錯誤是難免的, 有問題可以隨時交流. 拜~~, 週六下午愉快. 希望明天仍然是個好天氣~~