Linux基础 gdb调试器

该系列文章总纲链接:专题分纲目录 Linux环境


本章节内容的思维导图整理如下(从左上图标1开始,顺时针)


1 gdb调试器概述

GDB(GNU DeBugger)是 GNU 的调试器,一般和 gcc(GNU Compiler Collection)配搭使用。要使用 GDB 进行调试,编译程序时要指定-g 或-ggdb 的编译选项。如:

$gcc –g main.c / gcc –ggdb main.c

这样,gcc 就会在生成可执行文件时产生调试信息。

  1. -g 用于产生一般的调试信息
  2. -ggdb 则用于产生 GDB 特有的调试信息。使用-ggdb 时,可执行文件的尺寸会大大增加。    

2 gdb基本命令总结

2.1 调试前准备

要使用gdb调试器,首先编译时有:

    $gcc -g/-gdb aaa.c bbb.c ... -o main
    $gdb main

进入gdb调试器中。                                      

2.2 gdb list命令

list的几种使用方式:

  • 不带参数的命令list/l:列出源代码的一部分,接着上次的位置往下列,每次列 10 行。
  • 带一个参数的命令list 行号:列出从第几行开始的源代码/list 函数名 列出某个函数的源代码。
  • 带两个参数的命令list line1,line2:列出源文件从line1到line2之间的源代码。

2.3 gdb run命令

  • 不带参数的命令run/r:执行当前被调试的程序
  • 带n个参数的命令run argv[1]、argv[2]、...argv[n]。注意:如果再次使用不带参数的run命令,则gdb会使用前一条run命令的参数。在run命令的前面可以使用set和show命令对参数进行设置和显示。

2.4 操作断点的命令

2.4.1 设置断点

  • break/b:在代码里指定的行设置断点, 这将使程序执行到这里时被挂起。
  • break命令也可以在某函数上设置断点,使得程序执行到将要调用此函数之前停止。即break <函数名>。
  • break命令还可以使用表达式设置断点,即 break <line> if<condition>。
  • break命令还可以在指定的入口设置断点。即 break <filename:line>或者 break <filename:function-name>。

2.4.2 显示当前gdb的断点信息

利用命令Info break实现断点信息的显示。

2.4.3 删除指定的断点

delete/d     :删除已经存在的断点,后面参数是断点的号码。断点的信息用info来查看。

2.4.4 禁止/启用断点

启用断点:

enable breakpoint <断点的号码>。断点的信息用info来查看。

禁用断点:

disable breakpoint <断点的号码>。断点的信息用info来查看。

2.4.5 清除断点

clear的功能为清除某一行代码上已经存在的所有断点。主要有两种方式:

  • 不带参数的clear命令:clear:删除程序上次停止处的断点。
  • 带参数的clear命令:clear <number>:清除第number行代码上设置的所有断点。

2.4.6 观察点

watch表示在程序中设置一个观察点:格式即watch <condition>。观察点的条件一旦被触发,就会导致gdb中断。观察点与断点很相似,所有操作breakpoint的命令对watch都有效,区别在于:

  • 断点是在CPU到某一地址取指令时中断。
  • 观察点是在CPU到某一地址读取数据时中断。

2.5 查看运行时的数据

2.5.1 数据观察命令

print/p命令可以检查各个变量的值,在gdb中,可以随时查看全局变量、静态变量和局部变量这3种变量的值。发生冲突的时候同C语言中的隐藏机制是一样的。如果希望查看全局变量/其他函数的变量时,可以使用“::”操作符。操作符如下:

     print <file::variable>          //file为要查看的变量所在文件,但此文件一定要被目前调试程序所引用。
     print <function::variable>     //function为要查看的变量所在的函数
     print <file::function::variable>     //前两者的结合

2.5.2 对程序中函数的调用

print func(arg1,arg2,...)     //通过传递参数arg1、arg2打印出函数的返回值。

2.5.3 查看表达式的值

print <表达式>          //可以随时查看表达式的返回值。

2.5.4 查看数组的值

对于静态数组有:

print <array-name>,即可显示出数组中所有数据的内容了。

