GDB调试

GDB是一个强大的命令行调试工具。命令行的强大就是在于:

  • 可以形成执行序列
  • 可以形成脚本shell

UNIX下的软件全是命令行的,这给程序开发提代供了极大的便利。命令行软件的优势在于,它们可以非常容易的集成在一起,使用几个简单的已有工具的命令,就可以做出一个非常强大的功能。

例如:实现自动化 
    可以使用shell文本包装几个简单的工具命令,让它们可以集成在一起发挥更多功能。

1. GDB概述

一般来说,GDB主要完成下面四个方面的功能:

  • 启动你的程序,可以按照你的自定义的要求运行程序。
  • 可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
  • 当程序被停住时,可以检查此时你的程序中所发生的事。
  • 动态的改变你程序的执行环境。

2. GDB调试命令

backtrace       //显示程序中的当前位置和表示如何到达当前位置的栈跟踪(同义词:where)        
breakpoint      //在程序中设置一个断点        
cd              //改变当前工作目录        
clear           //删除刚才停止处的断点      
commands        //命中断点时,列出将要执行的命令        
continue        //从断点开始继续执行      
delete          //删除一个断点或监测点;也可与其他命令一起使用        
display         //程序停止时显示变量和表达时        
down            //下移栈帧,使得另一个函数成为当前函数       
frame           //选择下一条
continue        //命令的帧        
info            //显示与该程序有关的各种信息        
jump            //在源程序中的另一点开始运行       
kill            //异常终止在
gdb             //控制下运行的程序       
list            //列出相应于正在执行的程序的原文件内容      
next            //执行下一个源程序行,从而执行其整体中的一个函数        
print           //显示变量或表达式的值        
pwd             //显示当前工作目录      
pype            //显示一个数据结构(如一个结构或C++类)的内容        
quit            //退出gdb      
run             //执行该程序      
search          //在源文件中搜索正规表达式        
set-variable    //给变量赋值    
signal          //将一个信号发送到正在运行的进程        
step            //执行下一个源程序行,必要时进入下一个函数        
until           //结束当前循环      
up              //上移栈帧,使另一函数成为当前函数      
watch           //在程序中设置一个监测点(即数据断点)        
whatis          //显示变量或函数类型
Reverse-search      //在源文件中反向搜索正规表达式   

3. GDB调试

gcc是Linux或其他所有unix系统自带的编译器,它也有Windows的接口,不过在Windows上还是用Visual Studio的debugger比较简单。

假设程序文件名叫main.c,可以用以下的命令来编译它:

gcc main.c -o main //若程序没有error,生成目标文件main

如果想调试程序,需要告诉编译器:

gcc main.c -g -o main //-g 为调试命令gdb
//或者
gcc main.c -g -Wall -Werror -o main

这里讲一下以上编译选项:

  • -Wall –>显示所有的警告(warning all);
  • -W –> 只显示编译器认为可能出错的警告信息;
  • -w –> 忽略所有的警告信息。

一般情况,【-W】【-Wall】同时使用为佳:

gcc main.c -g -W -Wall -o main

GDB的启动、退出

启动GDB的方法有几种:

<1> gdb main  
//main是执行文件,一般在当前目录下
<2> gdb main core 
//同时调试一个运行程序和core文件
//core是程序非法执行后系统为了记录错误信息而产生的文件
<3> gdb main PID  
//如果程序是一个服务程序,则可以指定这个服务程序     
//运行时的PID,GDB会自动attach上去,并调试它。
//main应该在PATH环境变量中搜索得到。

GDB启动时,可以添加一些GDB的启动开关gdb -help查看详细开关。一下列举一些常用参数:

--symbols=SYMFILE : 从指定文件中读取符号表
--se=FILE : 从指定文件中读取符号表信息,并将它用在可执行文件中
--core=COREFILE : 调试时系统产生的core文件
--directory=DIR : 加入一个源文件的搜索路径。默认是PATH中定义的路径

退出:

kill //退出程序
quit //退出GDB

运行程序

若以gdb main的方式启动GDB,GDB会在PATH路径和当前目录中搜索源文件。若不确定GDB是否找到源文件,可以用命令l/list查看是否有显示部分源代码。使用r/run运行程序前,可能需要进行一下5方面的设置:

  1. 设置运行参数
    • set args -> 指定运行时的参数,如set args 10 20 30 40
    • show args -> 查看设置好的运行参数
  2. 运行环境
    • path -> 设定程序的运行路径
    • showpaths -> 查看路径
    • set environmentVarname=value -> 设置环境变量,如set env USER=benshow
    • env [ varname ] -> 查看环境变量,不带参数则打印当前所有环境变量
  3. 工作目录
    • cd -> 相当于shell中的cd
    • pwd -> 显示当前所在目录
  4. 程序的输入、输出
    • info terminal -> 显示程序用到的终端的模式
    • 使用重定向控制输出输入,run > outfile
    • tty -> 设置输入输出使用的终端设备,如:tty/dev/tty1
  5. 调试已运行的程序
    • ps 查看正在运行的程序的PID,然后用gdb PID 格式挂接正在运行的程序
    • 先用GDB关联上源代码,并进行调试,在GDB中用attach 挂接进程PID。用delete取消挂接。

