調試器GDB的基本使用方法

 

GDB調試的三種方式:html

1. 目標板直接使用GDB進行調試。linux

2. 目標板使用gdbserver,主機使用xxx-linux-gdb做爲客戶端。c++

3. 目標板使用ulimit -c unlimited,生成core文件;而後主機使用xxx-linux-gdb ./test ./core。shell

 Brendan Gregg關於gdb介紹《gdb Debugging Full Example (Tutorial): ncurses》。sass

1. gdb調試

構造測試程序以下main.c和sum.c以下:架構

main.c:

#include <stdio.h> #include <stdlib.h> extern int sum(int value); struct inout { int value; int result; }; int main(int argc, char * argv[]) { struct inout * io = (struct inout * ) malloc(sizeof(struct inout)); if (NULL == io) { printf("Malloc failed.\n"); return -1; } if (argc != 2) { printf("Wrong para!\n"); return -1; } io -> value = *argv[1] - '0'; io -> result = sum(io -> value); printf("Your enter: %d, result:%d\n", io -> value, io -> result); return 0; }
sum.c:
int sum(int value) { int result = 0; int i = 0; for (i = 0; i < value; i++) result += (i + 1); return result; }

而後gcc main.c sum.c -o main -g, 獲得main可執行文件.app

下面介紹了gdb大部分功能,1.1 設置斷點以及 1.3顯示棧幀是經常使用功能;調試過程當中能夠須要1.6 單步執行,而且1.4 顯示變量1.5顯示寄存器1.8 監視點1.9 改變變量的值dom

若是進程已經運行中,須要1.11 attach到進程,或者1.10 生成轉儲文件進行分析。固然爲了提升效率能夠自定義1.13 初始化文件函數

1.1 設置斷點

設置斷點能夠經過b或者break設置斷點,斷點的設置能夠經過函數名、行號、文件名+函數名、文件名+行號以及偏移量、地址等進行設置。oop

格式爲:

break 函數名

break 行號

break 文件名:函數名

break 文件名:行號

break +偏移量

break -偏移量

break *地址

查看斷點,經過info break查看斷點列表。

刪除斷點經過命令包括:

delete <斷點id>:刪除指定斷點

delete:刪除全部斷點

clear

clear 函數名

clear 行號

clear 文件名:行號

clear 文件名:函數名

斷點還能夠條件斷住

break 斷點 if 條件;好比break sum if value==9,當輸入的value爲9的時候纔會斷住。

condition 斷點編號:給指定斷點刪除觸發條件

condition 斷點編號 條件:給指定斷點添加觸發條件

以下能夠看出,當入參爲9的時候被斷住,而入參爲8的時候運行到結束。

斷點還能夠經過disable/enable臨時停用啓用。

disable

disable 斷點編號

disable display 顯示編號

disable mem 內存區域

 

enable

enable 斷點編號

enable once 斷點編號:該斷點只啓用一次,程序運行到該斷點並暫停後,該斷點即被禁用。

enable delete 斷點編號

enable display 顯示編號

enable mem 內存區域

1.1.1 斷點commands高級功能

大多數時候須要在斷點處執行一系列動做,gdb提供了在斷點處執行命令的高級功能commands。

#include <stdio.h>

int total = 0;

int square(int i)
{
    int result=0;

    result = i*i;

    return result;
}

int main(int argc, char **argv)
{
    int i;

    for(i=0; i<10; i++)
    {
        total += square(i);
    }
    return 0;
}

好比須要對如上程序square參數i爲5的時候斷點,並在此時打印棧、局部變量以及total的值

編寫gdb.init以下:

set logging on gdb.log


b square if i == 5
commands
  bt full
  i locals
  p total
  print "Hit break when i == 5"
end

在gdb shell中source gdb.init,而後r執行命令,結果以下:

能夠看出斷點在i==5的時候斷住了,而且此時打印了正確的值。

1.2 運行

