gdb 除錯技術
.gdbinit 設定
用戶根目錄下設置 .gdbinit 文件,文件內容如下:
set history save on
set history size 10000
set history filename ~/.gdb_history
set print pretty on
set print static-members off
set charset ASCII
GDB控制程序運行過程
- 設置環境變量與配置變量
(gdb) set env USER_NAME=smith
(gdb) show env
- 設置信號與中斷
(gdb) handle SIGINT ignore
- 設置寄存器
(gdb) set $PC = main
- 時候調試(內存轉儲)
$ gdb xxxx core
$(gdb) where
- 監視點
(gdb) watch a[0]
(gdb) watch *0x80049874
- 函數斷點
(gdb) b method_name
(gdb) b *method_name ; 函數調用前加斷點,可以觀察參數/返回地址/幀地址等的壓棧過程
- 條件斷點
(gdb) break 38 if a[0] == 0
- 線程斷點
(gdb) break **lines** thread **thread_no**
- 顯示信息
命令 | 說明 |
---|---|
info line xxx | xxx在內存中的地址(行號,函數名) |
info f | 當前棧信息 |
info args | 當前函數名及參數 |
info locals | 當前函數的局部變量 |
info catch | 當前函數異常處理信息 |
info thread | 當前線程信息 |
info proc mapping | 當前程序的內存映像信息 |
- 源碼搜索
(gdb) search <regexp> ; 向前找
(gdb) reverse_search <regexp> ; 全部搜索
- 指定源文件路徑 gdb啟動時通過 -d 來制定路徑
(gdb) dir <dirname:--:-->
(gdb) show directories ; 顯示源文件路徑
- 彙編顯示
(gdb) disas **func**
(gdb) x/5i 0xaddress ; 如果沒有符號情報,使用該方法
- 彙編及調試
(gdb) display /i $PC
(gdb) si ; 執行一條彙編指令
- 顯示內存值
(gdb) x/nuh (b,w,g,i,s)
(gdb) x/4i $PC
(gdb) x/16xb $SP
(gdb) x/s *(argv+1)
符號 | 說明 |
---|---|
s | 字符串 |
n | 顯示單位字節個數 |
b | 1個字節單位 |
h | 2個字節單位 |
w | 4個字節單位 |
g | 8個字節單位 |
i | 指令 |
- 顯示線程執行狀態
(gdb) thread apply **n** where ; n是線程編號
- 顯示動態數組
(gdb) p *array@len
- 運行時設置參數
% gdb --args program --foo --bar
(gdb) run
# or
(gdb) set args ....
(gdb) show args
- 調試已運行程序
;; 方法1
(gdb) <program> PID
;; 方法2
(gdb) attach <program>
- 設置觀察點
(gdb) watch <expr> ; 變化時
(gdb) rwatch <expr> ; 被讀時
(gdb) awatch <expr> ; 讀寫時
- 設置捕捉點
(gdb) catch <event> ; event可以是C++關鍵字(throw, catch), 系統調用(exec,fork,vfork)等
(gdb) tcatch <event> ; 只設置一次捕捉點,用完就刪除
- 輸出格式
符號 | 說明 |
---|---|
p/u | 16進制,無符號 |
p/a | 16進制 |
p/f | 浮點數 |
p/x | 16進制 |
p/t | 2進制 |
p/o | 8進制 |
p/c | 字符 |
- 程序跳轉
(gdb) jump <line>
(gdb) jump <address>
(gdb) set $PC=xxxx
- 其他顯示選項
命令 | 說明 |
---|---|
set print vtbl | 是否用規格的格式顯示虛函數表 |
set print address on/off | 是否顯示函數的參數地址 |
set print elements <numbers of elements> | 指定顯示數組的最大元素個數 |
set print null-stop on/off | 是否當遇到字符串結束符後停止 |
set print pretty on/off | 比較漂亮的顯示結構體 |
set print union on/off | 是否顯示結構體內的聯合數據 |
- Frame相關
(gdb) bt ; 顯示上下文
(gdb) frame ; 顯示當前幀
(gdb) frame n ; 顯示第n號幀
(gdb) up / down ; 向上或下移動幀
(gdb) i frame 1 ; 詳細顯示1號幀情報
用GDB調試嵌入式系統
#cd /opt
#tar xzvf /tmp/gdb-6.6.tar.gz
#cd /opt
#mkdir -p arm-gdb/build
#cd arm-gdb/build
#/opt/gdb-6.6/configure --target=arm-linux --prefix=/opt/arm-gdb
#make
#make install
#cd /opt/arm-gdb/bin/
#cp arm-linux-gdb /usr/bin/
#cd /opt/gdb-6.6/gdb/gdbserver
#./configure --host=arm-linux --target=arm-linux --prefix=/opt/arm-gdb/gdbserver
#make
#make install
目標板上
#cd \usr\lib
#ln –s libthread_db-x.x.so libthread_db.so
#ln –s libthread_db-x.x.so libthread_db.so.1
#./gdbserver 192.168.0.2:2345 hello
其中192.168.0.2為目標板的IP。2345為gdbserver打開的端口
#./arm-linux-gdb hello
(gdb)target remote 192.168.0.2:2345
...
死鎖時的對應
首先查看進程屬性
$ ps ax -L | grep xxxx
7259 7259 pts/2 Sl+ 0:00 -bash
7288 7288 pts/2 S 0:00 su
7289 7289 pts/2 S 0:00 bash
7374 7374 pts/2 R+ 0:00 ps ax -L
R ⇒ 執行狀態
S ⇒ 睡眠狀態(可中斷,有死鎖的可能性)
D ⇒ 睡眠狀態(不可中斷)
T ⇒ 停止狀態
檢測死鎖進程狀態
$ gdb -p 'pidof xxxx'
...
(gdb) bt
死鎖用測試腳本
debug.cmd
#####################################
set pagination off
set logging file debug.log
set logging overwrite
set logging on
start
set $addr1 = pthread_mutex_lock
set $addr2 = pthread_mutex_unlock
b *$addr1
b *$addr2
while 1
c
if $pc != $addr1 && $pc != $addr2
quit
end
bt
end
#####################################
# 使用
$ gdb xxxx -x debug.cmd
...
# 解析
$ cat debug.log | grep -A1 "^#0.*pthread_mutex_" | sed s/from\ .*$// | sed s/.*\ in\ //
測量程序的耗時
精密測量(時鐘週期)
Inter x86
在Intel x86CPU(也包括AMD的Athlon)內部,有一個按照CPU時鐘計算的64位的時間戳計數器(IA32_TIME_STAMP_COUNTER_MSR)。 通過RDTSC(Read Time Stamp Counter)命令,我們可以將這個值讀出來。通過它,我們可以高精度地測量某一程序的耗時。 因為這個計數器是64位的,所以基本上可以不必在意它有溢出的問題。 其實Windows的QueryPerformanceCounter內部函數就是利用了RDTSC指令。 通過RDTSC指令,計數器的值被保存到EAX(低階32bit)、EDX(高階32bit)。
在執行RDTSC指令之前,需要一條CPUID指令,該指令是為了確保測試之前的指令離開流水線,不要混入待測程序中。 但是,CPUID指令本身就比較耗時(幾百個時鐘週期) ,所以被測的程序需要減掉這一部分的損耗。 (Interl的RDTSC指令的介紹中,提到在前兩次調用CPUID的時候比較花時間,這之後就不會有很大的消耗了)
#ifndef RDTSC_H_
#define RDTSC_H_
inline unsigned long long rdtsc() {
unsigned long long ret;
__asm__ volatile ("rdtsc" : "=A" (ret));
return ret;
}
#endif /* RDTSC_H_ */
/* 使用例 */
#include "rdtsc.h"
#include <stdio.h>
int measure_func()
{
unsigned long long start = rdtsc();
to_be_measured();
unsigned long long stop = rdtsc();
printf("measured time : %I64d [clock]\n", stop - start);
return 0;
}
一般測量(毫秒級)
struct timespec ts;
long basesec;
long baseusec;
long pastusec;
clock_gettime(CLOCK_MONOTONIC, &ts);
basesec = ts.tv_sec;
baseusec = ts.tv_nsec/1000;
to_be_measured();
clock_gettime(CLOCK_MONOTONIC, &ts);
pastusec = (ts.tv_sec - basesec) * 1000000;
pastusec += (ts.tv_nsec/1000) - baseusec;
printf("Process Time :%d nano second.\n", pastusec);
Core Dump
# 有效化core dump
$ ulimit -c unlimited
# 設置dump size
$ ulimit -c 1073741824
# 設置 dump 路徑(kernel.core_pattern)
$ cat /etc/sysctl.conf
kernel.core_pattern = /var/core/%t-%e-%p-%c.core
kernel.core_uses_pid = 0
$ sysctl -p
# 自動壓縮保存的 core dump
$ cat /etc/sysctl.conf
kernel.core_pattern = = |/usr/local/sbin/core_helper %t %e %p %c
kernel.core_uses_pid = 0
$ sysctl -p
$ cat /usr/local/sbin/core_helper
#!/bin/sh
exec gzip -> /var/core/$1-$2-$3-$4.core.gz
# 系統全體的 core dump 有效,編輯 /etc/profile
ulimit -S -c unlimited > /dev/null 2>&1
# /etc/sysconfig/init 中添加
DAEMON_COREFILE_LIMIT = `unlimited`
# /etc/sysctl.conf 中添加
fs.suid_dumpable = 1
# 用gdb解析core dump
$ gdb -c dump.core ./a.out
# 系統默認棧的大小
$ ulimit -s
# 更改系統默認棧大小
$ ulimit -Ss 123456
應用程序調試技巧
SIGSEGV例外問題
發生SIGSEGV的時候,基本是以下幾種情況:
- 訪問空指針
- 訪問非法指針地址
- 棧溢出,訪問非法地址
Linux下實現程序異常時輸出backtrace
方法1 : 註冊信號處理
#include <stdlib.h>
#include <execinfo.h>
#include <signal.h>
void stacktrace(int signal) {
void *trace[128];
int n = backtrace(trace, sizeof(trace) / sizeof(trace[0]));
backtrace_symbols_fd(trace, n, 1);
}
int foo() {
return 1 /0;
}
void bar() {
foo();
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = stacktrace;
sa.sa_flags = SA_ONESHOT;
sigaction(SIGFPE, &sa, NULL);
bar();
return 0;
}
% ./a.out
./a.out(stacktrace+0x1f)[0x8048743]
/lib/libc.so.6[0x400466f8]
./a.out(bar+0xb)[0x8048796]
./a.out(main+0x65)[0x80487fd]
/lib/libc.so.6(__libc_start_main+0xc6)[0x40032e36]
./a.out[0x8048681]
zsh: 7442 floating point exception (core dumped) ./a.out
# 下面定位出錯代碼
% addr2line -e ./a.out 0x8048743
方法2 : 使用/lib/libSegFault.so
設置程序的 LD_PRELOAD=/lib/libSegFault.so SEGFAULT_SIGNALS=all 。
% export LD_PRELOAD=/lib/libSegFault.so
% export SEGFAULT_SIGNALS=all
% ./a.out
--- Floating point exception
Register dump:
EAX: 00000001 EBX: 40150880 ECX: 00000001 EDX: 00000000
ESI: 40016540 EDI: bfffe894 EBP: bfffe828 ESP: bfffe824
EIP: 080485f9 EFLAGS: 00010286
CS: 0023 DS: 002b ES: 002b FS: 0000 GS: 0000 SS: 002b
Trap: 00000000 Error: 00000000 OldMask: 00000000
ESP/signal: bfffe824 CR2: 00000000
Backtrace:
./a.out(foo+0x15)[0x80485f9]
./a.out(main+0x15)[0x8048619]
/lib/libc.so.6(__libc_start_main+0xc6)[0x40036e36]
./a.out[0x8048541]
Memory map:
08048000-08049000 r-xp 00000000 09:00 5538441 /home/satoru/tmp/a.out
08049000-0804a000 rw-p 00000000 09:00 5538441 /home/satoru/tmp/a.out
40000000-40016000 r-xp 00000000 08:01 700392 /lib/ld-2.3.2.so
40016000-40017000 rw-p 00015000 08:01 700392 /lib/ld-2.3.2.so
40017000-40018000 rw-p 00000000 00:00 0
40018000-4001b000 r-xp 00000000 08:01 700650 /lib/libSegFault.so
4001b000-4001c000 rw-p 00002000 08:01 700650 /lib/libSegFault.so
40021000-40149000 r-xp 00000000 08:01 700628 /lib/libc-2.3.2.so
40149000-40151000 rw-p 00127000 08:01 700628 /lib/libc-2.3.2.so
40151000-40154000 rw-p 00000000 00:00 0
bfffd000-c0000000 rwxp ffffe000 00:00 0
zsh: 11875 floating point exception (core dumped) ./a.out
其實 libSegFault.so 使用了gcc的擴展屬性 attribute((constructor)) 在main函數執行之前設置了信號處理。
方法3 : 使用catchsegv命令
catchsegv其實就是使用/lib/libSegFault.so的一個腳本。
$ catchsegv ./a.out
--- Floating point exception
Register dump:
EAX: 00000001 EBX: 40150880 ECX: 00000001 EDX: 00000000
ESI: 40016540 EDI: bffff674 EBP: bffff608 ESP: bffff604
EIP: 08048369 EFLAGS: 00010282
CS: 0023 DS: 002b ES: 002b FS: 0000 GS: 0000 SS: 002b
Trap: 00000000 Error: 00000000 OldMask: 80000000
ESP/signal: bffff604 CR2: 00000000
Backtrace:
/home/name/undernomal/test1.c:2(foo)[0x8048369]
/home/name/undernomal/test1.c:7(main)[0x8048389]
/lib/libc.so.6(__libc_start_main+0xc6)[0x40036e36]
../sysdeps/i386/elf/start.S:105(_start)[0x80482b1]
Memory map:
08048000-08049000 r-xp 00000000 03:01 328342 /home/name/undernomal/a.out
08049000-0804a000 rw-p 00000000 03:01 328342 /home/name/undernomal/a.out
0804a000-0806b000 rwxp 00000000 00:00 0
40000000-40016000 r-xp 00000000 03:01 2859 /lib/ld-2.3.2.so
40016000-40017000 rw-p 00015000 03:01 2859 /lib/ld-2.3.2.so
40017000-40018000 rw-p 00000000 00:00 0
40018000-4001b000 r-xp 00000000 03:01 95875 /lib/libSegFault.so
4001b000-4001c000 rw-p 00002000 03:01 95875 /lib/libSegFault.so
40021000-40149000 r-xp 00000000 03:01 3581 /lib/libc-2.3.2.so
40149000-40151000 rw-p 00127000 03:01 3581 /lib/libc-2.3.2.so
40151000-40154000 rw-p 00000000 00:00 0
bfffe000-c0000000 rwxp fffff000 00:00 0
$ env | grep LD_ ; env | grep SEG
LD_PRELOAD=/lib/libSegFault.so
SEGFAULT_SIGNALS=all
$ ./a.out
--- Floating point exception
Register dump:000 r-xp 00000000 03:01 328346 /home/name/undernomal/a2.out
08049000-0804a000 rw-p 00000000 03:01 328346 /home/name/undernomal/a2.out
EAX: 00000001 EBX: 40150880 ECX: 00000001 EDX: 00000000
ESI: 40016540 EDI: bffff684 EBP: bffff618 ESP: bffff614
40016000-40017000 rw-p 00015000 03:01 2859 /lib/ld-2.3.2.so
EIP: 08048369 EFLAGS: 000102860:00 0
40018000-4001b000 r-xp 00000000 03:01 95875 /lib/libSegFault.so
CS: 0023 DS: 002b ES: 002b FS: 0000 GS: 0000 SS: 002b
40021000-40149000 r-xp 00000000 03:01 3581 /lib/libc-2.3.2.so
Trap: 00000000 Error: 00000000 OldMask: 800000002.3.2.so
ESP/signal: bffff614 CR2: 00000000 0
bfffe000-c0000000 rwxp fffff000 00:00 0
Backtrace:name:~/undernomal$
./a.out[0x8048369]
./a.out[0x8048389]
/lib/libc.so.6(__libc_start_main+0xc6)[0x40036e36]
./a.out[0x80482b1]
Memory map:
08048000-08049000 r-xp 00000000 03:01 328342 /home/name/undernomal/a.out
08049000-0804a000 rw-p 00000000 03:01 328342 /home/name/undernomal/a.out
0804a000-0806b000 rwxp 00000000 00:00 0
40000000-40016000 r-xp 00000000 03:01 2859 /lib/ld-2.3.2.so
40016000-40017000 rw-p 00015000 03:01 2859 /lib/ld-2.3.2.so
40017000-40018000 rw-p 00000000 00:00 0
40018000-4001b000 r-xp 00000000 03:01 95875 /lib/libSegFault.so
4001b000-4001c000 rw-p 00002000 03:01 95875 /lib/libSegFault.so
40021000-40149000 r-xp 00000000 03:01 3581 /lib/libc-2.3.2.so
40149000-40151000 rw-p 00127000 03:01 3581 /lib/libc-2.3.2.so
40151000-40154000 rw-p 00000000 00:00 0
bfffe000-c0000000 rwxp fffff000 00:00 0
浮動小數點演算例外です (core dumped)
$ addr2line -f -e ./a.out 0x8048369
foo
/home/user/undernomal/test1.c:2
$ addr2line -f -e ./a.out 0x8048389
main
/home/user/undernomal/test1.c:7
$ addr2line -f -e ./a.out 0x80482b1
_start
../sysdeps/i386/elf/start.S:105
檢測日誌文件的更新
tail -f -n 10 mylog.log
利用 ltrace 檢測共享庫函數的調用
ltrace可以用來檢測共享庫的函數調用,同樣可以利用strace來跟蹤系統調用。
% ltrace -o log.txt wget https://www.codeblog.org/
% grep SSL log.txt | head
SSL_library_init(0, 0, 0, 0, 0) = 1
SSL_load_error_strings(0, 0, 0, 0, 0) = 0
OPENSSL_add_all_algorithms_noconf(0, 0, 0, 0, 0) = 1
SSL_library_init(0, 0, 0, 0, 0) = 1
SSLv23_client_method(0, 0, 0, 0, 0) = 0x40038880
SSL_CTX_new(0x40038880, 0, 0, 0, 0) = 0x808b228
SSL_CTX_set_verify(0x808b228, 0, 0x8068585, 0, 0) = 0x8068585
SSL_new(0x808b228, 0x7a060ed3, 1, 0, 0) = 0x808cd20
SSL_set_fd(0x808cd20, 3, 1, 0, 0) = 1
SSL_set_connect_state(0x808cd20, 3, 1, 0, 0) = 0
SSL_connect(0x808cd20, 3, 1, 0, 0
strace
檢測系統調用時的問題
strace -i xxxx ; 顯示系統調用時的地址,可以在gdb中加斷點用
strace -p 'pidof xxxx'
strace -o output.log xxxx ; 輸出到指定文件
strace xxxx 2>&1 | grep xxx
strace -f xxxx ; fock()的進程也執行trace
strace -t xxxx ; 表示系統調用時的時刻(秒單位)
strace -tt xxxx ; 表示系統調用時的時刻(毫秒單位)
valgrind
檢測內存洩漏, 非法內存訪問,未初始化訪問,二重delete, 釋放後訪問等
valgrind --leak-check=yes xxxx