对于动态数组有:

print *array@len     //@的左边是第一个内存的地址值,右边则是需要查看内存的长度,即查看一段连续的内存空间的值。

2.5.5 变量的输出格式

使用p/*,可以自定义gdb的输出格式。常见的几种数据显示格式如下:

     p/x:按16进制显示变量
     p/d:按10进制显示变量
     p/u:按16进制显示无符号整型
     p/o:按8进制显示变量
     p/t:按2进制显示变量
     p/a:按16进制显示变量
     p/c:按字符格式显示变量
     p/f:按浮点格式显示变量

2.5.6 查看内存

在gdb中,可以使用examine(简写x)来查看内存地址中的值。命令格式如下:

     x  /nfu <address>     //其中address就是内存中的地址。nfu是一些可选参数。
     n:正数,表示显示内存的长度。
     f:表示显示的格式,参见变量输出格式的参数定义。如果地址所指的是字符串,格式是s,如果是指令地址,那么格式是i。
     u:从当前地址向后请求的字节数。如果不指定,gdb默认是4个B,该参数可以用以下字符代替:
          b     :单字节
          h     :2字节
          w     :4字节
          g     :8字节

2.5.7 自动显示变量

在调试程序的过程中可以设置一些自动显示的变量,当程序停止/用户单步跟踪时,这些变量会自动显示,相关的命令是display,命令格式如下:

display/fmt(显示的格式) expr(所要显示的表达式)

其中,fmt的格式如下:

          /x:按16进制显示表达式
          /d:按10进制显示表达式
          /u:按16进制显示无符号整型
          /o:按8进制显示表达式
          /t:按2进制显示表达式
          /a:按16进制显示表达式
          /c:按字符格式显示表达式
          /f:按浮点格式显示表达式
          /i:按机器指令码格式显示表达式

expr可以是表达式,也可以是环境变量$pc(表示指令地址)。于是当程序停下后,就会出现源代码和机器指令码相对应的情形。

删除自动显示的命令

     undisplay
     delete display <dnums>     //其中dnums意为所设置好了的自动显式的编号。
     //如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示。

注意:

  • disable display与enable display中的disable和enalbe不删除自动显示的设置,而只是让其失效和恢复。
  • info display表示查看display设置的自动显示的信息。GDB会打出一张表格,向你报告当然调试中设置了多少个自动显示设置,其中包括,设置的编号,表达式,是否enable。         

2.5.8 设置显示选项

GDB中关于显示的选项比较多,这里只例举大多数常用的选项。

     set print address on/off     //打开/关闭地址输出,当程序显示函数信息时,GDB会显出/不显示函数的参数地址。系统默认为打开的。
     set print array on/off          //打开/关闭数组显示,打开后当数组显示时,每个元素占一行,如果不打开的话,每个元素则以逗号分隔。这个选项默认是关闭的。
     set print elements     on/off     //这个选项主要是设置数组的,如果你的数组太大了,那么就可以指定一个来指定数据显示的最大长度,当到达这个长度时,GDB就不再往下显示了。如果设置为0,则表示不限制。
     set print null-stop on/off     //如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示。这个选项默认为off。
     set print pretty on          //如果打开printf pretty这个选项,那么当GDB显示结构体时会比较漂亮。如:
          $1 = {
               next = 0x0,
               flags = {
                    sweet = 1,
                    sour = 1
               },
               meat = 0x54 "Pork"
               }
     set print pretty off          //关闭printf pretty这个选项,GDB显示结构体时会如下显示:$1 = {next = 0x0, flags = {sweet = 1, sour = 1}, meat = 0x54 "Pork"}
     set print sevenbit-strings on/off     //设置字符显示,是否按“\nnn”的格式显示,如果打开,则字符串或字符数据按\nnn显示,如“65”。
     set print union on/off          //设置显示结构体时,是否显式其内的联合体数据。例如有以下数据结构:
          typedef enum {Tree, Bug} Species;
          typedef enum {Big_tree, Acorn, Seedling} Tree_forms;
          typedef enum {Caterpillar, Cocoon, Butterfly}Bug_forms;
          struct thing {
               Species it;
               union {
                    Tree_forms tree;
                    Bug_forms bug;
               } form;
          };
          struct thing foo = {Tree, {Acorn}};
          当打开这个开关时,执行 p foo 命令后,会如下显示:
               $1 = {it = Tree, form = {tree = Acorn, bug = Cocoon}}
          当关闭这个开关时,执行 p foo 命令后,会如下显示:
               $1 = {it = Tree, form = {...}}
     set print object on/off          //在C++中,如果一个对象指针指向其派生类,如果打开这个选项,GDB会自动按照虚方法调用的规则显示输出,如果关闭这个选项的话,GDB就不管虚函数表了。这个选项默认是off。
     set print static-members on/off          //这个选项表示,当显示一个C++对象中的内容是,是否显示其中的静态数据成员。默认是on。
     set print vtbl     on/off     //当此选项打开时,GDB将用比较规整的格式来显示虚函数表时。其默认是关闭的。

2.5.9 显示变量的历史记录

当用GDB的print查看程序运行时的数据时,每一个print都会被GDB记录下来。GDB会以$1, $2, $3 .....这样的方式为你每一个print命令编上号。于是,可以使用这个编号访问以前的表达式,如$1。这个功能所带来的好处是,如果你先前输入了一个比较长的表达式,如果你还想查看这个表达式的值,你可以使用历史记录来访问,省去了重复输入。

2.5.10 查看寄存器

要查看寄存器的值,可以使用如下命令:

     info registers

查看寄存器的情况。(除了浮点寄存器)

     info all-registers

查看所有寄存器的情况。(包括浮点寄存器)

     info registers <register-name>

查看所指定的寄存器的情况。寄存器中放置了程序运行时的数据,比如程序当前运行的指令地址(ip),程序的当前堆栈地址(sp)等等。同样可以使用print命令来访问寄存器的情况,只需要在寄存器名字前加一个$符号就可以了。

2.5.11 查看gdb环境变量

可以在GDB的调试环境中定义自己的变量,用来保存一些调试程序中的运行数据。要定义一个GDB的变量只需使用GDB的set命令。GDB的环境变量和UNIX一样,也是以$起头。如:

     set $foo = *object_ptr
     set $foo = 50

使用环境变量时,GDB会在你第一次使用时创建这个变量,而在以后的使用中,则直接对其赋值。环境变量没有类型,可以给环境变量定义任一的类型。包括结构体和数组。使用show convenience命令查看当前所设置的所有的环境变量。
gdb环境变量功能非常强大,环境变量和程序变量的交互使用,将使得程序调试更为灵活便捷。例如:

     set $i = 0
     print bar[$i++]->contents

于是就不必像print bar[0]->contents, print bar[1]->contents一个一个地输入命令了。输入这样的命令后,只用敲回车,重复执行上一条语句,环境变量会自动累加,从而完成逐个输出的功能。

2.6 改变程序的执行

GDB可以实现让程序随意跳跃,类似于C语言中的goto语句。一旦使用GDB挂上被调试程序,当程序运行起来后,就可以根据自己的调试思路来动态地在GDB中更改当前被调试程序的运行线路或是其变量的值,这个强大的功能能够更好的调试程序,比如:在程序的一次运行中走遍程序的所有分支。

2.6.1 修改变量的值

修改被调试程序运行时的变量值,在GDB中很容易实现,使用GDB的print命令即可完成。如:

     (gdb) print x=4

其中x=4这个表达式是C/C++的语法,意为把变量x的值修改为4;在某些时候,很有可能你的变量和GDB中的参数冲突,如:

     (gdb) whatis width
     type = double
     (gdb) p width
     $4 = 13
     (gdb) set width=47
     Invalid syntax in expression.

因为,set width是GDB的命令,所以,出现了“Invalid syntax in expression”的设置错误,此时,你可以使用set var命令来告诉GDB,width不是你GDB的参数,而是程序的变量名,如:

(gdb) set var width=47

另外,还可能有些情况,GDB并不报告这种错误,所以保险起见,在你改变程序变量取值时,最好都使用set var格式的GDB命令。

2.6.2 跳转执行

一般来说,被调试程序会按照程序代码的运行顺序依次执行。GDB提供了乱序执行的功能,也就是说,GDB可以修改程序的执行顺序,可以让程序执行随意跳跃。这个功能可以由GDB的jump命令来完:

jump <location> 或者 jump *address     //这里的address是代码行的内存地址。

指定下一条语句的运行点。可以是文件的行号,可以是file:line格式,可以是+num这种偏移量格式。表示下一条运行语句从哪里开始。

注意:jump命令不会改变当前的程序栈中的内容,所以,当你从一个函数跳到另一个函数时,当函数运行完返回时进行弹栈操作时必然会发生错误,可能结果还是非常奇怪的,甚至于产生程序Core Dump。所以最好是同一个函数中进行跳转。

程序运行时,eip寄存器用于保存当前代码所在的内存地址。所以,jump命令也就是改变了这个寄存器中的值。于是,你可以使用“set $pc”来更改跳转执行的地址。如:set $pc = 0×485,这时程序下一步执行的代码地址就变成了“0x485”。

2.6.3 信号的产生与处理

使用singal命令,可以产生一个信号量给被调试的程序。如:中断信号Ctrl+C。这非常方便于程序的调试,可以在程序运行的任意位置设置断点,并在该断点用GDB产生一个信号量,这种精确地在某处产生信号非常有利程序的调试。语法格式是:

signal <signum>

UNIX的系统信号量通常从1到15。所以取值也在这个范围。single命令和shell的kill命令不同,系统的kill命令发信号给被调试程序时,是由GDB截获的,而single命令所发出一信号则是直接发给被调试程序的。

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:当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序会处理。
     nopass:当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。

2.6.4 强制调用函数

强制函数调用命令call,功能以及用法和print相同,命令格式如下:

     call func(arg1,arg2,...)

其中表达式中可以是函数,以此达到强制调用函数的目的。并显示函数的返回值,如果函数返回值是void,那么就不显示。

     print expr

另一个相似的命令print也可以完成这一功能,print后面可以跟表达式,所以也可以用他来调用函数,但是print和call的不同是,如果函数返回void,call则不显示,print则显示函数返回值,并把该值存入历史数据中。

2.6.5 强制函数返回

如果你的调试断点在某个函数中,并还有语句没有执行完。你可以使用return命令强制函数忽略还没有执行的语句并返回。

     return <value> 或者return <expression>

使用return命令取消当前函数的执行,并立即返回,如果指定了,那么该表达式的值会被认作函数的返回值。

2.7 gdb高级应用

2.7.1 产生core文件

在GDB下运行程序使得找bug更容易,但有时在GDB运行正常的程序时,在命令行下运行却会出问题,运行后留下一个名为core的文件。GDB可以载入core文件并检查上次运行死掉前的程序状态。

注意:有的版本默认设置不生成core文件,可以执行命令

$ulimit -c unlimited

生成core文件。

2.7.2 跟踪栈上数据

每个函数和它的变量都制定了一个frame,而最近调用的函数则在0号frame(底层frame),这些frame 形成一个程序运行栈。要打印此栈可以使用命令backtrace(可简化为 bt),frame之间的跳转可以使用命令frame num,直接跳转到第num frame上。

  • 使用命令info locals查看frame 当前的局部变量。
  • 使用命令frame up(上移)或者 frame down(下移)来转换frame。
  • 更多关于frame的信息可以使用命令info frame来查看。

2.7.3 绑定运行进程

为了调试core文件或者程序,gdb可以绑定到一个正在运行的进程上并进入到进程内。这是通过制定想要绑定gdb的程序进程ID而不是核心文件名实现的。在GDB中有:

     detach 离开进程
     attach <进程号>  绑定进程

在GDB外可以直接使用

gdb <可执行程序> <进程号>

2.7.4 源文件搜索

search命令可以向下搜索正则表达式:

     search regexp

reverse-search可以向上搜索正则表达式:

     reverse-search regexp

在查找到相应的正则表达式后该行的行号存入环境变量$_中,可以使用print命令来查看,即$print $_

2.7.5 机器语言工具

有一组专用的gdb变量可以用来检查和修改计算机的通用寄存器,在gdb中可以使用4个寄存器的标准名字。

     $ip:程序当前运行指令的地址。
     $pc:程序计数器。
     $fp:帧指针。
     $sp:栈指针。
     $ps:处理器状态。

使用print命令直接将结果打印出来,同时可以使用set命令重新赋值,从而修改程序运行状态。使用info line命令查看代码在内存中的地址,命令格式如下:

info line <line_num/func_name/file:line_num/file:func_name>

参数可以是源代码的行号、函数名,也可以使用操作符“:”精确定位到某个文件再加上行号/函数。结果会显示所指定的源代码在gdb中运行时的内存地址。

使用命令disassemble来查看当前正在执行源程序的汇编代码,它会把目前在内存中要执行源代码的及其指令显示出来。在每一行上显示指令地址,偏移量,指令内存等内容。

2.7.6 其他有用的调试命令

gdb中有用的调试命令有:

     pwd             //显示当前工作目录
     cd              //改变当前工作目录
     quit            //退出gdb
     shell [command] //对于在调试时修改源代码是很有用的
gdb语言环境显示命令有:
     show language   //查看当前的语言环境,如果gdb不能识别,C语言是默认的环境
     info frame      //查看当前函数的程序语言
     info source     //查看当前文件的程序语言,如果无法检测到,也可以手动设置,利用命令set language来实现,当set language命令后面没有参数时,默认显示gdb所支持的语言种类。

2.8 调试多进程

gdb调试多进程有两种方法:

@1 设置跟踪流:设置方法如下:

set follow-fork-mode [parent|child]
选择一个进程进行跟踪,另一个进程不受影响。之后在子进程代码设置断点即可。
如果要在fork函数之后断开某个进程的测试,则使用命令:
     set detach-on-fork [on,off]
     若选中on,则断开调试follow-fork-mode指定的进程。
     若选中off,gdb将控制父进程和子进程

@2 利用attach命令:gdb调试器中attach命令可以调试一个已经运行的程序,在进程调用fork函数后可以使用attach命令调试子进程。前提是要知道子进程的进程ID,并且子进程能够等待调试的开始,所以在利用attach命令的时候要添加辅助性代码。

2.9 gdb调试多线程

多线程调试,最重要的几个命令:

info threads  查看当前进程的线程。GDB会为每个线程分配一个ID, 后面操作线程的时候会用到这个ID。前面有*的是当前调试的线程
thread <ID>  切换调试的线程为指定ID的线程。
break file.c:100 thread all    在file.c文件第100行处为所有经过这里的线程设置断点。

在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令 set scheduler-locking  就可以实现这个需求。

set scheduler-locking off|on|step    
         off      不锁定任何线程,也就是所有线程都执行,这是默认值。
         on       只有当前被调试程序会执行。
         step     在单步的时候,除了next过一个函数的情况
                  (熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,
                  只有当前线程会执行。
thread apply ID1 ID2 command        让一个或者多个线程执行GDB命令command
thread apply all command            让所有被调试线程执行GDB命令command。

2.10 其他gdb的基本命令

常用命令基本描述
file装入想要调试的可执行文件
kill终止正在调试的程序
next/n执行下一行源代码但不进入函数内部
step/s执行下一行源代码而且进入函数内部
quit/q终止 gdb
make使你能不退出 gdb 就可以重新产生可执行文件
shell使你能不离开 gdb 就执行 UNIX shell 命令
回车在 gdb 提示符下按回车健将重复上一个命令
info/ilocals查看局部变量的值查看当前栈帧局部变量的值
help查看帮助说明
start开始执行程序,停在 main 函数第一行语句前面等待命令,或者重新回到gdb调试初始状态
backtrace(或 bt) 查看各级函数调用及参数
finish连续运行到当前函数返回为止,然后停下来等待命令
frame/f帧编号 选择栈帧

注意:gcc 的-g 选项并不是把源代码嵌入到可执行文件中的,在调试时也需要源文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值