「gdb 命令」以後,run能夠在gdb下運行命令;若是命令須要參數則跟在run以後。

若是須要斷點在main()處,直接執行start就能夠。

1.3 顯示棧幀

若是遇到斷點而暫停執行,或者coredump能夠顯示棧幀。

經過bt能夠顯示棧幀,bt full能夠顯示局部變量。

命令格式以下:

bt

bt full:不只顯示backtrace,還顯示局部變量

bt N:顯示開頭N個棧幀

bt full N

1.4 顯示變量

「print 變量」能夠顯示變量內容。

若是須要一行監控多個變量,能夠經過p {var1, var2, var3}。

若是要跟蹤自動顯示,可使用display {var1, var2, var3}

1.5 顯示寄存器

info reg能夠顯示寄存器內容。

在寄存器名以前加$能夠顯示寄存器內容,

p $寄存器:顯示寄存器內容

p/x $寄存器:十六進制顯示寄存器內容。

 

用x命令能夠顯示內容內容,「x/格式 地址」。

x $pc:顯示程序指針內容

x/i $pc:顯示程序指針彙編。

x/10i $pc:顯示程序指針以後10條指令。

x/128wx 0xfc207000:從0xfc20700開始以16進制打印128個word。

還能夠經過disassemble指令來反彙編。

disassemble

disassemble 程序計數器 :反彙編pc所在函數的整個函數。

disassemble addr-0x40,addr+0x40:反彙編addr先後0x40大小。

1.6 單步執行

單步執行有兩個命令next和step,二者的區別是next遇到函數不會進入函數內部,step會執行到函數內部。

若是須要逐條彙編指令執行,能夠分別使用nexti和stepi。

1.7 繼續執行

 調試時,使用continue命令繼續執行程序。程序遇到斷電後再次暫停執行;若是沒有斷點,就會一直執行到結束。

continue:繼續執行

continue 次數:繼續執行必定次數。

1.8 監視點

要想找到變量在何處被改變,可使用watch命令設置監視點watchpoint。

watch <表達式>:表達式發生變化時暫停運行

awatch <表達式>:表達式被訪問、改變是暫停執行

rwatch <表達式>:表達式被訪問時暫停執行

其餘變種還包括watch expr [thread thread-id] [mask maskvalue],其中mask須要架構支持。

GDB不能監控一個常量,好比watch 0x600850報錯。

可是能夠watch *(int *)0x600850。

1.9 改變變量的值

「經過set variable <變量>=<表達式>」來修改變量的值。

 set $r0=xxx:設置r0寄存器的值爲xxx。

1.10 生成內核轉儲文件

經過「generate-core-file」生成core.xxxx轉儲文件。

而後gdb ./main ./core.xxxx查看恢復的現場。

另外一命令gcore能夠從命令行直接生成內核轉儲文件。

gcore `pidof 命令`:無需中止正在執行的程序已得到轉儲文件。

1.11 attach到進程 

若是程序已經運行,或者是調試陷入死循環而沒法返回控制檯進程,可使用attach命令。

attach pid

經過ps aux能夠查看進程的pid,而後使用bt查看棧幀。

以top爲例操做步驟爲:

1. ps -aux查看進程pid,爲16974.

2. sudo gdb attach 16974,使用gdb 附着到top命令。

3. 使用bt full查看,當前棧幀。此時使用print等查看信息。

4. 還能夠經過info proc查看進程信息。

1.12 反覆執行

continue、step、stepi、next、nexti均可以指定重複執行的次數。

ignore 斷點編號 次數:能夠忽略指定次數斷點。

1.13 初始化文件

Linux環境下初始化文件爲.gdbinit。

若是存在.gdbinit文件,gdb在啓動的以前就將其做爲命令文件運行。

初始化文件和命令文件執行順序爲:HOME/.gdbinit > 運行命令行選項 > ./.gdbinit > -x指定命令文件。

1.14 設置源碼目錄

調試過程當中若是須要關聯到源碼,查看更詳細的信息。

