Linux gdb多進(jìn)程、多線程調(diào)試過(guò)程
前言
gdb 是 linux 平臺(tái)下進(jìn)行程序調(diào)試的最常用的工具。簡(jiǎn)單的程序調(diào)試就是加斷點(diǎn),然后一步一步讓程序運(yùn)行,直到找到 bug 。
一般的程序調(diào)試起來(lái)比較簡(jiǎn)單,但是在多進(jìn)程或多線程情況下調(diào)試起來(lái)就比較麻煩。
若 test.c 是你想要調(diào)試的程序,那么在編譯時(shí)需要加 -g,即 gcc test.c -g -o test。完成編譯后使用命令:gdb test。
常用命令
命令 | 命令縮寫(xiě)及例子 | 說(shuō)明 |
list + n | l + n | 顯示源碼第n行前后的代碼,顯示范圍有限。 |
break + n | b + n | 在第n行設(shè)置斷點(diǎn) |
info | i | 描述程序狀態(tài) |
run | r | 開(kāi)始運(yùn)行程序 |
display | disp | 跟蹤查看某個(gè)變量的值 |
info display | 用于顯示當(dāng)前所有要顯示值的表達(dá)式的情況 | |
undisplay | undisplay + 編號(hào) | 用于結(jié)束某個(gè)表達(dá)式值的顯示 |
step | s | 執(zhí)行下一條語(yǔ)句,如果該語(yǔ)句為函數(shù)調(diào)用,則進(jìn)入函數(shù)執(zhí)行其中的第一條語(yǔ)句 |
next | n | 執(zhí)行下一條語(yǔ)句,如果該語(yǔ)句為函數(shù)調(diào)用,不會(huì)進(jìn)入函數(shù)內(nèi)部執(zhí)行 |
p | 打印內(nèi)部變量的值 | |
continue | c | 繼續(xù)運(yùn)行,直到遇到下一個(gè)斷點(diǎn) |
start | st | 開(kāi)始執(zhí)行程序,在main函數(shù)的第一條語(yǔ)句前面停下來(lái) |
kill | k | 終止正在調(diào)試的程序 |
quit | q | 退出gdb |
set args | set args arg1 arg2 | 設(shè)置運(yùn)行參數(shù) |
show args | show args | 查看運(yùn)行參數(shù) |
finish | finish | 一直運(yùn)行到函數(shù)返回并打印函數(shù)返回時(shí)的堆棧地址和返回值及參數(shù)值等信息 |
堆棧相關(guān)命令
命令 | 例子 | 說(shuō)明 |
backtrace | bt | 查看堆棧信息 |
frame | f 1 | 查看棧幀 |
info reg | info reg/ i r | 查看寄存器使用情況 |
info stack | info stack | 查看堆棧使用情況 |
up/down | up/down | 跳到上一層/下一層函數(shù) |
這里以一個(gè)簡(jiǎn)單的程序?yàn)槔?,進(jìn)行調(diào)試。
#include <bits/stdc++.h> using namespace std; #define M 5 int fact(int n) //線性遞歸 { if (n < 0) return 0; else if(n == 0 || n == 1) return 1; else return n * fact(n - 1); } int facttail(int n, int a) //尾遞歸 { if (n < 0) return 0; else if (n == 0) return 1; else if (n == 1) return a; else return facttail(n - 1, n * a); } int facttail1(int n, int a) { while(n > 0) { a = n * a; n--; } return a; } int main() { //printf("%p", facttail); int a = fact(M); int b = facttail(M, 1); cout << "A:" << a <<endl; cout << "B:" << b <<endl; }
(1)開(kāi)始 gdb 調(diào)試
(2)設(shè)置斷點(diǎn)
(3)查看棧的使用情況
更為詳細(xì)的斷點(diǎn)調(diào)試
命令 | 例子 | 說(shuō)明 |
break + 設(shè)置斷點(diǎn)的行號(hào) | break n | 在n行處設(shè)置斷點(diǎn) |
tbreak + 行號(hào)或函數(shù)名 | tbreak n/func | 設(shè)置臨時(shí)斷點(diǎn),到達(dá)后被自動(dòng)刪除 |
break + filename + 行號(hào) | break main.c:10 | 用于在指定文件對(duì)應(yīng)行設(shè)置斷點(diǎn) |
break + <0x...> | break 0x3400a | 用于在內(nèi)存某一位置處暫停 |
break + 行號(hào) + if + 條件 | break 10 if i==3 | 用于設(shè)置條件斷點(diǎn),在循環(huán)中使用非常方便 |
info breakpoints/watchpoints [n] | info break | n表示斷點(diǎn)編號(hào),查看斷點(diǎn)/觀察點(diǎn)的情況 |
clear + 要清除的斷點(diǎn)行號(hào) | clear 10 | 用于清除對(duì)應(yīng)行的斷點(diǎn),要給出斷點(diǎn)的行號(hào),清除時(shí)GDB會(huì)給出提示 |
delete + 要清除的斷點(diǎn)編號(hào) | delete 3 | 用于清除斷點(diǎn)和自動(dòng)顯示的表達(dá)式的命令,要給出斷點(diǎn)的編號(hào),清除時(shí)GDB不會(huì)給出任何提示 |
disable/enable + 斷點(diǎn)編號(hào) | disable 3 | 讓所設(shè)斷點(diǎn)暫時(shí)失效/使能,如果要讓多個(gè)編號(hào)處的斷點(diǎn)失效/使能,可將編號(hào)之間用空格隔開(kāi) |
awatch/watch + 變量 | awatch/watch i | 設(shè)置一個(gè)觀察點(diǎn),當(dāng)變量被讀出或?qū)懭霑r(shí)程序被暫停 |
rwatch + 變量 | rwatch i | 設(shè)置一個(gè)觀察點(diǎn),當(dāng)變量被讀出時(shí),程序被暫停 |
catch | 設(shè)置捕捉點(diǎn)來(lái)補(bǔ)捉程序運(yùn)行時(shí)的一些事件。如:載入共享庫(kù)(動(dòng)態(tài)鏈接庫(kù))或是C++的異常 | |
tcatch | 只設(shè)置一次捕捉點(diǎn),當(dāng)程序停住以后,應(yīng)點(diǎn)被自動(dòng)刪除 |
gdb多進(jìn)程調(diào)試
命令 | 例子 | 說(shuō)明 |
set follow-fork-mode [parent|child] | set follow-fork-mode parent or child | 設(shè)置調(diào)試器的模式,mode參數(shù)可以是: parent :fork之后調(diào)試原進(jìn)程,子進(jìn)程不受影響,這是缺省的方式 child :fork之后調(diào)試新的進(jìn)程,父進(jìn)程不受影響。 |
show follow-fork-mode | show follow-fork-mode | 顯示當(dāng)前調(diào)試器的模式 |
set detach-on-fork [on|off] | set detach-on-fork on or off | 設(shè)置gdb在fork之后是否detach進(jìn)程中的其中一個(gè),或者繼續(xù)保留控制這兩個(gè)進(jìn)程 。 on:子進(jìn)程(或者父進(jìn)程,依賴于follow-fork-mode的值)會(huì)detach然后獨(dú)立運(yùn)行,這是缺省的mode off:兩個(gè)進(jìn)程都受gdb控制,一個(gè)進(jìn)程(子進(jìn)程或父進(jìn)程,依賴于follow-fork-mode)被調(diào)試,另外一個(gè)進(jìn)程被掛起 |
info inferiors | info inferiors | 顯示所有進(jìn)程 |
inferiors processid | inferiors 2 | 切換進(jìn)程 |
detach inferiors processid | detach inferiors processid | detach 一個(gè)由指定的進(jìn)程,然后從fork 列表里刪除。這個(gè)進(jìn)程會(huì)被允許繼續(xù)獨(dú)立運(yùn)行。 |
kill inferiors processid | kill inferiors processid | 殺死一個(gè)由指定的進(jìn)程,然后從fork 列表里刪除。 |
catch fork | catch fork | 讓程序在fork,vfork或者exec調(diào)用的時(shí)候中斷 |
調(diào)試多進(jìn)程時(shí),需要設(shè)置 detach-on-fork 的值,默認(rèn)值為 on,設(shè)置為 off 的含義:一個(gè)進(jìn)程被調(diào)試,另外一個(gè)進(jìn)程被掛起,這樣就可以交替的調(diào)試進(jìn)程。follow-fork-mode 的默認(rèn)值為 parent,即默認(rèn)調(diào)試父進(jìn)程。調(diào)試代碼:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { int num = 0; pid_t pid = fork(); if(pid == 0) //子進(jìn)程 { while(1) { num++; printf("child:pid:[%d] num:[%d]\n", getpid(), num); sleep(2); } } else { while(1){ num = num + 2; printf("parent:pid:[%d] num:[%d]\n", getpid(), num); sleep(2); } } return 0; }
(1) 查看系統(tǒng)默認(rèn)的 follow-fork-mode 和 detach-on-fork 及 設(shè)置follow-fork-mode 和 detach-on-fork
show follow-fork-mode show detach-on-fork set follow-fork-mode [parent|child] set detach-on-fork [on|off]
(2) 設(shè)置斷點(diǎn)并查看斷點(diǎn)信息
(3) 運(yùn)行程序并使用 info inferiors 命令 (顯示GDB調(diào)試的所有進(jìn)程,其中帶有*的進(jìn)程是正在調(diào)試的進(jìn)程)
(4) 使用 inferior + [編號(hào)] 切換進(jìn)程,對(duì)子進(jìn)程進(jìn)行調(diào)試
(5) 繼續(xù)運(yùn)行程序,觀察子進(jìn)程的輸出
(6) 中斷子進(jìn)程運(yùn)行后,開(kāi)始逐步調(diào)試
(7) 再次切換回父進(jìn)程完成調(diào)試
gdb多線程調(diào)試
命令 | 例子 | 說(shuō)明 |
info threads | info threads | 查詢線程信息 |
thread + 線程號(hào) | thread 2 | 切換線程 |
thread apply [threadno] [all] + 命令 | thread apply [threadno] [all] bt | 線程根據(jù)相應(yīng)的命令完成操作 |
set print thread-events | set print thread-events | 控制線程開(kāi)始和結(jié)束時(shí)的打印信息 |
show print thread-events | show print thread-events | 顯示線程打印信息的開(kāi)關(guān)狀態(tài) |
調(diào)試代碼:
#include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <errno.h> #include <unistd.h> /*全局變量*/ int sum = 0; /*互斥量 */ pthread_mutex_t mutex; /*聲明線程運(yùn)行服務(wù)程序*/ void* pthread_function1 (void*); void* pthread_function2 (void*); int main (void) { /*線程的標(biāo)識(shí)符*/ pthread_t pt_1 = 0; pthread_t pt_2 = 0; int ret = 0; /*互斥初始化*/ pthread_mutex_init (&mutex, NULL); /*分別創(chuàng)建線程1、2*/ ret = pthread_create( &pt_1, //線程標(biāo)識(shí)符指針 NULL, //默認(rèn)屬性 pthread_function1, //運(yùn)行函數(shù) NULL); //無(wú)參數(shù) if (ret != 0) { perror ("pthread_1_create"); } ret = pthread_create( &pt_2, //線程標(biāo)識(shí)符指針 NULL, //默認(rèn)屬性 pthread_function2, //運(yùn)行函數(shù) NULL); //無(wú)參數(shù) if (ret != 0) { perror ("pthread_2_create"); } /*等待線程1、2的結(jié)束*/ pthread_join (pt_1, NULL); pthread_join (pt_2, NULL); printf ("main programme exit!\n"); return 0; } /*線程1的服務(wù)程序*/ void* pthread_function1 (void*a) { int i = 0; printf ("This is pthread_1!\n"); for( i=0; i<3; i++ ) { pthread_mutex_lock(&mutex); /*獲取互斥鎖*/ /*臨界資源*/ sum++; printf ("Thread_1 add one to num:%d\n",sum); pthread_mutex_unlock(&mutex); /*釋放互斥鎖*/ /*注意,這里以防線程的搶占,以造成一個(gè)線程在另一個(gè)線程sleep時(shí)多次訪問(wèn)互斥資源,所以sleep要在得到互斥鎖后調(diào)用*/ sleep (1); } pthread_exit ( NULL ); } /*線程2的服務(wù)程序*/ void* pthread_function2 (void*a) { int i = 0; printf ("This is pthread_2!\n"); for( i=0; i<5; i++ ) { pthread_mutex_lock(&mutex); /*獲取互斥鎖*/ /*臨界資源*/ sum++; printf ("Thread_2 add one to num:%d\n",sum); pthread_mutex_unlock(&mutex); /*釋放互斥鎖*/ /*注意,這里以防線程的搶占,以造成一個(gè)線程在另一個(gè)線程sleep時(shí)多次訪問(wèn)互斥資源,所以sleep要在得到互斥鎖后調(diào)用*/ sleep (1); } pthread_exit ( NULL ); }
(1) 設(shè)置斷點(diǎn)并查看信息
(2) 運(yùn)行程序,這里可以不設(shè)第一個(gè)斷點(diǎn)
(3) 兩個(gè)線程交替運(yùn)行,觀察不同線程的輸出結(jié)果
(4) 使用 info threads 查看線程信息,使用 thread + [編號(hào)] 切換線程
(5) 使用 thread apply + [編號(hào)] + 命令
鎖定其他線程,只讓當(dāng)前線程運(yùn)行
(1) 設(shè)置 set scheduler-locking on
(2) 觀察線程 1 運(yùn)行的情況并完成調(diào)試
其他命令
(1) thread apply ID1 ID2 IDN command: 讓線程編號(hào)是ID1,ID2…等等的線程都執(zhí)行command命令。
(2) thread apply all command:所有線程都執(zhí)行command命令。
(3) set scheduler-locking off|on|step: 在調(diào)試某一個(gè)線程時(shí),其他線程是否執(zhí)行。在使用step或continue命令調(diào)試當(dāng)前被調(diào)試線程的時(shí)候,其他線程也是同時(shí)執(zhí)行的,如果我們只想要被調(diào)試的線程執(zhí)行,而其他線程停止等待,那就要鎖定要調(diào)試的線程,只讓他運(yùn)行。
off
:不鎖定任何線程,默認(rèn)值。on
:鎖定其他線程,只有當(dāng)前線程執(zhí)行。step
:在step(單步)時(shí),只有被調(diào)試線程運(yùn)行。
(4) set non-stop on/off:當(dāng)調(diào)試一個(gè)線程時(shí),其他線程是否運(yùn)行。
(5) set pagination on/off: 在使用backtrace時(shí),在分頁(yè)時(shí)是否停止。
(6) set target-async on/ff: 同步和異步。同步,gdb在輸出提示符之前等待程序報(bào)告一些線程已經(jīng)終止的信息。而異步的則是直接返回。
(7) show scheduler-locking: 查看當(dāng)前鎖定線程的模式
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
apache,nginx上傳目錄無(wú)執(zhí)行權(quán)限的設(shè)置方法
至于為什么設(shè)置上傳目錄無(wú)權(quán)限這個(gè)我就不累贅了,現(xiàn)在比較流行的web服務(wù)有iis,apache,nginx,使用操作系統(tǒng)無(wú)非是windows or *nux2010-12-12一道題理解Linux中sort命令的多個(gè)參數(shù)
今天小編就為大家分享一篇關(guān)于一道題理解Linux中sort命令的多個(gè)參數(shù),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03Apache?APISIX?Dashboard?未授權(quán)訪問(wèn)漏洞分析(CVE-2021-45232)
Apache?APISIX?是一個(gè)動(dòng)態(tài)、實(shí)時(shí)、高性能的?API?網(wǎng)關(guān),?提供負(fù)載均衡、動(dòng)態(tài)上游、灰度發(fā)布、服務(wù)熔斷、身份認(rèn)證、可觀測(cè)性等豐富的流量管理功能,這篇文章主要介紹了Apache?APISIX?Dashboard?未授權(quán)訪問(wèn)漏洞(CVE-2021-45232),需要的朋友可以參考下2023-03-03win10+Ubuntu 20.04 LTS雙系統(tǒng)安裝(UEFI + GPT)(圖文,多圖預(yù)警)
這篇文章主要介紹了win10+Ubuntu 20.04 LTS雙系統(tǒng)安裝(UEFI + GPT)(圖文,多圖預(yù)警),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07linux云服務(wù)搭建七日殺服務(wù)器的詳細(xì)流程
這篇文章主要介紹了linux云服務(wù)搭建七日殺服務(wù)器的詳細(xì)流程,本文通過(guò)實(shí)例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08Ubuntu系統(tǒng)下擴(kuò)展LVM根目錄的方法
這篇文章主要給大家介紹了關(guān)于Ubuntu系統(tǒng)下擴(kuò)展LVM根目錄的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05