1. 程式人生 > >GDB除錯工具入門

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 檢視變數

  1. 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 ...             #浮點數輸出
  1. 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;
}

使用wathisptype命令除錯,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

參考文獻