能夠經過directory或者set substitute-path來制定源碼目錄。

1.15 TUI調試

TUI(TextUser Interface)爲GDB調試的文本用戶界面,能夠方便地顯示源代碼、彙編和寄存器文本窗口。

源代碼窗口和彙編窗口會高亮顯示程序運行位置並以'>'符號標記。有兩個特殊標記用於標識斷點,第一個標記用於標識斷點類型:

B:程序至少有一次運行到了該斷點

b:程序沒有運行到過該斷點

H:程序至少有一次運行到了該硬件斷點

h:程序沒有運行到過該硬件斷點 

第二個標記用於標識斷點使能與否:

+:斷點使能Breakpointis enabled. -:斷點被禁用Breakpointis disabled. 

當調試程序時,源代碼窗口、彙編窗口和寄存器窗口的內容會自動更新。

1.16 Catchpoint

catch能夠根據某些類型事件來中止程序執行。

能夠經過catch syscall close,捕捉產生系統調用close的時候中止程序執行。

其餘的catch事件還包括,throw、syscall、assert、exception等等。

1.17 自定義腳本

命令行的入參能夠經過argc和*argv獲取。

1.17.0 註釋、賦值、顯示

# - 爲腳本添加註釋。

set - 爲變量賦值,以$開頭,以便區分gdb仍是調試程序變量。

例如:set $x = 1

顯示變量能夠經過echo、printf。

1.17.1自定義命令

利用define命令能夠自行定義命令,還可使用document命令給自定義命令添加說明。

define adder
  if $argc == 2
    print $arg0 + $arg1
  end
  if $argc == 3
    print $arg0 + $arg1 + $arg2
  end
end

document adder
  Sum two or three variables.
end

執行bf自定義命令,結果以下。

 

無行參聲明,但能夠直接用$arg0,$arg1引用, $argc 爲形參個數

1.17.2 條件語句

條件命令:if...else...end。這個同其它語言中提供的if命令沒什麼區別,只是注意結尾的end

1.17.3 循環語句

循環命令:while...end。gdb一樣提供了loop_breakloop_continue命令分別對應其它語言中的breakcontinue,另外一樣注意結尾的end

set logging on overwrite gdb.log------------將顯示log保存到gdb.log中。 set pagination off--------------------------關閉分頁顯示功能。

tar jtag jtag://localhost:1025--------------鏈接上JTAG。

d-------------------------------------------刪除現有斷點。

b func_a------------------------------------在func_a增長斷點。
commands------------------------------------斷點後,執行以下命令。
  b func_b----------------------------------在func_a斷點以後,在func_b增長斷點。
    commands
bt full-------------------------------打印func_b處棧幀。 c-------------------------------------繼續執行。 end b file
.c:555------------------------------在file.c的555行增長斷點 commands while 1-------------------------------無限執行next命令。 next end end c-----------------------------------------繼續執行,纔會觸發func_b和file.c:555斷點。 end c-------------------------------------------是程序獲得繼續執行。

在命令行gdb -x gdb.init bin;或者gdb bin,而後在命令行soruce gdb.init一樣能夠更新腳本。

1.18 dump內存到指定文件

在gdb調試中可能須要將一段內存導出到文件中,能夠藉助dump命令。

命令格式:

dump binary memory FILE START STOP

好比dump binary memory ./dump.bin 0x0 0x008000000,將內存區間從0x0到0x00800000導出到dump.bin中。

2. gdb+gdbserver遠程調試

目標板gdbserver+主機gdb遠程調試的方式,比較適合目標板性能受限,只能提供gdbserver功能。

在主機上執行gdb進行遠程調試。測試程序以下。

#include <stdio.h>

void C(int *p)
{
    *p = 0x12;
}

void B(int *p)
{
    C(p);
}
void A(int *p)
{
    B(p);
}
void A2(int *p)
{
    C(p);
}
int main(int argc, char **argv)
{
    int a;
    int *p = NULL;
    A2(&a);  // A2 > C
    printf("a = 0x%x\n", a);
    A(p);    // A > B > C
    return 0;
}