单步运行

若设置了断点,执行 run 命令后程序将停在第一次遇到的断点处,此时可用
- continue恢复运行,直到遇到下一个断点或程序结束。
- next、step单步执行:
- next -> 下一行代码,不进入函数内部
- step -> 遇到函数时,进入函数内部

//继续执行,三个命令作用一样,其中参数表示忽略后面的断点
continue [ignore-count]
c [ignore-count]
fg [ignore-count]
//单步跟踪程序,若遇到的函数被编译了有调试信息,则进入该函数
//count表示执行后面的[count]条指令后停止
step [count]
s [count]
//next
next [count]
n [count]
//当前函数执行完后返回,并打印函数返回时的堆栈地址、返回值、参数值等
finish
//退出循环体
until/u

设置断点

break function //b function,在函数处设置断点
break linenum  //b linenum,在该行设置断点
break +offset/-offset //在当前行的后一行/前一行设置断点
break filename:linenum/function //进入文件
breakif condition  //若果条件成立,暂停
info breakpoints[n]  //查看断点,n 为短点号 ==info b[n]
delete b[n] //删除断点

设置观察点

watch expr //为表达式(变量设置一个断点,当发生变化时停止运行)
rwatch expr //表达式expr被读时,停止程序
awatch expr //表达式的值被读写时,暂停程序
info watchpoints[n] //查看观察点信息

设置捕捉点

可以通过设置捕捉点(陷阱)来捕捉程序运行时的一些事件,如载入共享库(动态的链接库)或是程序的异常。

catch  event  //event发生时,暂停
tcatch  event  //只设置一次捕捉点,程序暂停后,捕捉点被自动清除

event可以是以下内容:

catch assert :     //捕捉ada语言的assert断言失败
catch catch :      //捕捉C++收到的异常
catch exception :  //捕捉ada语言的execption
catch exec :       //捕捉exec调用
catch fork :       //捕捉fork调用
catch syscall :    //捕捉syscall调用
catch throw :      //捕捉C++抛出异常
catch vfork :      //捕捉vfork调用

维护停止点

断点、观察点和捕捉点统称为停止点,如果觉得设置的停止点没有作用了,可以使用delete、clear、disable、enable等命令来维护停止点。

clear [function/linenum/file-linenum]
delete [breakpoints/range..] // d
disable [b/range…] //dis,比以上两方式更好,相当于放入回收站
enable //让停止点生效

停止条件维护

针对break…if condition,condition设置好后还是可以更改的,方法如下:

//将原来bnum行的停止条件condition修改为expression
condition bnum expression 
condition bnum    //清除改行的停止条件
ignore bnum count //忽略改行的停止条件count次,任性!

Gdb实战

要使用GDB调试程序,必须确保被调试程序的可执行文件(.obj)附加了调试信息,为此,gcc / g++编译源码时需要使用-g选项:

$ gcc –g example.c –o example
$ gdb example
//or 
$ gdb
    :file example //内部装载目标文件
    :run          //gdb内置命令

如果一切顺利,程序运行到结束,gdb重新获得控制权;
如果中途崩溃,gdb会记录崩溃之前的堆栈信息,方便查找原因。

演示代码:(存在bug)

#include 

int wib(int no1, int no2)
{
    int result, diff;

    diff = no1 - no2;
    result = no1/diff;

    return result;
}

int main(int argc, char *argv[])
{
    int value, div, result, total;
    int i;

    value = 10;
    div = 6;
    total = 0;

    for (i=0; i<10; i++)
    {
        result = wib(value, div);
        total += result;
        div++;
        value--;
    }
    printf("%d wibed by %d equals %d\n", value, div, total);

    return 0;
}

程序运行10次for循环,通过wib函数算出累加值,再打印出结果。
编译运行后,使用gdb内置命令调试代码:

(gdb)run
...
Program received signal SIGFPE, Arithmetic exception.
...
in wib(no1=8, no2=8) at example.c : 8

 8        result = no1/diff
(gdb)

