一、常用普通調試命令
1.簡單介紹GDB
介紹: gdb是Linux環境下的代碼調試⼯具。
使⽤:需要在源代碼⽣成的時候加上 -g 選項。
開始使⽤: gdb binFile
退出: ctrl + d 或 quit
2.調試過程
(1)list命令
- list linenum 顯⽰binFile第linenum行周圍的源代碼,接着上次的位置往下列,每次列10⾏。
- list function 顯示函數名為function的函數的源程序
- list 顯示當前行后面的源程序
- list - 顯示當前行前面的源程序
(2)run或r
- run args run命令可以直接接命令行參數值,也可以在執行run之前通過 set args + 參數值實現。
(3)break(b)
- b linenum 在某行打斷點
- b +offset/-offset 在當前行號的前面或后面offset停住
- b filename:linenum 在某文件的某行打斷點
- b filename:function 在某文件某個函數入口停住
- b *address 在程序的運行地址處停住
- b 沒有參數在下一句停住
- b where if condition 當某個條件滿足時,在某一行停住(這個很有用,比如b 10 if ret == 5)
- breaktrace(或bt) 查看各級函數調⽤及參數
- delete breakpoints 刪除所有斷點
- delete breakpoints n 刪除序號為n的斷點
- disable breakpoints 禁⽤斷點
- enable breakpoints 啟⽤斷點