對目標板的設置方式是:開啓端口2345做爲gdbserver銅線端口。

gdbserver :2345 test_debug

主機上執行gdb test_debug,而後tar remote 192.168.2.84.2345鏈接遠程gdbserver。

目標板會收到「Remote debugging from host 192.168.33.77」消息,表示二者鏈接成功。

主機上就能夠進行遠程調試,continue以後兩端獲得的結果以下:

目標板輸出「a=0x12」以後中止運行,

主機上獲得SIGSEGV,並能夠查看backtrace信息。能夠看出問題點在指針p指向NULL,0指針賦值錯誤。

3. 經過core+gdb離線分析

在目標板上執行ulimit -c unlimited,執行應用程序。

程序出錯後,會在當前目錄下生成core文件。

將core文件拷出後,再PC上執行xxx-linux-gdb ./test ./core進行分析。

3.1 加載庫文件

在運行xxx-linux-gdb ./test ./core以後,可能存在庫文件關聯不上的狀況。

使用info sharedlibrary,查看庫加載狀況。

From        To          Syms Read   Shared Object Library
                        No          xxx.so
                        No          /lib/libdl.so.2
                        No          /lib/libpthread.so.0
0x2ab6ec00  0x2ac09ba4  Yes         xxx/lib/libstdc++.so.6
                        No          /lib/libm.so.6
0x2acec460  0x2acf626c  Yes         xxx/lib/libgcc_s.so.1
                        No          /lib/libc.so.6
                        No          /lib/ld.so.1

能夠經過set solib-search-pathset solib-absolute-prefix來設置,對應庫所在的路徑。

From        To          Syms Read   Shared Object Library
0x2aaca050  0x2aacc8d0  Yes         xxx.so
0x2aad0ad0  0x2aad17ac  Yes (*)     xxx/lib/libdl.so.2
0x2aad8a50  0x2aae7434  Yes (*)     xxx/lib/libpthread.so.0
0x2ab6ec00  0x2ac09ba4  Yes         xxx/lib/libstdc++.so.6
0x2ac4b3d0  0x2acb1988  Yes         xxx/lib/libm.so.6
0x2acec460  0x2acf626c  Yes         xxx/lib/libgcc_s.so.1
0x2ad17b80  0x2adf699e  Yes         xxx/lib/libc.so.6
0x2aaa89e0  0x2aabf66c  Yes (*)     xxx/lib/ld.so.1
(*): Shared library is missing debugging information.

能夠看出相關庫文件都已經加載,只是部分庫文件沒有調試信息。

3.2 查看backtrace

查看coredump的backtrace經過bt便可,更全的信息經過bt full。

產看函數調用棧的幾個函數

bt:顯示全部的函數調用棧幀的信息,每一個幀一行。

bt n:顯示棧定的n個幀信息。

bt -n:顯示棧底的n個幀信息。

bt full:顯示棧中全部幀的徹底信息如:函數參數,本地變量。

bt full n:用法同上。

bt full -n

(gdb) bt
#0  0x2ad71f1e in memcpy () from xxx/lib/libc.so.6
#1  0x2ad71ac0 in memmove () from xxx/lib/libc.so.6
#2  0x0011f36c in std::__copy_move<false, true, std::random_access_iterator_tag>::__copy_m<unsigned char> (__first=0x34dfb008 "\377\330\377", <incomplete sequence \340>, __last=0x34eeea2c "", 
    ...
#3  0x0011ee22 in std::__copy_move_a<false, unsigned char*, unsigned char*> (__first=0x34dfb008 "\377\330\377", <incomplete sequence \340>, __last=0x34eeea2c "", __result=0x2b2013c0 "\377\330\377", <incomplete sequence \340>)
    at xxxinclude/c++/6.3.0/bits/stl_algobase.h:386
