EMACS & 程序 编程点滴...

天下难事必作于易,天下大事必作于细

Lastupdated: 2011-01-01

调试技术

TOPGDB

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 是否显示结构体内的联合数据
  • Frame相关
       (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
#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

目标板上

1
2
3
4
#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打开的端口

1
2
3
#./arm-linux-gdb hello
(gdb)target remote 192.168.0.2:2345
 ...

TOP死锁时的对应

首先查看进程属性
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

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调试用代码

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 /* RDTSC_H */


/* 使用例 */
#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 /* 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;
}

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);
TOPLinux
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
# 有效化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

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

TOPstrace

检测系统调用时的问题
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
© www.yifeiyang.net
net tracking

                                                                                                 stats