(4)單步命令
- step count 一次性執行count步,如果有函數會進入函數
- next count 一次執行count,不進入函數
- finish 運行程序,直到當前函數完成返回,並打印函數返回時的堆棧地址和返回值以及參數信息
- until 退出循環體(尤其是針對for循環這種,很煩的)
(5)continue命令
continue(或c):從當前位置開始連續⽽⾮單步執⾏程序
(6)print(p)命令
- x 按十六進制格式顯示變量
- d 按十進制格式顯示變量
- u 按十六進制格式顯示無符號整型
- o 按八進制格式顯示變量
- t 按二進制格式顯示變量t 按二進制格式顯示變量
- a 按十六進制格式顯示變量
- c 按字符格式顯示變量
- f 按浮點數格式顯示變量
(7)watch命令
- watch expr 為表達式expr設置一個觀察點,一旦表達式值有變化,馬上停住程序
- rwatch expr 當表達式expr被讀時,停住程序
- awatch expr 當表達式的值被讀或被寫時,停住程序。
- info watchpoints 列出所有觀察點(info指令通常可以去套 舉例如下,演示觀測*i的值,一旦變化停下來:

(8)examine命令

(9)jump命令
- jump linespec 指定下一條語句的運行點,linespec可以是linenum,filename+linenum,+offset這幾種形式
- jump address 跳到代碼行的地址
(10)signal命令
(11)set命令
- set args 設置命令行參數
- set env environmentVarname=value 設置環境變量。如:set env USER=benben
(12)call命令
- call function 強制調用某函數
(13)disassemble命令

- info(i) locals: 查看當前棧幀局部變量的值
- info break : 查看斷點信息
- info(或i) breakpoints: 查看當前設置了哪些斷點
- finish: 執⾏到當前函數返回,然后挺下來等待命令
- set var: 修改變量的值
- display 變量名: 跟蹤查看⼀個變量,每次停下來都顯⽰它的值
- undisplay: 取消對先前設置的那些變量的跟蹤
- until X⾏號: 跳⾄X⾏ 直接回
- n 或 next: 單條執⾏
- p 變量: 打印變量值。
(14)另外一些常用調試命令
until 跳出循環。在執行完循環體內的最后一條語句之后執行 until, 就會執行完循環體到后面的一個語句停下。
finish 跳出當前函數,執行完當前的函數。
tui是一個命令行的界面,能同時把代碼顯示出來。
二、使用gdb調試core文件
1.認識core文件
2.配置生成core文件
①使用 ulimit -c 查看core開關,如果為0表示關閉,不會生成core文件;
②使用 ulimit -c [filesize] 設置core文件大小,當最小設置為4之后才會生成core文件;
③使用 ulimit -c unlimited 設置core文件大小為不限制,這是常用的做法;
④如果需要開機就執行,則需要將這句命令寫到 /etc/profile 等文件。
core文件命名和保存路徑:
①core文件有默認的名稱和路徑;
②自己命名和指定保存路徑,可以通過 /proc/sys/kernel/core_pattern 設置 core 文件名和保存路徑,方法如下:
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
命名的參數列表:
%p - insert pid into filename 添加pid
%u - insert current uid into filename 添加當前uid
%g - insert current gid into filename 添加當前gid
%s - insert signal that caused the coredump into the filename 添加導致產生core的信號
%t - insert UNIX time that the coredump occurred into filename 添加core文件生成時的unix時間
%h - insert hostname where the coredump happened into filename 添加主機名
%e - insert coredumping executable name into filename 添加命令名。
3.調試core文件
(1)方法1: gdb [exec file] [core file] 然后執行bt看堆棧信息(或者where命令)
(2)方法1:gdb -c [core file],然后 file [exec file],最后再使用 bt 查看錯誤位置
https://blog.csdn.net/sunxiaopengsun/article/details/72974548
三、使用gdb調試多進程多線程程序
1.設置
child on 只調試子進程
parent off 同時調試兩個進程,gdb跟主進程,子進程block在fork位置
child off 同時調試兩個進程,gdb跟子進程,主進程block在fork位置
查詢正在調試的進程:info inferiors
切換調試的進程: inferior <infer number>
添加新的調試進程: add-inferior [-copies n] [-exec executable] ,可以用file executable來分配給inferior可執行文件。
其他:remove-inferiors infno, detach inferior

2.演示代碼
下面這段代碼的主要流程就是在main函數中fork創建一個子進程,然后在父進程中又創建一個線程,接着就使用gdb進行調試(block子進程)。
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <sys/types.h> 4 #include <unistd.h> 5 6 int main(int argc, const char **argv) 7 { 8 int pid; 9 pid = fork(); 10 if (pid != 0) //add the first breakpoint. 11 Parent(); 12 else 13 Child(); 14 return 0; 15 } 16 17 //Parent process handle. 18 void Parent() 19 { 20 pid_t pid = getpid(); 21 char cParent[] = "Parent"; 22 char cThread[] = "Thread"; 23 pthread_t pt; 24 25 printf("[%s]: [%d] [%s]\n", cParent, pid, "step1"); 26 27 if (pthread_create(&pt, NULL, (void *)*ParentDo, cThread)) 28 { 29 printf("[%s]: Can not create a thread.\n", cParent); 30 } 31 32 ParentDo(cParent); 33 sleep(1); 34 } 35 //Parent process handle after generate a thread. 36 void * ParentDo(char *argv) 37 { 38 pid_t pid = getpid(); 39 pthread_t tid = pthread_self(); //Get the thread-id selfly. 40 char tprefix[] = "thread"; 41 42 printf("[%s]: [%d] [%s] [%lu] [%s]\n", argv, pid, tprefix, tid, "step2"); //add the second breakpoint. 43 printf("[%s]: [%d] [%s] [%lu] [%s]\n", argv, pid, tprefix, tid, "step3"); 44 45 return NULL; 46 } 47 //Child process handle. 48 void Child() 49 { 50 pid_t pid = getpid(); 51 char prefix[] = "Child"; 52 printf("[%s]: [%d] [%s]\n", prefix, pid, "step1"); 53 return; 54 }
如果直接運行程序,那么輸出的結果如下:

3.gdb調試
3.1設置調試模式和Catchpoint
設置調試父子進程,gdb跟主進程,子進程block在fork位置。
這時可以另開一個終端,使用如下命令查看當前CentOS系統所有進程的狀態:發現父進程PID為10062,通過fork產生的子進程為10065:
同時,可以使用命令cat /proc/10062/status
查看當前進程的詳細信息:進程PID為10060,它的父進程(即GDB進程)為10062,同時這也是追蹤進程ID,線程數Threads為1(共享使用該信號描述符的線程數,在POSIX多線程序應用程序中,線程組中的所有線程使用同一個信號描述符)。

3.2 設置第一個斷點
在程序的第46行設置斷點,並運行到斷點處:
這時再次使用命令pstree -pul查看當前系統進程的狀態:發現此時仍然只有父進程13162和子進程13159。
3.3 執行到第一個斷點此時如果切換到子進程13162
重新切換到父進程13159
3.4 設置第二個斷點並調試
在第50行設置斷點繼續調試主進程(使父進程產生線程),其中父進程和線程到底是誰先執行是由內核調度控制的。
這時使用命令pstree -pul查看當前系統進程的狀態:存在父進程13159和子進程13162以及父進程創建的一個線程14208(線程用大括號{}表示)。
同時,使用命令cat /proc/13159/status
查看當前進程的詳細信息:進程PID為13159,它的父進程(即GDB進程)為13154,同時這也是追蹤進程ID,線程數Threads為2(當前父進程13159+線程14208)。
3.5 查看第二個斷點處的調試信息
3.6手動切換到線程
3.7 開始執行第二個斷點處的代碼
這時使用命令查看當前系統進程的狀態:存在父進程13159和子進程13162,其中線程14208已經結束了。
本文部分參考:https://typecodes.com/cseries/multilprocessthreadgdb.html