gdb除錯詳解與darknet框架gdb除錯過程
準備工作
開啟core, 採集程式崩潰的狀態
首先你跟著我做開啟core崩潰狀態採集. 可以通過ulimit -c
檢視,如果是0
表示沒有開啟. 開啟按照下面操作:
sudo gedit /etc/profile
在/etc/profile
最後一行新增下面幾句話設定全域性開啟 core檔案除錯,大小不限.
# No core files by default 0, unlimited is oo
ulimit -S -c unlimited > /dev/null 2>&1
最後立即生效.
source /etc/profile
再跟著我做, 因為生成的core
core
命名規則, 讓其變成[core.pid]
格式.
sudo gedit /etc/sysctl.conf
在該檔案的最後的加上如下幾句話,並儲存
# open, add core.pid
kernel.core_pattern = ./core_%t_%p_%e
kernel.core_uses_pid = 1
立即啟用
sudo sysctl -p /etc/sysctl.conf
最後是ulimit -c
與cat /proc/sys/kernel/core_uses_pid
檢視,下面狀態表示core啟用都搞好了.
如果顯示沒有開啟成功,可以試試登出系統或者重啟
簡單接觸 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; }
我們從下圖說起,
使用命令
gcc -g -Wall -o heoo.out heoo.c
gdb heoo.out
gdb heoo.out
表示gdb
載入heoo.out
開始除錯. 如果需要使用gdb
除錯的話編譯的時候gcc
需要加上-g
命令.
其中l
命令表示 檢視載入原始碼內容. .
下面將演示如何加斷點,使用命令b 函式名
或者b 行數
,r
表示除錯的程式開始執行.
p
命令表示 列印值. n
表示過程除錯, 到下一步. 不管子過程如何都不進入. 直接一次跳過.
下面的s 表示單步除錯, 遇到子函式,會進入函式內部除錯.
總結一下 . l
檢視原始碼 ,b
加斷點, r
開始執行除錯, n
下一步, s
下一步但是會進入子函式. p
輸出資料. c
跳過直到下一個斷點處,watch 變數名
給變數新增監視點,whatis 變數名
列印變數名的型別, finish
跳出當前程式碼(之前跳入除錯),q
表示程式退出.
到這裡gdb 基本會用了. 是不是也很容易. 直白. 小程式碼可以隨便除錯了.
看到這裡基礎知識普及完畢了. 後面可以不看了. 有機會再看. 好那我們接著扯.
gdb其它開發中用的命令
開始扯一點, linux總是敲命令操作, 也很不安全. 有時候暈了. 寫這樣編譯命令.
gcc -g -Wall -o heoo.c heoo.out
非常恐怖, heoo.c
程式碼刪除了. heoo.out => heoo.c
先建立後生成失敗退出. 原先的內容被抹掉了. 哈哈. 伺服器開發, 經驗不足, 熟練度不夠.自己都怕自己.
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
中我們開始除錯. 輸入下面指令進行執行:
gcc -g -Wall -o houge.out houge.c
./houge.out
一執行段錯誤, 出現了我們的 core.pid
檔案
通過gdb houge.out core.27047
開始除錯. 馬上定位出來了錯誤原因.
除錯記憶體堆疊資訊
剛開始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 表示從那個地址開始
gdb設定條件斷點
如下如所示,很簡單b 17 if i == 8
. 在17行設定一個斷點,並且只有i==8
的時候才會觸發.
gdb刪除斷點
d
後面跟斷點索引1,2,3…clear
行數或名稱. 刪除哪一行斷點. 看下面演示
到這裡 介紹的gdb除錯技巧基本都夠用了. 感覺用圖形ide,例如vs除錯也就用到這些了.
估計gdb除錯突破20min過去了.夠用了. 後面可以不用看了.
gdb除錯回退
加入你正在使用GDB7.0以上版本的偵錯程式並且執行在支援反向除錯的平臺,你就可以用以下幾條命令來除錯程式:
reverse-continue
反向執行程式知道遇到一個能使程式中斷的事件(比如斷點,觀察點,異常)。
reverse-step
反向執行程式到上一次被執行的原始碼行。
reverse-stepi
反向執行程式到上一條機器指令
reverse-next
反向執行到上一次被執行的原始碼行,但是不進入函式。
reverse-nexti
反向執行到上一條機器指令,除非這條指令用來返回一個函式呼叫、整個函式將會被反向執行。
reverse-finish
反向執行程式回到呼叫當前函式的地方。
set exec-direction [forward | reverse]
設定程式執行方向,可以用平常的命令step
和continue
等來執行反向的除錯命令。
上面的反向執行也可以理解為撤銷後面執行的語句所產生的效果,回到以前的狀態。
好的,接下來我們來試試看如何反向除錯。
首先確認自己的平臺支援程序記錄回放(Process Record and Replay),當在偵錯程式啟用程序記錄回放功能時,偵錯程式會記錄下子程序,也就是被除錯程序的每一步的執行狀態與上一步執行狀態的差異,需要撤銷的時候就可以很方便回到上一步。
假設我們有以下C程式:
int main(int argc, const char *argv[])
{
int a = 0;
a = 1;
a = 2;
return 0;
}
將它編譯並加上除錯符號:
gcc -Wall -g a.c
開始除錯
gdb a.out
接下來設定一個斷點在第三行:
(gdb) b 3
Breakpoint 1 at 0x804839a: file a.c, line 3.
執行,程式會在第三行的地方停下來:
(gdb) r
Starting program: /home/cheryl/a.out
Breakpoint 1, main (argc=1, argv=0xbffff3e4) at a.c:3
3 int a = 0;
給變數a設定監視點方便我們觀察:
(gdb) watch a
Hardware watchpoint 2: a
啟動程序記錄回放:
(gdb) record
現在每執行一步偵錯程式都會記錄下變化,以便回溯。我們連續執行3條語句。
(gdb) n
4 a = 1;
(gdb)
Hardware watchpoint 2: a
Old value = 0
New value = 1
main (argc=1, argv=0xbffff3e4) at a.c:5
5 a = 2;
(gdb)
Hardware watchpoint 2: a
Old value = 1
New value = 2
main (argc=1, argv=0xbffff3e4) at a.c:6
6 return 0;
可以看到,a的值先是從0變為了1,然後變為2,如果想讓程式倒退回到以前的狀態怎麼辦?可以用reverse-next命令:
(gdb) reverse-next
Hardware watchpoint 2: a
Old value = 2
New value = 1
main (argc=1, argv=0xbffff3e4) at a.c:5
5 a = 2;
(gdb)
Hardware watchpoint 2: a
Old value = 1
New value = 0
main (argc=1, argv=0xbffff3e4) at a.c:4
4 a = 1;
(gdb)
No more reverse-execution history.
main (argc=1, argv=0xbffff3e4) at a.c:3
3 int a = 0;
(gdb)
這樣程式就倒退到了我們啟動程序記錄回放的地方,a的值經過兩步回到了最初的狀態。
若需要關閉程序記錄回放,可以使用record stop:
(gdb) record stop
Process record is stoped and all execution log is deleted.
GDB 格式化結構體輸出
set print address on
開啟地址輸出,當程式顯示函式資訊時,GDB會顯出函式的引數地址。系統預設為開啟的,
show print address
檢視當前地址顯示選項是否開啟。
set print array on
開啟陣列顯示,開啟後當陣列顯示時,每個元素佔一行,如果不開啟的話,每個元素則以逗號分隔。這個選項預設是關閉的。與之相關的兩個命令如下,我就不再多說了。
set print array off
show print array
set print elements
這個選項主要是設定陣列的,如果你的陣列太大了,那麼就可以指定一個來指定資料顯示的最大長度,當到達這個長度時,GDB就不再往下顯示了。如果設定為0,則表示不限制。
show print elements
檢視print elements的選項資訊。
set print null-stop
如果打開了這個選項,那麼當顯示字串時,遇到結束符則停止顯示。這個選項預設為off。
set print pretty on
如果開啟printf pretty這個選項,那麼當GDB顯示結構體時會比較漂亮。
set print pretty off
show print pretty
set print union on
set print union off
show print union
列印 C 中的聯合體。預設是 on 。
gdb 列印陣列
可以用下面的方法來顯示陣列
p *[email protected]
其中p
相當於print
,array
就是陣列首地址,也可以是陣列名,len
是想要顯示的陣列的長度。
比如我有一個數組的定義
int a[] = {1, 2, 3, 4, 5};
那麼想要顯示的時候就可以寫:
p *[email protected]
這樣就會顯示陣列a中的所有元素。
也可以使用display
在每一步除錯的時候都顯示:
display *[email protected]
取消顯示就用undisplay
,不過這時候要寫顯示的號碼。
gdb 除錯darknet實際工程
darknet
原始碼是makefile管理的,之前不會在Linux除錯大型專案,今天探索了一下,這裡介紹一下。
準備工作
從這裡下載原始碼
修改makefile檔案中DEBUG=0
改為DEBUG=1
進行除錯。其中編譯選項-O0
,意思是不進行編譯優化,gdb在預設情況下會使用-O2
,會出現print變數中出現<optimized out>
。
接著編譯原始碼:
make clean
make
根目錄會出現darknet
可執行檔案。
在工程根目錄執行如下命令下載權重:
wget https://pjreddie.com/media/files/yolov3-tiny.weights
開始除錯
終端輸入如下語句,開始除錯
gdb ./darknet
在gdb
命令中輸入執行程式需要的引數型別
set args detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg
為了對整個工程進行除錯,這裡需要將src
目錄新增進來,在gdb
命令中輸入如下指令:
DIR ./src
在gdb
命令中為main
函式設定斷點
b main
開始除錯,在gdb
命令中輸入r
,回車,發現程式停留在第一行。
接著可以在第435行,即char *outfile = find_char_arg(argc, argv, "-out", 0);
,打上斷點b 435
;
在gdb
命令中輸入b parser.c:761
在子函式parser.c
的761行打上斷點;
輸入c
,回車,程式跳到下一個斷點,即停留下一個斷點所在行;
輸入n
單步執行,不跳入子函式。
輸入s
命令單步執行並跳入此處呼叫的子函式;
輸入print 變數名
或者p 變數名
即可檢視該變數值;輸入finish
跳出子函式;
輸入q
結束除錯。
gdb 多執行緒多程序除錯
到這裡實戰中用的機會少了, 也就老鳥會用上些. 這部分可以除錯,不好除錯. 一般一調估計小半天就走了. 好,那我們處理最後10min.
gdb除錯巨集
首先看上面命令
- macro expand 巨集(引數) => 得到巨集匯出內容.
- info macro 巨集名 => 巨集定義內容
如果你需要用到上面gdb功能, 檢視和匯出巨集的話.還需要gcc 支援,生成的時候加上 -ggdb3如下
gcc -Wall -ggdb3 -o houge.out houge.c
就可以使用了. 擴充套件一下 對於 gcc 編譯的有個過程叫做 預編譯gcc -E -o *.i *.c.
這時候處理多數巨集,直接展開, 也可以檢視最後結果. 也算也是一個黑科技.
開始多執行緒除錯
首先看測試用例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
分別是檢視,切換,設定同步除錯.到這裡多執行緒除錯基本完畢了.
開始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種除錯好辦法
-
寫單元測試
-
打日誌檢測日誌,分析
到這裡 gdb30分鐘內容講解完畢. 多試試寫寫練一練, gdb基本突破沒有問題.
參考連結
Linux基礎 30分鐘GDB除錯快速突破
gdb除錯4–回退
One more thing
更多關於人工智慧、Python、C++、計算機等知識,歡迎訪問我的個人部落格進行交流, 點這裡~~