引言 Linus心靈雞湯linux
在*nix開發中有道卡叫gdb調試,無論你怎麼搞. 它依然在那絲絕不會鬆動.今天致敬一個 活着的傳奇 Linus Torvalds程序員
Unix 始於上個世紀60年代,在70年代獲得了迅猛的發展,算法
這時候的李納斯還躺在祖父公寓的搖籃裏睡大覺,若是不是後來 Unix 王國自亂陣腳,shell
出現陣營分裂和法律糾紛,可能 Linux 系統根本都不會出現。真實的狀況是,ubuntu
Unix 浪費了大把的時間和機會,彷佛就是爲了等待這個大鼻子、頭髮紛亂的芬蘭小子長大,而後一決高下。centos
李納斯贏得了本身的時間,他一刻不停的磨練本身的技藝,在清晨的微光中練習算法,數組
在赫爾辛基的雪山上編譯代碼,隨時隨地補充的糧草和武器。安全
二十一年以後,李納斯撫着雪亮的刀鋒上路了,他要去追尋屬於程序員的最高榮耀。[服務器
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 多進程調試demo 能夠參照 http://blog.csdn.net/pbymw8iwm/article/details/7876797
使用方式和線程調試思路是同樣的. 就是gdb 的命令換了字符. 工做中多進程調試遇到少.
遇到了不多用gdb調試. 會用下面2種調試好辦法
2) 寫單元測試
3) 打日誌檢測日誌,分析
到這裏 gdb30分鐘內容講解完畢. 多試試寫寫練一練, gdb基本突破沒有問題.
後記
錯誤是不免的, 有問題能夠隨時交流. 拜~~, 週六下午愉快. 但願明天仍然是個好天氣~~