gdb调试

Debugging with gdbhttps://www.eecs.umich.edu/courses/eecs373/readings/Debugger.pdf

             http://sourceware.org/gdb/current/onlinedocs/gdb/   内容较新

gdb概述: 

  UNIX及UNIX-like下的调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在 UNIX平台下做软件,你会发现GDB这个调试工具相比于VC、z的优点是具有修复网络断点以及恢复链接等功能,比BCB的图形化调试器有更强大的功能。所谓“尺有所短,寸有所长”就是这个道理。

  注意,如果要使用gdb调试指定的C/C++程序,则该程序在编译时要添加:-g选项,如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。

gdb使用技巧:

https://wizardforcel.gitbooks.io/100-gdb-tips/content/set-watchpoint.html

https://sourceware.org/gdb/onlinedocs/gdb/index.html#SEC_Contents

如何生成可调试程序

以使用gcc为例,例如编写好test.c文件

1、最简单的

gcc test.c 会在当前路径下直接生成a.out

2、分步骤编译

将源文件编译成目标文件:gcc - c test.c,将生成test.o文件

再将目标文件编译成可执行文件:gcc -o test test.o

3、一步到位,并可通过 -o 设置生成文件名

gcc test.c -o test  //test.c是文件名

-o 表示输出,test是输出的文件名

  但是通过上述命令编译生成test,在使用gdb进行调试、加断点的时候,会出现:No symbol table is loaded. Use the "file" command.

  因为在编译的时候没有使用 -g 选项,通过命令:gcc -g test.c -o test 编译后,即可通过gdb进行调试,并且添加断点也可以。

gdb调试相关命令

调试

  • s:step,即跳进调用的函数
  • n:next,继续执行下一行代码,单条语句执行
  • c:继续运行程序,continue的缩写

断点相关

 添加断点

  •  在文件特定行加断点:b file:linenum,如果在程序当前执行的文件上的某行加断点,则可简单的使用:b linenum 即可;弊端:如果你修改了源程序,则之前通过行号设置的断点可能就不是你想要的了,需要重新设置断点
  • 指定函数:b func,或 break func
  • 退出函数:finish

设置断点的方法:
    break <function>
        在进入指定函数时停住。C++中可以使用class::function或function(type,type)格式来指定函数名。

    break <linenum>
        在指定行号停住。

    break +offset
    break -offset
        在当前行号的前面或后面的offset行停住。offiset为自然数。

    break filename:linenum
        在源文件filename的linenum行处停住。

    break filename:function
        在源文件filename的function函数的入口处停住。

    break *address
        在程序运行的内存地址处停住。

    break
        break命令没有参数时,表示在下一条指令处停住。

    break ... if <condition>
        ...可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i==100,表示当i为100时停住程序。

    查看断点时,可使用info命令,如下所示:(注:n表示断点号)
    info breakpoints [n]
    info break [n]

保存断点信息为文件 & 加载含断点信息的文件

  在gdb中,可以使用如下命令将设置的断点保存下来:

(gdb) save breakpoints file-name-to-save

  下次调试时,可以使用如下命令批量设置保存的断点:

(gdb) source file-name-to-save

设置临时断点

  设置临时断点后,该断点只执行一次,通过tbreak(缩写:tb)进行临时断点设置:tb file:linenum

条件断点

gdb可以设置条件断点,也就是只有在条件满足时,断点才会被触发,命令是“break … if cond”,只有在表达式为true的时候断点才会生效 

 查看:

  • 查看断点:info b回车,或者info break
  • 查看堆栈:bt

断点管理

  查看断点:info b回车后,会打印断点的编号及所在位置、是否使能等;如果要删除某个断点,可以:a)d 编号,b)delete 编号;如果要使某个断点disable/enable,可以:disable/enable 编号;

观察点

  参考:https://wizardforcel.gitbooks.io/100-gdb-tips/content/set-watchpoint.html
  gdb可以使用“watch”命令设置观察点,缩写:wa,也就是当一个变量值发生变化时,程序会停下来。通过 watch 变量名 的形式,当变量的值发生改变时,程序就会停下来。

  观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。有下面的几种方法:

   
    watch <expr>
        为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序。
       
    rwatch <expr>
        当表达式(变量)expr被读时,停住程序。
       
    awatch <expr>
        当表达式(变量)的值被读或被写时,停住程序。
   
    info watchpoints
        列出当前所设置了的所有观察点。

打印

 打印变量

  • p 变量名,print的缩写
  • 以指定的格式打印变量:print /<f> expr,例如对于一些数据,可能要按16进制打印,就可以使用命令:print /x <expr>

  打印的格式包括:

  x  按十六进制格式显示变量。

  d  按十进制格式显示变量。
  u  按十六进制格式显示无符号整型。
  o  按八进制格式显示变量。
  t  按二进制格式显示变量。
  a  按十六进制格式显示变量。
  c  按字符格式显示变量。
  f  按浮点数格式显示变量。

  如果想要打印格式更好看些,可以输入命令:set print pretty on

  有时候在调试时,打印的变量并没有完全显示,显示完整的变量信息,输入命令:set print element 0

打印ASCII和宽字符字符串

  • ASCII字符串:x/s 变量名
  • 打印宽字符字符串,首先确认宽字符的长度
p sizeof(wchar_t)

由于当前平台宽字符的长度为4个字节,则用“x/ws 变量名”命令。如果是2个字节,则用“x/hs 变量名”命令进行打印。

 打印大数组

在gdb中,如果要打印大数组的内容,缺省最多会显示200个元素:

可以使用如下命令,设置这个最大限制数:

(gdb) set print elements number-of-elements

也可以使用如下命令,设置为没有限制:

(gdb) set print elements 0

