gdb调试

一、启动GDB调试

使用gdb调试首先在编译程序时加上-g参数:

$ gcc –g –o foo foo.c

$ gcc -ggdb3 -o test test.c

ggdb3告诉gcc,使用gdb的扩展产生调试符号。其中“3”表示使用的是第三级(最高级)调试信息。

启动gdb调试有多种方法,可以根据不同的场景选择合适的方式,这也是gdb比较好用的地方。

1. 程序没有运行时,gdb +<program> 直接用gdb运行程序;

2. 程序运行中的gdb调试有两种方式:

a.ps查看程序的PID,gdb + <program> + PID ,自动挂接到已运行的程序;

b.ps产看程序的PID,gdb + <program>运行gdb后,用attach + PID指令挂接到程序, 并用detach来取消挂接的进程。

3. 程序已经死掉后,gdb +<program> + core文件进行调试,core文件是程序非法执行后产生的“核心转储”文件。输入bt命令可以得到更加详尽的信息。

有些情况下core不能生成,需要用ulimit -c unlimited指令先设置系统环境。

假设一个程序a.out 生成了一个 core 文件,那么可以通过如下命令来进入调试:
gdb a.out core
当然如果core文件和a.out没有在同一个目录,也可以直接
gdb -c core
这种情况下gdb是找不到符号表的,所以需要在进入gdb之后通过
file a.out (a.out代表路径)
来载入符号表,不管是哪一种方式,进入调试界面之后,按下where或者bt就可以看见出错的堆栈情况。

如果是交叉编译,也可在pc上使用交叉编译器gdb调试:arm-linux-gdb a.out core

二、程序内调用gdb

程序发生段错误,但是该异常发生有一定的随机性,为了捕获异常并进行gdb调试,在程序中捕获SIGSEGV信号并进行如下处理,这样当程序运行出现段错误时直接进入gdb调试环境。

void segv_handler(int no) 
{
    char buf[512];
    char cmd[512];
    FILE *file;

    snprintf(buf, sizeof(buf), "/proc/%d/cmdline", getpid());
    if(!(file = fopen(buf, "r"))){ 
        exit(EXIT_FAILURE);
    } 
    if(!fgets(buf, sizeof(buf), file)){ 
        exit(EXIT_FAILURE);
    } 
    if(buf[strlen(buf) - 1 ] == '\n'){ 
        buf[strlen(buf) -1] = '\0';
    } 
    snprintf(cmd, sizeof(cmd), "gdb %s %d", buf, getpid());
    system(cmd);
}

注册函数:

signal(SIGSEGV, segv_handler);

三、常用指令

关于gdb调试的常用指令及介绍可以参考这里,http://blog.csdn.net/liwf616/article/details/46833107

gdb环境下直接按下回车表示执行上一条命令。

0. help

help [name] 显示GDB命令的信息,或者显示如何使用GDB的总体信息

1. 文件

file [filename] 装入想要调试的可执行文件

kill [filename] 终止正在调试的程序

load  动态载入一个可执行文件到调试器

2. 运行

run [arglist]    运行您的程序 (如果指定了arglist,则将arglist作为参数运行程序)

quit 退出GDB

continue 继续运行您的程序 (在停止之后,比如在一个断点之后)

finish 继续执行,直到当前函数返回,fin。

return 强制从当前函数返回,return 0

3. 断点

break 10 设置断点,在源程序第10行

break func 设置断点,在func函数入口

b 25 if i=5条件断点

xbreak 在当前函数的退出的点上设置一个断点

txbreak 在当前函数的退出的点上设置一个临时的断点(只可使用一次)

info break 查看断点信息

clear 删除一个断点,这个命令需要指定代码行或者函数名作为参数

disable 禁止断点功能,这个命令需要禁止的断点在断点列表索引值作为参数

enable允许断点功能,这个命令需要允许的断点在断点列表索引值作为参数

delete 断点号

ignore 忽略某个断点制定的次数。例:ignore 4 23 忽略断点4的23次运行,在第24次的时候中断

4. 调试

next 单步跟踪,函数调用当作一条简单语句执行,可简写为n

step 单步跟踪,函数调进入被调用函数体内,可简写为s

stepi 或si单步跟踪一条机器指令

nexti 或ni单步跟踪一条机器指令

5. 显示

list  列出产生执行文件的源代码的一部分

print 打印变量、字符串、表达式等的值,可简写为p

    p count 打印count的值

    p cou1+cou2+cou3 打印表达式值

    p *array@len

    p *arr@5;数组开头5个。

    p arr[3]@5; arr[3]及其后4个。

-------------------------------------------------------------------------------------

用p命令查看一个void   *型的变量的时候,提示:

attempt   to   dereference   a   generic   a   pointer    

主要是指针类型不确定,强制转换指针类型后,可以使用转换后的指针类型打印元素。

如 void *p;

# p *p 不成功

# p *(unsigned char *)p       成功

-------------------------------------------------------------------------------------

X 地址; 查看地址内容

    X/format 指定显示单元的个数

display 在断点的停止的地方,显示指定的表达式的值。(永久显示变量)

    display/x i

    x按16进制显示变量,d十进制,u十六进制显示无符号整形, 

    o八进制,t二进制,c字符显示变量,f按浮点数格式显示变量。

undisplay 删除一个display设置的变量显示。这个命令需要将display list中的索引做参数。

ptype 永久显示变量的类型

bt backtrace 查看函数堆栈

    bt n只打印栈顶上n层的栈信息

    bt -n只打印栈底下n层的栈信息

frame 切换栈帧

    frame n ;n为栈层编号

info locals显示栈内变量存值。

watch 使你能监视一个变量的值而不管它何时被改变

    rwatch 指定一个变量,如果这个变量被读,则暂停程序运行,在调试器中显示信息,并等待下一个调试命令。

    awatch 指定一个变量,如果这个变量被读或者被写,则暂停程序运行,在调试器中显示信息,并等待下一个调试命令。

whatis  显示变量的值和类型

info catch显示当前函数中的异常处理信息。

info args显示当前函数的参数名及值。

6. 设置

set 设置变量的值。例如:set nval=54 将把54保存到nval变量中

set args指定运行时参数。(如:set args10 20 30 40 50)

     show args查看设置好的运行参数。

path <dir> 设定程序的运行路径。

    showpaths 查看程序的运行路径。

set environment varname [=value] 设置环境变量。如:setenv USER=hchen

    show environment [varname] 查看环境变量。

7. shell

shell 不退出GDB就使用shell命令

make <make-args>不退出GDB就重新编译程序

cd <dir> 相当于shell的cd命令。

pwd 显示当前的所在目录。

info terminal 显示程序用到的终端模式。

tty指写输入输出的终端设备。如:tty /dev/ttyb

until在一个循环体内单步跟踪时,该命令运行程序到退出循环体,简写u。

四、多线程调试

info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程。

thread ID 切换当前调试的线程为指定ID的线程。

break thread_test.c:123 thread all 在所有线程中相应的行上设置断点

thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command。 

thread apply all command 让所有被调试线程执行GDB命令command。

set scheduler-locking off|on|step 估计是实际使用过多线程调试的人都可以发现,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。off 不锁定任何线程,也就是所有线程都执行,这是默认值。 on 只有当前被调试程序会执行。 step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。

五、远程调试

目标板IP:192.168.111.111

宿主机PC:192.168.111.222

1)目标板上运行gdbserver:

gdbserver 192.168.111.222:6666 ./a.out

2)宿主机上运行

arm-linux-gdb ./a.out

(gdb) target remote 192.168.111.111:6666

3)arm-linux-gdb加载目标系统库文件如果失败需要手动加载:

(gdbset solib-search-path /home/zxd/MerriiLinux_qa1_qe1/boot/config/gcc-linaro/arm-linux-gnueabi/libc/lib/

六、示例

1. 调试示例

#include <stdio.h>
int func(int n)
{
int sum=0,i;
for(i=0; i<n; i++)
{
sum+=i;
}
return sum;
}
void main()
{
int i; long result = 0;
for(i=1; i<=100; i++)
{
result += i;
}
printf(“result[1-100] = %d \n”, result );
printf(“result[1-250] = %d \n”, func(250) );
}

编译生成执行文件:(Linux下)
gcc -g tst.c -o tst
使用GDB调试:

hchen/test>gdb tst <––––– 启动GDB
GNU gdb 5.1.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type “show copying” to see the conditions.
There is absolutely no warranty for GDB. Type “show warranty” for details.
This GDB was configured as “i386-suse-linux”…
(gdb) l <–––––––––– l命令相当于list,从第一行开始例出原码。
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
(gdb) <–––––––––– 直接回车表示,重复上一次命令
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
(gdb) break 16 <–––––––––– 设置断点,在源程序第16行处。
Breakpoint 1 at 0×8048496: file tst.c, line 16.
(gdb) break func <–––––––––– 设置断点,在函数func()入口处。
Breakpoint 2 at 0×8048456: file tst.c, line 5.
(gdb) info break <–––––––––– 查看断点信息。
Num Type Disp Enb Address What
1 breakpoint keep y 0×08048496 in main at tst.c:16
2 breakpoint keep y 0×08048456 in func at tst.c:5
(gdb) r <––––––––––- 运行程序,run命令简写
Starting program: /home/hchen/test/tst
Breakpoint 1, main () at tst.c:17 <––––– 在断点处停住。
17 long result = 0;
(gdb) n <––––––––––- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) n
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) c <––––––––––- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050 <–––––程序输出。
Breakpoint 2, func (n=250) at tst.c:5
5 int sum=0,i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p i <––––––––––- 打印变量i的值,print命令简写。
$1 = 134513808
(gdb) n
8 sum+=i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8 sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt <––––––––––- 查看函数堆栈。
#0 func (n=250) at tst.c:5
#1 0x080484e4 in main () at tst.c:24
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish <––––––––––- 退出函数。
Run till exit from #0 func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24 printf(“result[1-250] = %d \n”, func(250) );
Value returned is $6 = 31375
(gdb) c <––––––––––- 继续运行。
Continuing.
result[1-250] = 31375 <–––––程序输出。
Program exited with code 027. <––––程序退出,调试结束。
(gdb) q <––––––––––- 退出gdb。