提示程序line_8收到了一个算术异常中断信号,同时给信号的代码行内容。可以通过list列出错误代码旁边的十行代码。从gdb消息可以看出,第八行的除法运算出了错,而这一行中将变量no1/diff,
要查看变量的值,用gdb内置命令print。

(gdb)print no1 
$2=8
(gdb)print diff
$3=0

由此可推断出算术异常时由除数为0的除法运算造成的。通过print no1-no2重新估算这个变量。gdb告诉我们这两个变量都为8,所以我们需要检查调用了wib的main函数,查看在什么时候发生的。

(gdb)continue   //继续执行程序

使用断点

(gdb)list main  //打印main函数开始前后的代码
(gdb)break main //进入main函数时设置断点
(gdb)break 25   //在25行wib()函数出设置断点
Breakpoint 1 at ...: file example.c, line 25
(gdb)

gdb显示已经在请求的那一行设置了1号断点

(gdb)run    //从头开始运行程序,直到gdb发生中断

此时,gdb会生成一条消息,指出在哪个断点中断,以及程序运行到何处。

(gdb)next   //单步执行

跟多断点和观察点
要知道调用wib函数之前什么时候value等于div,因此在上一示例中第25行处设置了断点,程序必须执行两次才会发生这种状况。我们只要在断点上设置一个条件就能使gdb在value==div时暂停。要设置条件,可以在设置断点时同时定义出发条件:

(gdb)break 25 if value==div
 Breakpoint 1 at ... : file example.c, line 25
(gdb)
condition 1 div==value    #修改出发条件
condition 1    #修改为无条件断点

显示断点信息

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040052a in main at example

了解div什么时候更改

(gdb)watch div==value
Hardware watchpoint 2:div==value
(gdb)

OK ,基本操作就介绍到这里,要学会怎么使用还需要自己实践。实践了才能学到根本。


以下内容截自本人根据例子编译、调试所得,可以参考

zhang@zhang:~/code$ vim example.c
zhang@zhang:~/code$ gcc -g example.c -o example
zhang@zhang:~/code$ gdb example  //启动gdb调试

Type "apropos word" to search for commands related to "word"
Reading symbols from gdb_example...done.

(gdb) run   //运行程序
Starting program: /home/zhang/code/gdb_example 
//gdb显示程序接收到一个算术异常信号,并指出出现在哪部分代码
Program received signal SIGFPE, Arithmetic exception.
0x000000000040053d in wib (no1=8, no2=8) at example.c:8
8       result = no1/diff;

(gdb) print no1   //打印出变量no1的当前值
$1 = 8
(gdb) print diff  //diff当前值
$4 = 0

(gdb) continue //继续运行程序
Continuing.

Program terminated with signal SIGFPE, Arithmetic exception.
The program no longer exists.

(gdb) list   //打印出错代码的后10行
3   int wib(int no1, int no2)
4   {
5       int result, diff;
6   
7       diff = no1 - no2;
8       result = no1/diff;
9   
10      return result;
11  }
12  
(gdb) list main //打印main函数开始前后的代码
9   
10      return result;
11  }
12  
13  int main(int argc, char *argv[])
14  {
15      int value, div, result, total;
16      int i;
17  
18      value = 10;
(gdb) //此处只需要按一下Enter键
19      div = 6;
20      total = 0;
21  
22      for (i=0; i<10; i++)
23      {
24          result = wib(value, div);
25          total += result;
26          div++;
27          value--;
28      }

(gdb) b 24  //在24行处设置断点
Breakpoint 1 at 0x400575: file gdb_example.c, line 24.

(gdb) info b //查看断点具体信息
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400575 in main at example.c:24

(gdb) run //继续运行,程序会在1号断点处暂停
Starting program: /home/zhang/code/example 

Breakpoint 1, main (argc=1, argv=0x7fffffffde38) at example.c:24
24          result = wib(value, div);
(gdb) next //单步执行,下一条指令
25          total += result;
(gdb) 
26          div++;
(gdb) 
27          value--;
(gdb) 

(gdb) watch div==value  //
Hardware watchpoint 4: div==value
(gdb) continue
Continuing.

Breakpoint 1, main (argc=1, argv=0x7fffffffde38) at gdb_example.c:24
24          result = wib(value, div);
(gdb) 
Continuing.
Hardware watchpoint 4: div==value

Old value = 0
New value = 1
main (argc=1, argv=0x7fffffffde38) at gdb_example.c:22
22      for (i=0; i<10; i++)
(gdb) info locals 
value = 8
div = 8
result = 4
total = 6
i = 1
(gdb) info watch
Num     Type           Disp Enb Address            What
4       hw watchpoint  keep y                      div==value
    breakpoint already hit 1 time

你可能感兴趣的:(嵌入式开发)