GDB除錯工具入門
0 gdb介紹
偵錯程式GDB允許檢視在執行一個程式時其內部時發生了什麼,或者是程式奔潰(crashed)時它正在做什麼。
- gdb通過以下四種事情來捕獲某個行為的異常錯誤(bug):
執行程式,指定可能影響其動作的內容。 - 讓程式在指定的情況下停止。
- 檢查當程式停止時發生了什麼。
- 改變程式中的內容,以便於更正一個錯誤,然後繼續尋找下一個錯誤。
gdb可用於除錯C,C++,Fortran,Modula-2。
gdb通過在終端執行gdb
命令啟用。一旦啟動,他從命令列讀取命令,直到使用quit
命令讓它退出。也可以通過使用help command
命令獲取線上幫助。
gdb可以使用無參或者帶參無選項執行,不過一般都使用帶參的命令,例如:指定一個程式或者指定core檔案:
gdb program #指定一個可執行程式
gdb program core #指定一個可執行程式以及core檔案
gdb program pid #指定一個正在執行的程式
gdb -p pid #同上
1 gdb常用命令
1.1 list
list命令用於檢視程式碼,可簡寫為l。
list #檢視上一次list中心附近的10行程式碼,-5~+5
list n #檢視第n行附近10行程式碼,n-5~n+5
list b,e #檢視b,e行範圍的程式碼
list function #檢視函式function附近的程式碼
list file:line #檢視檔案file第line行附件的程式碼
list file:function #檢視檔案file的函式function附近的程式碼
list *address #地址為address的行附近的程式碼,使用info add name獲取地址
1.2 break
break命令用於設定斷點,可用b簡化;delete用於刪除斷點,可用d簡化。
break n #在第n行設定斷點
break function #在函式function設定斷點,可以是庫函式
break file:line #在檔案file第line行設定斷點
break file:function #在檔案file的function函式設定斷點
break n if condition #根據條件在第n行設定斷點,例如b 16 if i==10
break *address #在地址為address的行設定斷點
1.3 delete和clear
每次使用break設定斷點都會分配一個斷點號,例如:
(gdb) b 16
Breakpoint 1 at 0x400512: file test.cc, line 16.
(gdb) b 17
Breakpoint 2 at 0x40051b: file test.cc, line 17.
要刪除斷點使用可以使用delete
命令:
delete [breakpoints num] [range...]
delete n #刪除n號斷點
delete m-n #刪除m-n號斷點
也可以使用clear
命令,clear是基於行的,不是刪除所有斷點:
clear n #刪除n行的所有斷點
clear function #刪除函式function的斷點
clear file:line #刪除檔案file第n行的所有斷點
clear file:function #刪除檔案:函式的所有斷點
1.4 檢視變數
print
命令
print命令用來在除錯程式時檢視變數值,可簡化為p。
print var #列印var的值
print *array@len #以{a, b, ...}格式列印動態陣列
print array #以{a, b, ...}格式列印靜態陣列
print file::var or print function::var #列印全域性變數
#指定輸出格式:
print/u[d|o|x...] ... #作為無符號數輸出
print/t ... #二進位制輸出
print/d ... #十進位制輸出
print/o ... #八進位制輸出
print/x ... #十六進位制輸出
print/a ... #十六進位制輸出
print/c ... #字元輸出
print/f ... #浮點數輸出
display
命令
display var #每次執行到斷點都列印var的值
1.5 程式執行時命令
1.run
命令
run命令用來開始執行程式,直到第一個斷點停止程式。可簡寫為r。例如:
(gdb) break 20
Breakpoint 1 at 0x40056d: file test.cc, line 20.
(gdb) run
Starting program: /home/chenjunhan/Learning/gdb/test Breakpoint 1, main () at test.cc:20
2.continue
命令
continue命令用來從上次斷點停止之後開始繼續執行程式,直到下一個斷點。可簡寫為c。例如:
(gdb) b 15
Breakpoint 1 at 0x40050b: file test.cc, line 15.
(gdb) b 16
Breakpoint 2 at 0x400512: file test.cc, line 16.
(gdb) r
Starting program: /home/chenjunhan/Learning/gdb/test
Breakpoint 1, main () at test.cc:15
(gdb) continue
Continuing.
Breakpoint 2, main () at test.cc:16
3.next
命令
next命令用於執行一行源程式程式碼,此行程式碼中的函式呼叫也一併執行;即“step over”,可簡寫為n。
4.step
命令
step命令用於執行一行源程式程式碼,如果此行程式碼中有函式呼叫,則進入該函式;即“step into”,可簡寫為s。
2 gdb除錯函式
2.1 列出可執行函式所有函式名稱
info|i functions
2.2 執行函式內容
next|n #不進入函式,直接執行函式並返回
step|s #進入函式,執行函式體
call|print function #任意位置直接執行函式
2.3 退出函式
finish #退出正在執行的函式,自動執行剩下的程式碼
return [expression] #退出正在執行的函式,後面的程式碼不執行,可以通過expression修改函式返回值
2.4 函式堆疊幀
info|i frame #顯示當前函式堆疊幀資訊,包括指令暫存器的值,區域性變數地址及值等資訊。
backtrace|bt #顯示堆疊幀層次結構
frame n|address #切換函式堆疊幀層次
up|down n #向上/下選擇n層函式堆疊幀
up-silently|down-silently n #同上,不過不列印資訊
3 gdb設定watchpoint
gdb可以使用watch
命令設定觀察點,也就是當一個變數值發生變化時,程式會停下來。例如:
int a = 0;
void *pthread_func(void *args)
{
while (1)
{
++a;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, pthread_func, NULL);
sleep(1000);
return 0;
}
使用catch|wa
觀察全域性變數a:
(gdb) file catch
Reading symbols from catch...done.
(gdb) break pthread_func
Breakpoint 1 at 0x400659: file catch.c, line 11.
(gdb) watch a
Hardware watchpoint 2: a
(gdb) r
Starting program: /home/chenjunhan/Learning/gdb/catch
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff77f6700 (LWP 6578)]
[Switching to Thread 0x7ffff77f6700 (LWP 6578)]
Breakpoint 1, pthread_func (args=0x0) at catch.c:11
11 ++a;
(gdb) continue
Continuing.
Hardware watchpoint 2: a
Old value = 0
New value = 1
pthread_func (args=0x0) at catch.c:12
12 sleep(1);
(gdb) continue
Continuing.
Breakpoint 1, pthread_func (args=0x0) at catch.c:11
11 ++a;
3.1 設定觀察點
catch var #為var設定觀察點,在run之前
catch *(type*)(address) #為地址address指向的變數設定觀察點
3.2 列出所有觀察點
info watchpoints
3.3 斷點控制命令
disable|enable|delete n
3.4 為watch指定生效執行緒
info threads #列出所有threads編號等資訊,thread_create之後
watch var thread t_num #只有編號為t_num的thread修改了var值才會停下來
3.4 只讀/讀寫觀察點,只對硬體觀察點才生效
rwatch|rw var #設定只讀觀察點每次訪問var都會停下來
wwatch|aw var #設定讀寫觀察點,當發生讀取或改變變數值的行為時,程式就會暫停住
4 gdb設定catchpoint
使用gdb除錯程式時,可以用tcatch
命令設定catchpoint只觸發一次。例如:
int main()
{
pid_t pid;
int i = 0;
for (i = 0;i < 4; ++i)
{
if ((pid = fork()) < 0) //fork error
exit(1);
else if (pid == 0) //child
exit(0);
}
printf("Hello World\n");//parent
return 0;
}
使用tcatch fork
為fork設定catchpoint
(gdb) tcatch fork
Catchpoint 1 (fork)
(gdb) r
Starting program: /home/chenjunhan/Learning/gdb/catch
Temporary catchpoint 1 (forked process 6838), 0x00007ffff7ad5ee4 in __libc_fork
() at ../nptl/sysdeps/unix/sysv/linux/x86_64/../fork.c:130
130 ../nptl/sysdeps/unix/sysv/linux/x86_64/../fork.c: 沒有那個檔案或目錄.
(gdb) c
Continuing.
Hello World
[Inferior 1 (process 6834) exited normally]
4.1 為fork/vfork/exec設定catchpoint
catch fork
catch vfork
catch exec
4.2 為系統呼叫設定catchpoint
catch syscall [syscall_name|syscall_num] #例如catch syscall mmap
catch syscall #為所有系統呼叫設定catchpoint
5 gdb高階列印技巧
5.1 列印區域性變數
- 使用
backtrace full
命令
(gdb) bt full
#0 fun_a () at a.c:6
a = 0
#1 0x000109b0 in fun_b () at a.c:12
b = 1
#2 0x000109e4 in fun_c () at a.c:19
c = 2
#3 0x00010a18 in fun_d () at a.c:26
d = 3
#4 0x00010a4c in main () at a.c:33
var = -1
- 使用
info locals
命令,列印當前函式區域性變數
(gdb) info locals
a = 0
5.2 列印列印程序記憶體資訊
info proc mappings #檢視記憶體映像資訊
info files #更詳細地輸出程序的記憶體資訊,包括引用的動態連結庫等
info target #功能同上
5.3 列印變數型別
typedef struct student_t{
char *name;
int age;
} student;
int main()
{
student s;
student *ps;
return 0;
}
使用wathis
和ptype
命令除錯,ptype可以列出詳細的型別。
(gdb) whatis s
type = student
(gdb) whatis ps
type = student *
(gdb) ptype s
type = struct student_t {
char *name;
int age;
}
5.4 列印變數所在檔案
info variables var
6 gdb應用於多程序/執行緒
6.1 多程序除錯
a.除錯正在執行的程序
#沒啟動gdb
ps -a | grep program #檢視程序id
gdb program pid #除錯
gdb --pid pid #同上
#啟動gdb
attach pid
#斷開
detach
b.除錯子程序
gdb除錯預設追蹤父程序,要追蹤子程序,則執行下列命令:
set follow-fork-mode child
c.同時除錯父程序和子程序
gdb除錯時,預設追蹤一個程序,使用下列命令可以同時除錯多個程序:
set detach-on-fork off #預設執行父程序,掛起其他程序
set schedule-multiple on #父程序,子程序一起執行
之後gdb預設追蹤父程序,其他程序被掛起,使用下列命令可以切換程序:
inferior infno #切換到子程序
info inferior #列印所有程序資訊
inferior n #選擇切換除錯程序
6.2 多執行緒除錯
a.顯示執行緒資訊
thread|_thread
檢視當前執行緒id
(gdb) thread
[Current thread is 1 (Thread 0x7ffff7fcc740 (LWP 9494))]
(gdb) printf "current thread:%d\n",$_thread
current thread:1
info threads
檢視所有執行緒資訊
(gdb) info threads
Id Target Id Frame
3 Thread 0x7ffff6ff5700 (LWP 9627) "thread" 0x00007ffff78b7dfd in nanosleep () at ../sysdeps/unix/syscall-template.S:81
2 Thread 0x7ffff77f6700 (LWP 9626) "thread" 0x00007ffff78b7dfd in nanosleep () at ../sysdeps/unix/syscall-template.S:81
1 Thread 0x7ffff7fcc740 (LWP 9622) "thread" main () at thread.cc:21
b.切換執行緒
thread id
c.只允許一個執行緒執行
gdb除錯程式時,一旦程式斷住,所有執行緒都會停止。當除錯其中一個執行緒時,所有執行緒都會開始執行。如果想除錯一個執行緒同時其他執行緒停止,可用以下命令:
set scheduler-locking on
6.3 除錯多個程式
#新增一個新的除錯程式。-copies指定執行多少份,預設1
add-inferior [-copies n] [-exec program]
#複製新增一個新的除錯程式,infno表示除錯程序編號,預設為當前inferior
clone-inferior [-copies -n] [infno]
info inferior #列出所有除錯的程序
maint info program-spaces #列出所有除錯的程序名稱,inferior編號,程序id
inferior n #切換程序
7 gdb分析core dump
7.1 core檔案
程式由於各種異常或者bug導致在執行過程中異常退出或者中止,並且在滿足一定條件下(這裡為什麼說需要滿足一定的條件呢?下面會分析)會產生一個叫做core的檔案。
通常情況下,core檔案會包含了程式執行時的記憶體,暫存器狀態,堆疊指標,記憶體管理資訊還有各種函式呼叫堆疊資訊等,我們可以理解為是程式工作當前狀態儲存生成第一個檔案,許多的程式出錯的時候都會產生一個core檔案,通過工具分析這個檔案,我們可以定位到程式異常退出的時候對應的堆疊呼叫等資訊,找出問題所在並進行及時解決。
ubuntu預設不生成core檔案,可用一下命令解決:
ulimit -c unlimited
7.2 gdb顯式生成core檔案
generate-core-file #先run
gcore #上面的簡寫
7.3 使用core檔案進行除錯
示例程式:
int main()
{
int *p = NULL;
*p = 0;
return 0;
}
程式分析:上面程式當執行*p = 0
時程式會crashed,產生core dump file,下面使用gdb進行除錯:
#shell環境載入core檔案
gdb test corefile
#同上,gdb環境中
file test
core corefile
#gdb載入core file之後列印的資訊
[New LWP 9672]
Core was generated by `./test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000000000040054b in main () at test.c:8
8 *p = 0;
#也可以使用where命令定位錯誤位置
(gdb) where
#0 0x000000000040054b in main () at test.c:8