目錄
- 一、概述
- 二、gdb調(diào)試
- 2.1、設(shè)置斷點
- 2.2、運行
- 2.3、顯示棧幀
- 2.4、顯示變量
- 2.5、顯示寄存器
- 2.6、單步執(zhí)行
- 2.7、繼續(xù)執(zhí)行
- 2.8、監(jiān)視點
- 2.9、改變變量的值
- 2.10、生成內(nèi)核轉(zhuǎn)儲文件
- 2.11、attach到進程
- 2.12、反復(fù)執(zhí)行
- 2.13、初始化文件
- 2.14、設(shè)置源碼目錄
- 2.15、TUI調(diào)試
- 2.16、Catchpoint
- 2.17、自定義腳本
- 2.17.0、注釋、賦值、顯示
- 2.17.1、自定義命令
- 2.17.2、條件語句
- 2.17.3、循環(huán)語句
- 2.18、dump內(nèi)存到指定文件
- 三、gdb+gdbserver遠(yuǎn)程調(diào)試
- 四、通過core+gdb離線分析
- 4.1、加載庫文件
- 4.2、查看backtrace
- 4.3、Core Dump核心轉(zhuǎn)存儲文件目錄和命名規(guī)則
- 4.4、ulimit的使用
- 五、GDB小技巧
- 5.1、關(guān)閉
- 5.2、附著到已運行kernel
一、概述
GDB調(diào)試的三種方式:
1. 目標(biāo)板直接使用GDB進行調(diào)試。
2. 目標(biāo)板使用gdbserver,主機使用xxx-linux-gdb作為客戶端。
3. 目標(biāo)板使用ulimit -c unlimited,生成core文件;然后主機使用xxx-linux-gdb ./test ./core。
二、gdb調(diào)試
構(gòu)造測試程序如下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可執(zhí)行文件.
下面介紹了gdb大部分功能,1.1 設(shè)置斷點以及 1.3顯示棧幀是常用功能;調(diào)試過程中可以需要1.6 單步執(zhí)行,并且1.4 顯示變量、1.5顯示寄存器、1.8 監(jiān)視點、1.9 改變變量的值。
如果進程已經(jīng)運行中,需要1.11 attach到進程,或者1.10 生成轉(zhuǎn)儲文件進行分析。當(dāng)然為了提高效率可以自定義1.13 初始化文件。
2.1、設(shè)置斷點
設(shè)置斷點可以通過b或者break設(shè)置斷點,斷點的設(shè)置可以通過函數(shù)名、行號、文件名+函數(shù)名、文件名+行號以及偏移量、地址等進行設(shè)置。
格式為:
break 函數(shù)名
break 行號
break 文件名:函數(shù)名
break 文件名:行號
break +偏移量
break -偏移量
break *地址
查看斷點,通過info break查看斷點列表。
刪除斷點通過命令包括:
delete <斷點id>:刪除指定斷點
delete:刪除所有斷點
clear
clear 函數(shù)名
clear 行號
clear 文件名:行號
clear 文件名:函數(shù)名
斷點還可以條件斷住
break 斷點 if 條件;比如break sum if value==9,當(dāng)輸入的value為9的時候才會斷住。
condition 斷點編號:給指定斷點刪除觸發(fā)條件
condition 斷點編號 條件:給指定斷點添加觸發(fā)條件
如下可以看出,當(dāng)入?yún)?的時候被斷住,而入?yún)?的時候運行到結(jié)束。
斷點還可以通過disable/enable臨時停用啟用。
disable
disable 斷點編號
disable display 顯示編號
disable mem 內(nèi)存區(qū)域
enable
enable 斷點編號
enable once 斷點編號:該斷點只啟用一次,程序運行到該斷點并暫停后,該斷點即被禁用。
enable delete 斷點編號
enable display 顯示編號
enable mem 內(nèi)存區(qū)域
2.1.1、斷點commands高級功能
大多數(shù)時候需要在斷點處執(zhí)行一系列動作,gdb提供了在斷點處執(zhí)行命令的高級功能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參數(shù)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執(zhí)行命令,結(jié)果如下:
可以看出斷點在i==5的時候斷住了,并且此時打印了正確的值。
2.2、運行
“gdb 命令”之后,run可以在gdb下運行命令;如果命令需要參數(shù)則跟在run之后。
如果需要斷點在main()處,直接執(zhí)行start就可以。
2.3、顯示棧幀
如果遇到斷點而暫停執(zhí)行,或者coredump可以顯示棧幀。
通過bt可以顯示棧幀,bt full可以顯示局部變量。
命令格式如下:
bt
bt full:不僅顯示backtrace,還顯示局部變量
bt N:顯示開頭N個棧幀
bt full N
2.4、顯示變量
“print 變量”可以顯示變量內(nèi)容。
如果需要一行監(jiān)控多個變量,可以通過p {var1, var2, var3}。
如果要跟蹤自動顯示,可以使用display {var1, var2, var3}
2.5、顯示寄存器
info reg可以顯示寄存器內(nèi)容。
在寄存器名之前加$可以顯示寄存器內(nèi)容,
p $寄存器:顯示寄存器內(nèi)容
p/x $寄存器:十六進制顯示寄存器內(nèi)容。
用x命令可以顯示內(nèi)容內(nèi)容,“x/格式 地址”。
x $pc:顯示程序指針內(nèi)容
x/i $pc:顯示程序指針匯編。
x/10i $pc:顯示程序指針之后10條指令。
x/128wx 0xfc207000:從0xfc20700開始以16進制打印128個word。
還可以通過disassemble指令來反匯編。
disassemble
disassemble 程序計數(shù)器:反匯編pc所在函數(shù)的整個函數(shù)。
disassemble addr-0x40,addr+0x40:反匯編addr前后0x40大小。
2.6、單步執(zhí)行
單步執(zhí)行有兩個命令next和step,兩者的區(qū)別是next遇到函數(shù)不會進入函數(shù)內(nèi)部,step會執(zhí)行到函數(shù)內(nèi)部。
如果需要逐條匯編指令執(zhí)行,可以分別使用nexti和stepi。
2.7、繼續(xù)執(zhí)行
調(diào)試時,使用continue命令繼續(xù)執(zhí)行程序。程序遇到斷電后再次暫停執(zhí)行;如果沒有斷點,就會一直執(zhí)行到結(jié)束。
continue:繼續(xù)執(zhí)行
continue 次數(shù):繼續(xù)執(zhí)行一定次數(shù)。
2.8、監(jiān)視點
要想找到變量在何處被改變,可以使用watch命令設(shè)置監(jiān)視點watchpoint。
watch <表達(dá)式>:表達(dá)式發(fā)生變化時暫停運行
awatch<表達(dá)式>:表達(dá)式被訪問、改變是暫停執(zhí)行
rwatch<表達(dá)式>:表達(dá)式被訪問時暫停執(zhí)行
其他變種還包括watch expr [thread thread-id] [mask maskvalue],其中mask需要架構(gòu)支持。
GDB不能監(jiān)控一個常量,比如watch 0x600850報錯。
但是可以watch *(int *)0x600850。
2.9、改變變量的值
“通過set variable <變量>=<表達(dá)式>”來修改變量的值。
set $r0=xxx:設(shè)置r0寄存器的值為xxx。
2.10、生成內(nèi)核轉(zhuǎn)儲文件
通過“generate-core-file”生成core.xxxx轉(zhuǎn)儲文件。
然后gdb ./main ./core.xxxx查看恢復(fù)的現(xiàn)場。
另一命令gcore可以從命令行直接生成內(nèi)核轉(zhuǎn)儲文件。
gcore `pidof 命令`:無需停止正在執(zhí)行的程序已獲得轉(zhuǎn)儲文件。
2.11、attach到進程
如果程序已經(jīng)運行,或者是調(diào)試陷入死循環(huán)而無法返回控制臺進程,可以使用attach命令。
attach pid
通過ps aux可以查看進程的pid,然后使用bt查看棧幀。
以top為例操作步驟為:
1. ps -aux查看進程pid,為16974.
2. sudo gdb attach 16974,使用gdb 附著到top命令。
3. 使用bt full查看,當(dāng)前棧幀。此時使用print等查看信息。
4. 還可以通過info proc查看進程信息。
2.12、反復(fù)執(zhí)行
continue、step、stepi、next、nexti都可以指定重復(fù)執(zhí)行的次數(shù)。
ignore 斷點編號 次數(shù):可以忽略指定次數(shù)斷點。
2.13、初始化文件
Linux環(huán)境下初始化文件為.gdbinit。
如果存在.gdbinit文件,gdb在啟動的之前就將其作為命令文件運行。
初始化文件和命令文件執(zhí)行順序為:HOME/.gdbinit > 運行命令行選項 > ./.gdbinit > -x指定命令文件。
2.14、設(shè)置源碼目錄
調(diào)試過程中如果需要關(guān)聯(lián)到源碼,查看更詳細(xì)的信息。
可以通過directory或者set substitute-path來制定源碼目錄。
2.15、TUI調(diào)試
TUI(TextUserInterface)為GDB調(diào)試的文本用戶界面,可以方便地顯示源代碼、匯編和寄存器文本窗口。
源代碼窗口和匯編窗口會高亮顯示程序運行位置并以'>'符號標(biāo)記。有兩個特殊標(biāo)記用于標(biāo)識斷點,第一個標(biāo)記用于標(biāo)識斷點類型:
B:
程序至少有一次運行到了該斷點
b:
程序沒有運行到過該斷點
H:
程序至少有一次運行到了該硬件斷點
h:
程序沒有運行到過該硬件斷點
第二個標(biāo)記用于標(biāo)識斷點使能與否:
+:
斷點使能Breakpointisenabled.-:
斷點被禁用Breakpointisdisabled.
當(dāng)調(diào)試程序時,源代碼窗口、匯編窗口和寄存器窗口的內(nèi)容會自動更新。
2.16、Catchpoint
catch可以根據(jù)某些類型事件來停止程序執(zhí)行。
可以通過catch syscall close,捕捉產(chǎn)生系統(tǒng)調(diào)用close的時候停止程序執(zhí)行。
其他的catch事件還包括,throw、syscall、assert、exception等等。
2.17、自定義腳本
命令行的入?yún)⒖梢酝ㄟ^argc和*argv獲取。
2.17.0、注釋、賦值、顯示
# - 為腳本添加注釋。
set - 為變量賦值,以$開頭,以便區(qū)分gdb還是調(diào)試程序變量。
例如:set $x = 1
顯示變量可以通過echo、printf。
2.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
執(zhí)行bf自定義命令,結(jié)果如下。
無行參聲明,但可以直接用$arg0,$arg1引用, $argc 為形參個數(shù)
2.17.2、條件語句
條件命令:if...else...end
。這個同其它語言中提供的if
命令沒什么區(qū)別,只是注意結(jié)尾的end
。
2.17.3、循環(huán)語句
循環(huán)命令:while...end
。gdb同樣提供了loop_break
和loop_continue
命令分別對應(yīng)其它語言中的break
和continue
,另外同樣注意結(jié)尾的end
。
set logging on overwrite gdb.log------------將顯示log保存到gdb.log中。
set pagination off--------------------------關(guān)閉分頁顯示功能。
tar jtag jtag://localhost:1025--------------連接上JTAG。
d-------------------------------------------刪除現(xiàn)有斷點。
b func_a------------------------------------在func_a增加斷點。
commands------------------------------------斷點后,執(zhí)行如下命令。
b func_b----------------------------------在func_a斷點之后,在func_b增加斷點。
commands bt full-------------------------------打印func_b處棧幀。
c-------------------------------------繼續(xù)執(zhí)行。
end
b file.c:555------------------------------在file.c的555行增加斷點
commands
while 1-------------------------------無限執(zhí)行next命令。
next
end
end
c-----------------------------------------繼續(xù)執(zhí)行,才會觸發(fā)func_b和file.c:555斷點。
end
c-------------------------------------------是程序得到繼續(xù)執(zhí)行。
在命令行g(shù)db -x gdb.init bin;或者gdb bin,然后在命令行soruce gdb.init同樣可以更新腳本。
2.18、dump內(nèi)存到指定文件
在gdb調(diào)試中可能需要將一段內(nèi)存導(dǎo)出到文件中,可以借助dump命令。
命令格式:
dump binary memory FILE START STOP
比如dump binary memory ./dump.bin 0x0 0x008000000,將內(nèi)存區(qū)間從0x0到0x00800000導(dǎo)出到dump.bin中。
三、gdb+gdbserver遠(yuǎn)程調(diào)試
目標(biāo)板gdbserver+主機gdb遠(yuǎn)程調(diào)試的方式,比較適合目標(biāo)板性能受限,只能提供gdbserver功能。
在主機上執(zhí)行g(shù)db進行遠(yuǎn)程調(diào)試。測試程序如下。
#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;
}
對目標(biāo)板的設(shè)置方式是:開啟端口2345作為gdbserver銅線端口。
gdbserver :2345 test_debug
主機上執(zhí)行g(shù)db test_debug,然后tar remote 192.168.2.84.2345連接遠(yuǎn)程gdbserver。
目標(biāo)板會收到“Remote debugging from host 192.168.33.77”消息,表示兩者連接成功。
主機上就可以進行遠(yuǎn)程調(diào)試,continue之后兩端得到的結(jié)果如下:
目標(biāo)板輸出“a=0x12”之后停止運行,
主機上得到SIGSEGV,并可以查看backtrace信息。可以看出問題點在指針p指向NULL,0指針賦值錯誤。
四、通過core+gdb離線分析
在目標(biāo)板上執(zhí)行ulimit -c unlimited,執(zhí)行應(yīng)用程序。
程序出錯后,會在當(dāng)前目錄下生成core文件。
將core文件拷出后,再PC上執(zhí)行xxx-linux-gdb ./test ./core進行分析。
4.1、加載庫文件
在運行xxx-linux-gdb ./test ./core之后,可能存在庫文件關(guān)聯(lián)不上的情況。
使用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-path和set solib-absolute-prefix來設(shè)置,對應(yīng)庫所在的路徑。
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.
可以看出相關(guān)庫文件都已經(jīng)加載,只是部分庫文件沒有調(diào)試信息。
4.2、查看backtrace
查看coredump的backtrace通過bt即可,更全的信息通過bt full。
產(chǎn)看函數(shù)調(diào)用棧的幾個函數(shù)
bt:顯示所有的函數(shù)調(diào)用棧幀的信息,每個幀一行。
bt n:顯示棧定的n個幀信息。
bt -n:顯示棧底的n個幀信息。
bt full:顯示棧中所有幀的完全信息如:函數(shù)參數(shù),本地變量。
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 ?? ()
4.3、Core Dump核心轉(zhuǎn)存儲文件目錄和命名規(guī)則
默認(rèn)情況下core文件存在應(yīng)用當(dāng)前路徑下,為了區(qū)分可以進行設(shè)置。
區(qū)分core主要通過/proc/sys/kernel/core_uses_pid和/proc/sys/kernel/core_pattern進行設(shè)置。
/proc/sys/kernel/core_uses_pid:可以控制產(chǎn)生的core文件的文件名中是否添加pid作為擴展,如果添加則文件內(nèi)容為1,否則為0。
proc/sys/kernel/core_pattern:可以設(shè)置格式化的core文件保存位置或文件名,比如原來文件內(nèi)容是core-%e
echo "/tmp/core-%e-%p" > core_pattern。
將會控制所產(chǎn)生的core文件會存放到/corefile目錄下,產(chǎn)生的文件名為core-命令名-pid-時間戳
以下是參數(shù)列表:
%p - insert pid into filename 添加pid
%u - insert current uid into filename 添加當(dāng)前uid
%g - insert current gid into filename 添加當(dāng)前gid
%s - insert signal that caused the coredump into the filename 添加導(dǎo)致產(chǎn)生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 添加命令名
當(dāng)然,你可以用下列方式來完成:
sysctl -w kernel.core_pattern=/tmp/core-%e-%p
4.4、ulimit的使用
功能說明:控制shell程序的資源。
語法:ulimit [-aHS][-c <core文件上限>][-d <數(shù)據(jù)節(jié)區(qū)大小>][-f <文件大小>][-m <內(nèi)存大小>][-n <文件數(shù)目>][-p <緩沖區(qū)大小>][-s <堆疊大小>][-t <CPU時間>][-u <程序數(shù)目>][-v <虛擬內(nèi)存大小>]
補充說明:ulimit為shell內(nèi)建指令,可用來控制shell執(zhí)行程序的資源。
參數(shù):
-a 顯示目前資源限制的設(shè)定。
-c <core文件上限> 設(shè)定core文件的最大值,單位為區(qū)塊。
-d <數(shù)據(jù)節(jié)區(qū)大小> 程序數(shù)據(jù)節(jié)區(qū)的最大值,單位為KB。
-f <文件大小> shell所能建立的最大文件,單位為區(qū)塊。
-H 設(shè)定資源的硬性限制,也就是管理員所設(shè)下的限制。
-m <內(nèi)存大小> 指定可使用內(nèi)存的上限,單位為KB。
-n <文件數(shù)目> 指定同一時間最多可開啟的文件數(shù)。
-p <緩沖區(qū)大小> 指定管道緩沖區(qū)的大小,單位512字節(jié)。
-s <堆疊大小> 指定堆疊的上限,單位為KB。
-S 設(shè)定資源的彈性限制。
-t <CPU時間> 指定CPU使用時間的上限,單位為秒。
-u <程序數(shù)目> 用戶最多可開啟的程序數(shù)目。
-v <虛擬內(nèi)存大小> 指定可使用的虛擬內(nèi)存上限,單位為KB。
五、GDB小技巧
5.1、關(guān)閉
Type <return> to continue, or q <return> to quit---
當(dāng)現(xiàn)實內(nèi)容多的時候,GDB會強制分頁,現(xiàn)實就會暫停。但是可能并不需要,可以通過set pagination off關(guān)閉。
5.2、附著到已運行kernel
在已運行的Linux上,如果發(fā)生死機異常等問題,這時候定位問題需要使用jtag連接上。
連接方法是:
gdb-----------------------------------------------進入gdb shell。
target remote localhost:1025-------------------在gdb shell中通過ip:port連接上target。
file vmlinux----------------------------------------加載符號表。
然后就可以在線查看運行狀態(tài)了。
以上就是詳解Linux下調(diào)試器GDB的基本使用方法的詳細(xì)內(nèi)容,更多關(guān)于Linux 調(diào)試器GDB的資料請關(guān)注腳本之家其它相關(guān)文章!