(gdb) set print elements unlimited
(gdb) p array

 打印变量类型和所在的文件

在gdb中,可以使用如下命令查看变量的类型,下面的he为变量名:

(gdb) whatis he
type = struct child

如果想查看详细的类型信息:

(gdb) ptype he
type = struct child {
    char name[10];
    enum {boy, girl} gender;
}

如果想查看定义该变量的文件:

(gdb) i variables he
All variables matching regular expression "he":

File variable.c:
struct child he;

Non-debugging symbols:
0x0000000000402030  she
0x00007ffff7dd3380  __check_rhosts_file

gdb会显示所有包含(匹配)该表达式的变量。如果只想查看完全匹配给定名字的变量:

(gdb) i variables ^he$
All variables matching regular expression "^he$":

File variable.c:
struct child he;

函数调用

退出正在调试的函数

  • 输入finish
  • 输入return
  • 输入:return 值,例如:return 10,退出了函数并且修改了函数的返回值

直接执行函数

通过 call func(),或者 print func() 的形式,直接调用并执行某个函数

列出附近的代码:

  GDB 可以打印出所调试程序的源代码,当然,在程序编译时一定要加上-g的参数,把源程序信息编译到执行文件中。不然就看不到源程序了。当程序停下来以后,GDB会报告程序停在了那个文件的第几行上。你可以用list命令来打印程序的源代码。还是来看一看查看源代码的GDB命令吧。
   
    list <linenum>
        显示程序第linenum行的周围的源程序。
   
    list <function>
        显示函数名为function的函数的源程序。
       
    list
        显示当前行后面的源程序。
   
    list -
        显示当前行前面的源程序。

一般是打印当前行的上5行和下5行,如果显示函数是是上2行下8行,默认是10行,当然,你也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。

    set listsize <count>
        设置一次显示源代码的行数。
       
    show listsize
        查看当前listsize的设置。

list命令还有下面的用法:

    list <first>, <last>
        显示从first行到last行之间的源代码。
   
    list , <last>
        显示从当前行到last行之间的源代码。
       
    list +
        往后显示源代码。

 一般来说在list后面可以跟以下这们的参数:

     <linenum>   行号。

    <+offset>   当前行号的正偏移量。
    <-offset>   当前行号的负偏移量。
    <filename:linenum>  哪个文件的哪一行。
    <function>  函数名。
    <filename:function> 哪个文件中的哪个函数。
    <*address>  程序运行时的语句在内存中的地址。

 退出程序:

  • q:quit的缩写

调试正在运行的进程

  已知进程号,通过gdb调试并查看问题:gdb -p 进程号

调试中使用已编写好的断点文件

  • 在gdb调试过程中,可以通过 source xx文件的形式,这样不用每次都手动输入断点信息

 

启动gdb

  启动gdb有以下三种方式:

   1、gdb <program>
       program也就是你的执行文件,一般在当然目录下。

    2、gdb <program> core
       用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。

    3、gdb <program> <PID>
       如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试它。program应该在PATH环境变量中搜索得到。

信号(Signals)

  信号是一种软中断,是一种处理异步事件的方法。一般来说,操作系统都支持许多信号。尤其是UNIX,比较重要应用程序一般都会处理信号。UNIX定义了许多信号,比如SIGINT表示中断字符信号,也就是Ctrl+C的信号,SIGBUS表示硬件故障的信号;SIGCHLD表示子进程状态改变信号;SIGKILL表示终止程序运行的信号,等等。信号量编程是UNIX下非常重要的一种技术。

  GDB有能力在你调试程序的时候处理任何一种信号,你可以告诉GDB需要处理哪一种信号。你可以要求GDB收到你所指定的信号时,马上停住正在运行的程序,以供你进行调试。你可以用GDB的handle命令来完成这一功能。

    handle <signal> <keywords...>
    在GDB中定义一个信号处理。信号<signal>可以以SIG开头或不以SIG开头,可以用定义一个要处理信号的范围(如:SIGIO-SIGKILL,表示处理从SIGIO信号到SIGKILL的信号,其中包括SIGIO,SIGIOT,SIGKILL三个信号),也可以使用关键字all来标明要处理所有的信号。一旦被调试的程序接收到信号,运行程序马上会被GDB停住,以供调试。其<keywords>可以是以下几种关键字的一个或多个。

        nostop
            当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号。
        stop
            当被调试的程序收到信号时,GDB会停住你的程序。
        print
            当被调试的程序收到信号时,GDB会显示出一条信息。
        noprint
            当被调试的程序收到信号时,GDB不会告诉你收到信号的信息。
        pass
        noignore
            当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序会处理。
        nopass
        ignore
            当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。


    info signals
    info handle
        查看有哪些信号在被GDB检测中。

   例如,对于SIGUSR1信号,如果不想处理该信号,可以输入:handle SIGUSR1 nostop noprint

数组

    有时候,你需要查看一段连续的内存空间的值。比如数组的一段,或是动态分配的数据的大小。你可以使用GDB的“@”操作符,“@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度。例如,你的程序中有这样的语句:
    
        int *array = (int *) malloc (len * sizeof (int));
       
    于是,在GDB调试过程中,你可以以如下命令显示出这个动态数组的取值:

       *array@len

    @的左边是数组的首地址的值,也就是变量array所指向的内容,右边则是数据的长度,其保存在变量len中,其输出结果,大约是下面这个样子的:
   
        (gdb) p 
*array@len
        $1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40}

    如果是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。

参考:

https://blog.csdn.net/haoel/article/details/2880

https://blog.csdn.net/haoel/article/details/2881

https://baike.baidu.com/item/gdb/10869514

原文地址:https://www.cnblogs.com/zyk1113/p/13502181.html