Lastupdated:
2011-01-01
调试技术
TOP.gdbinit 设定
用户根目录下设置 .gdbinit 文件,文件内容如下:
1
2
3
4
5
6
|
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
|
|
TOPGDB控制程序运行过程
(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 |
是否显示结构体内的联合数据 |
(gdb) bt ; 显示上下文
(gdb) frame ; 显示当前帧
(gdb) frame n ; 显示第n号帧
(gdb) up / down ; 向上或下移动帧
(gdb) i frame 1 ; 详细显示1号帧情报
TOP用GDB调试嵌入式系统
gdbserver的安装与配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
|
|
目标板上
其中192.168.0.2为目标板的IP。2345为gdbserver打开的端口
1
2
3
|
#./arm-linux-gdb hello
(gdb)target remote 192.168.0.2:2345
...
|
|
- 首先查看进程属性
1
2
3
4
5
|
$ 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 ⇒ 停止状态
- 检测死锁进程状态
1
2
3
|
$ gdb -p 'pidof xxxx'
...
(gdb) bt
|
|
- 死锁用测试脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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\ //
|
|
TOPVisual Studio
TOP将VC6设置为缺省调试器
1
2
3
4
|
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug]
"Auto"="0"
"Debugger"="\"C:\\Program Files\\Microsoft Visual Studio\\Common\\MSDev98\\Bin\\msdev.exe\" -p %ld -e %ld"
"UserDebuggerHotKey"=dword:00000000
|
|
TOPwatch栏操作
1) @err,hr 显示API函数调用GetLastError的返回值,和解释
2) @eax,hr 显示eax寄存器的值,由于win的API的返回值放在eax中,所以这句话就是得到最近一个API的返回值
3) p,***(数字) 数组指针扩展出来只有单个元素,而你又想看到全部数组元素,可以用这个技巧
TOP调试宏(for Windows)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#define ASSERT(x) ((void)((!!(x)) || \
(_gui_assert(__FILE__, __MODULE__, __LINE__, #x) != 1) || \
(_gui_break(), 0)))
#endif
extern int _gui_assert
(const char* p_file, const char* p_module, int line, const char* p_exp)
{
return _CrtDbgReport(_CRT_ASSERT, p_file + strlen(p_file) - 16, line, p_module, "%s", p_exp);
}
extern void _gui_break(void)
{
_CrtDbgBreak();
}
|
|
TOP测量程序的耗时
TOP精密测量(时钟周期)
TOPInter 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的时候比较花时间,这之后就不会有很大的消耗了)
以下给出windows和linux平台的测试用例。
TOPWindows OS/Visual C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#ifndef RDTSC_H
#define RDTSC_H
inline __int64 __fastcall rdtsc()
{
__asm {
cpuid
rdtsc
}
};
#endif
#include "rdtsc.h"
#include <stdio.h>
int measure_func()
{
SetThreadAffinityMask(GetCurrentThread(), 1);
__int64 start = rdtsc();
to_be_measured();
__int64 stop = rdtsc();
printf("measured time : %I64d [clock]\n", stop - start);
return 0;
}
|
|
TOPLinux OS/GNU C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#ifndef RDTSC_H_
#define RDTSC_H_
inline unsigned long long rdtsc() {
unsigned long long ret;
__asm__ volatile ("rdtsc" : "=A" (ret));
return ret;
}
#endif
#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;
}
|
|
TOP一般测量(毫秒级)
TOPWindows
1
2
3
4
5
6
7
|
DWORD ust, ued;
ust = GetTickCount();
to_be_measured();
ued = GetTickCount();
printf("Process Time :%d milli second.\n", ued - ust);
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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);
|
|
TOPCore Dump
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
$ ulimit -c unlimited
$ ulimit -c 1073741824
$ cat /etc/sysctl.conf
kernel.core_pattern = /var/core/%t-%e-%p-%c.core
kernel.core_uses_pid = 0
$ sysctl -p
$ 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
exec gzip -> /var/core/$1-$2-$3-$4.core.gz
ulimit -S -c unlimited > /dev/null 2>&1
DAEMON_COREFILE_LIMIT = `unlimited`
fs.suid_dumpable = 1
$ gdb -c dump.core ./a.out
$ ulimit -s
$ ulimit -Ss 123456
|
|
TOP应用程序调试技巧
TOPSIGSEGV例外问题
发生SIGSEGV的时候,基本是以下几种情况:
- 访问空指针
- 访问非法指针地址
- 栈溢出,访问非法地址
TOPLinux下实现程序异常时输出backtrace
TOP方法1 : 注册信号处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#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;
}
|
|
- 下面输出结果
1
2
3
4
5
6
7
8
9
10
11
|
% ./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
|
|
TOP方法2 : 使用/lib/libSegFault.so
设置程序的 LD_PRELOAD=/lib/libSegFault.so SEGFAULT_SIGNALS=all 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
% 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函数执行之前设置了信号处理。
TOP方法3 : 使用catchsegv命令
catchsegv其实就是使用/lib/libSegFault.so的一个脚本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
$ 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
|
|
TOP检测日志文件的更新
1
|
tail -f -n 10 mylog.log
|
|
TOP利用 ltrace 检测共享库函数的调用
ltrace可以用来检测共享库的函数调用,同样可以利用strace来跟踪系统调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
% 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
|
|
- 检测系统调用时的问题
1
2
3
4
5
6
7
|
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 ; 表示系统调用时的时刻(毫秒单位)
|
|
TOPvalgrind
- 检测内存泄漏, 非法内存访问,未初始化访问,二重delete, 释放后访问等
1
|
valgrind --leak-check=yes xxxx
|
|