#4  0x0011e7e2 in std::__copy_move_a2<false, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char> >, unsigned char*> (__first=..., __last=..., __result=0x2b2013c0 "\377\330\377", <incomplete sequence \340>)
    at xxx/bits/stl_algobase.h:424
#5  0x0011dfd2 in std::copy<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char> >, unsigned char*> (__first=..., __last=..., __result=0x2b2013c0 "\377\330\377", <incomplete sequence \340>)
    at xxx/6.3.0/bits/stl_algobase.h:456
#6  0x0011c948 in xxx
#7  0x00133e08 in xxx
#8  0x2aada31e in start_thread () from xxx/libc/lib/libpthread.so.0
#9  0x005a11b4 in ?? ()

3.3 Core Dump核心轉存儲文件目錄和命名規則

默認狀況下core文件存在應用當前路徑下,爲了區分能夠進行設置。

區分core主要經過/proc/sys/kernel/core_uses_pid和/proc/sys/kernel/core_pattern進行設置。

/proc/sys/kernel/core_uses_pid:能夠控制產生的core文件的文件名中是否添加pid做爲擴展,若是添加則文件內容爲1,不然爲0。

proc/sys/kernel/core_pattern:能夠設置格式化的core文件保存位置或文件名,好比原來文件內容是core-%e

echo "/tmp/core-%e-%p" > core_pattern。

將會控制所產生的core文件會存放到/corefile目錄下,產生的文件名爲core-命令名-pid-時間戳

如下是參數列表:
    %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 添加命令名

固然,你能夠用下列方式來完成:

sysctl -w kernel.core_pattern=/tmp/core-%e-%p

3.4 ulimit的使用

功能說明:控制shell程序的資源。

語  法:ulimit [-aHS][-c <core文件上限>][-d <數據節區大小>][-f <文件大小>][-m <內存大小>][-n <文件數目>][-p <緩衝區大小>][-s <堆疊大小>][-t <CPU時間>][-u <程序數目>][-v <虛擬內存大小>]

補充說明:ulimit爲shell內建指令,可用來控制shell執行程序的資源。

參  數:
   -a  顯示目前資源限制的設定。 
   -c <core文件上限>  設定core文件的最大值,單位爲區塊。 
   -d <數據節區大小>  程序數據節區的最大值,單位爲KB。 
   -f <文件大小>  shell所能創建的最大文件,單位爲區塊。 
   -H  設定資源的硬性限制,也就是管理員所設下的限制。 
   -m <內存大小>  指定可以使用內存的上限,單位爲KB。 
   -n <文件數目>  指定同一時間最多可開啓的文件數。 
   -p <緩衝區大小>  指定管道緩衝區的大小,單位512字節。 
   -s <堆疊大小>  指定堆疊的上限,單位爲KB。 
   -S  設定資源的彈性限制。 
   -t <CPU時間>  指定CPU使用時間的上限,單位爲秒。 
   -u <程序數目>  用戶最多可開啓的程序數目。 
   -v <虛擬內存大小>  指定可以使用的虛擬內存上限,單位爲KB。

4. GDB小技巧

4.1 關閉 Type <return> to continue, or q <return> to quit---

當現實內容多的時候,GDB會強制分頁,現實就會暫停。可是可能並不須要,能夠經過set pagination off關閉。

4.2 附着到已運行kernel

在已運行的Linux上,若是發生死機異常等問題,這時候定位問題須要使用jtag鏈接上。

鏈接方法是:

gdb-----------------------------------------------進入gdb shell。
target remote localhost:1025-------------------在gdb shell中經過ip:port鏈接上target。
file vmlinux----------------------------------------加載符號表。

 

而後就能夠在線查看運行狀態了。

 

參考文檔:

Linux環境下段錯誤的產生緣由及調試方法小結》《linux應用調試技術之GDB和GDBServer》《gdbServer + gdb 調試

相關文章
相關標籤/搜索