2. 当本目录文件wang大小超过2*1024时可能出现段错误(超过很大时,小时短时间不会出现),会进入segv_handler(),但不会触发gdb。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
#include <execinfo.h>

#define SYSLOG_LINE_BUFFER_SIZE  2*1024
#define BUFFER_SIZE  4*1024
#define FILEN "wang"

char bbuf[BUFFER_SIZE]={0};

void m_syslog_dbg(char *format, ...)
{
    va_list ptr;
    char buf[SYSLOG_LINE_BUFFER_SIZE] = {0};

    openlog("log", 0, LOG_DAEMON);
    // put log
    va_start(ptr, format);
    vsprintf(buf, format, ptr);
    va_end(ptr);
    syslog(LOG_DEBUG, "%s", buf);
    closelog();

    return;
}

void segv_handler(int no) 
{   
    printf("seg....\n");
#if 1
    void * array[10]; /* 25 层,太够了 : ),你也可以自己设定个其他值 */
    char **strings;

    int nSize = backtrace(array, sizeof(array)/sizeof(array[0]));
for (int i=nSize-1; i>=0; i--){ /* 头尾几个地址不必输出,看官要是好奇,输出来看看就知道了 */

        /* 修正array使其指向正在执行的代码 */ 
//      printf("SIGSEGV catched when running code at %x\n", (char*)array[i] - 1);
        printf("SIGSEGV catched when running code at %x\n", array[i]);

    }
#if 1
    strings = backtrace_symbols(array, nSize);
    printf("backtrace...%d\n", nSize);
    if(strings == NULL){
        printf("strings NULL\n");
        exit(EXIT_FAILURE);
    }

    for(int i=nSize-1; i>=0; i--){
        printf("%s\n", strings[i]);
    }

    free(strings);
#endif

#endif
#if 0
    char buf[128];
    char cmd[128];
    FILE *file;
    pid_t pid = getpid();   

    snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid);
    if((file = fopen(buf, "r")) == NULL){
        exit(EXIT_FAILURE);
    }
    if(fgets(buf, sizeof(buf), file) == NULL ){
        exit(EXIT_FAILURE);
    }

    if(buf[strlen(buf)-1] == '\n'){
        buf[strlen(buf)-1] = '\0';
    } 

    snprintf(cmd, sizeof(cmd), "/usr/bin/gdb %s %d", buf, pid);
    printf("receive signo %d-->%s\n", no, cmd);
//  system(cmd);
//  while(1);
#endif
}
int main(void )
{

    signal(SIGSEGV, segv_handler);

    struct stat st;
    int ret = 0;
    ret = stat(FILEN, &st);
    if(ret != 0){
        perror("stat");
        return -1;
    }

    int fd = open(FILEN, O_RDONLY);
    if(fd < 0){
        printf("open error\n");
        return -1;
    }


    //read(fd, bbuf, st.st_size);
    ret = read(fd, bbuf, sizeof(bbuf));
    printf("file size = %d, %d\n", ret, getpid());
    m_syslog_dbg("recevice [%s]\n", bbuf);

    printf("the end\n");
    while(1){
        sleep(1);
        printf("running...\n");
    }

    close(fd);
    return 0;
}

  

参考:

1. backtrace、backtrace_symbols、backtrace_symbols_fd-support for application self-debugging

2. linux/unix 段错误捕获_转

3. C/C++捕获段错误,打印出错的具体位置(精确到哪一行)_转

4. gdb调试段错误及使用

5. 使用gdb调试程序详解 https://blog.csdn.net/liwf616/article/details/46833107

posted @ 2016-04-25 09:48  yuxi_o  阅读(401)  评论(0编辑  收藏  举报