首页 > 数据库技术 > 详细

GDB深入研究

时间:2015-11-26 22:58:22      阅读:417      评论:0      收藏:0      [点我收藏+]

GDB深入研究

一、GDB代码调试

(一)GDB调试实例

在终端中编译一个示例C语言小程序,保存到文件 gdb-sample.c 中,用GCC编译之

技术分享
#include <stdio.h>

int nGlobalVar = 0;

int tempFunction(int a, int b)
{
    printf("tempFunction is called, a = %d, b = %d /n", a, b);
    return (a + b);
}

int main()
{
    int n;
    n = 1;
    n++;
    n--;

    nGlobalVar += 100;
    nGlobalVar -= 12;

    printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);

    n = tempFunction(1, 2);
    printf("n = %d", n);

    return 0;
}
技术分享

 

技术分享

技术分享

在上面的命令行中,使用 -o 参数指定了编译生成的可执行文件名为 gdb-sample,使用参数 -g 表示将源代码信息编译到可执行文件中。如果不使用参数 -g,会给后面的GDB调试造成不便。

 下面输入“gdb”命令启动GDB,将首先显示GDB说明:

技术分享

 

下面使用“file”命令载入被调试程序 gdb-sample(这里的 gdb-sample 即前面 GCC 编译输出的可执行文件)

技术分享

 

上图中最后一行“(gdb) ”为GDB内部命令引导符,等待用户输入GDB命令。 

上图倒数第二行提示已经加载成功。

下面使用“r”命令执行(Run)被调试文件,因为尚未设置任何断点,将直接执行到程序结束

之后使用“b”命令在 main 函数开头设置一个断点(Breakpoint)

之后一行提示已经成功设置断点,并给出了该断点信息:在源文件 gdb-sample.c 第14行处设置断点;这是本程序的第一个断点(序号为1);断点处的代码地址为 0x8048418。向上看源代码,第14行中的代码为“n = 1”,恰好是 main 函数中的第一个可执行语句(因为前面的“int n;”为变量定义语句,并非可执行语句)。 

之后, 再次使用“r”命令执行(Run)被调试程序:

技术分享

 

程序中断在gdb-sample.c第14行处,即main函数是第一个可执行语句处。 上面最后一行信息为:下一条将要执行的源代码为“n = 1;”,它是源代码文件gdb-sample.c中的第14行。 

下面使用“s”命令(Step)执行下一行代码(即第14行“n = 1;”):

技术分享

 

上面的信息表示已经执行完“n = 1;”,并显示下一条要执行的代码为第15行的“n++;”。

既然已经执行了“n = 1;”,即给变量 n 赋值为 1,那我们用“p”命令(Print)看一下变量 n 的值是不是 1 :

技术分享

果然是 1。($1表示这是第一次使用“p”命令——再次执行“p n”将显示“$2 = 1”。)

下面我们分别在第21行打印处、tempFunction 函数开头各设置一个断点(分别使用命令“b 21”“b tempFunction”):

技术分享

使用“c”命令继续(Continue)执行被调试程序,程序将中断在第二个断点(21行),此时全局变量 nGlobalVar 的值应该是 88;再一次执行“c”命令,程序将中断于第三个断点(7行,tempFunction 函数开头处),此时tempFunction 函数的两个参数 a、b 的值应分别是 1 和 2:

技术分享

 

 再一次执行“c”命令(Continue),因为后面再也没有其它断点,程序将一直执行到结束:

技术分享

(二)GDB常用命令

 命令  解释  示例
file <文件名> 加载被调试的可执行程序文件。
因为一般都在被调试程序所在目录下执行GDB,因而文本名不需要带路径。
(gdb) file gdb-sample
r Run的简写,运行被调试的程序。
如果此前没有下过断点,则执行完整个程序;如果有断点,则程序暂停在第一个可用断点处。
(gdb) r
c Continue的简写,继续执行被调试程序,直至下一个断点或程序结束。 (gdb) c
b <行号>
b <函数名称>
b *<函数名称>
b *<代码地址>

d [编号]

b: Breakpoint的简写,设置断点。两可以使用“行号”“函数名称”“执行地址”等方式指定断点位置。
其中在函数名称前面加“*”符号表示将断点设置在“由编译器生成的prolog代码处”。如果不了解汇编,可以不予理会此用法。

d: Delete breakpoint的简写,删除指定编号的某个断点,或删除所有断点。断点编号从1开始递增。

(gdb) b 8
(gdb) b main
(gdb) b *main
(gdb) b *0x804835c

(gdb) d

s, n s: 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数;
n: 执行一行源程序代码,此行代码中的函数调用也一并执行。

s 相当于其它调试器中的“Step Into (单步跟踪进入)”;
n 相当于其它调试器中的“Step Over (单步跟踪)”。

这两个命令必须在有源代码调试信息的情况下才可以使用(GCC编译时使用“-g”参数)。

(gdb) s
(gdb) n
si, ni si命令类似于s命令,ni命令类似于n命令。所不同的是,这两个命令(si/ni)所针对的是汇编指令,而s/n针对的是源代码。 (gdb) si
(gdb) ni
p <变量名称> Print的简写,显示指定变量(临时变量或全局变量)的值。 (gdb) p i
(gdb) p nGlobalVar
display ...

undisplay <编号>

display,设置程序中断后欲显示的数据及其格式。
例如,如果希望每次程序中断后可以看到即将被执行的下一条汇编指令,可以使用命令
“display /i $pc”
其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。

undispaly,取消先前的display设置,编号从1开始递增。

(gdb) display /i $pc

(gdb) undisplay 1

i Info的简写,用于显示各类信息,详情请查阅“help i”。 (gdb) i r
q Quit的简写,退出GDB调试环境。 (gdb) q
help [命令名称] GDB帮助命令,提供对GDB名种命令的解释说明。
如果指定了“命令名称”参数,则显示该命令的详细说明;如果没有指定参数,则分类显示所有GDB命令,供用户进一步浏览和查询。
(gdb) help display

 

 

二、CGDB代码调试

cgdb可以看作gdb的界面增强版,cgdb主要功能是在调试时进行代码的同步显示,这增加了调试的方便性,提高了调试效率。其他功能则与gdb一样,可使用其常用命令。所以这里只做简单介绍,常用命令等参见gdb。

主要功能介绍:

  1. 相比GDB,增加了语法加亮的代码窗口,显示在GDB窗口的上部,随GDB的调试位置代码同步显示。

  2. 断点设置可视化 。

  3. 在代码窗口中可使用GDB常用命令 。

  4. 在代码窗口可进行代码查找,支持正则表达式 。

界面及使用说明

  1. 代码窗口

    调试时同步显示被调试程序源代码,自动标记出程序运行到的位置。当焦点在代码窗口时,可以浏览代码、查找代码以及执行命令 ,操作方式同vi 。常用命令如下:

     i : 焦点切换到GDB窗口 。
     o :打开文件选择框,可选择要显示的代码文件 。
     空格 :设置/取消断点 。
     k:向上移动
     j:向下移动
     /:查找
  2. 状态条窗口

    同vi的状态条,一般显示当前打开的源文件名,当代码窗口进入命令状态时,显示输入的命令等信息

  3. GDB窗口

    CGDB的操作界面,同GDB ,按ESC键则焦点切换到代码窗口 。

    启动&退出——启动:cgdb;退出:在代码窗口或GDB窗口,执行quit命令 。

代码实现:

技术分享

  1. “(gdb)”表示GDB已经启动,等待我们输入命令。此时程序并未开始运行,输入“run”开始运行程序。这种方式在GDB内部运行程序:

    技术分享

  2. List n,m表示显示n到m行的代码

    技术分享

  3. 设置断点,break n,用step单步执行(这里break 21,结果首先打印出 “hello!”,再次s,打印出“Who are you ?”):

    技术分享

三、汇编代码调试

汇编级的调试或跟踪,需要用到display命令“display /i $pc”,如上表所示,

 

“display /i $pc”
其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。
undispaly,取消先前的display设置,编号从1开始递增。

 

技术分享

看到了汇编代码,“n = 1;”对应的汇编代码是“movl $0x1,0x1c(%esp)”。

并且以后程序每次中断都将显示下一条汇编指定(“si”命令用于执行一条汇编代码——区别于“s”执行一行C代码)

技术分享

接下来我们试一下命令“b *<函数名称>”。 为了更简明,有必要先删除目前所有断点(使用“d”命令——Delete breakpoint)

技术分享

当被询问是否删除所有断点时,输入“y”并按回车键即可。

下面使用命令“b *main”在 main 函数的 prolog 代码处设置断点(prolog、epilog,分别表示编译器在每个函数的开头和结尾自行插入的代码):

技术分享

技术分享

技术分享

此时可以使用“i r”命令显示寄存器中的当前值———“i r”即“Infomation Register”,

也可以输入“i r 寄存器名”显示任意一个指定的寄存器值:

技术分享

 

最后输入命令“q”,退出(Quit)GDB调试环境

 

 

技术分享

 

四、DDD代码调试

(一)DDD简介

DDD,全称是Data Display Debugger,对于Linux系统中的编程人员来说,它就是windows系统下面的visual studio ,功能强大,是Linux世界中少数有图形界面的程序调试工具。DDD是命令行调试器的图形前端,除了一般的程序调试功能以外,还具有交互式图形数据显示的功能。它在嵌入式应用开发中也十分出色。DDD最初源于1990年Andreas Zeller编写的VSL结构化语言,后来经过一些程序员的努力,演化成今天的模样。DDD的功能非常强大,可以调试用C\C++、Ada、 Fortran、Pascal、Modula-2和Modula-3编写的程序;可以超文本方式浏览源代码;能够进行断点设置、回溯调试和历史纪录编辑;具有程序在终端运行的仿真窗口,并在远程主机上进行调试的能力;图形数据显示功能(Graphical Data Display)是创建该调试器的初衷之一,能够显示各种数据结构之间的关系,并将数据结构以图形化形式显示;具有GDB/DBX/XDB的命令行界面,包括完全的文本编辑、历史纪录、搜寻引擎。

(二)DDD调试过程

首先,我们制作一个程序文档,作为我们后面调试的对象。

打开终端命令行窗口,输入命令vi testddd.c,建立testddd.c文件:

技术分享

在testddd.c文件中输入一些C语言的程序数据,DDD工具可以调试很多种程序设置基于的代码,本次调试以C语言作为说明对象。

技术分享

把testddd.c文件编译成可以执行的文件testddd,命令:gcc -g -o testddd testddd.c,注意一定要带-g参数,否则生成的可执行文件中没有必要的调试信息,最终使用DDD工具不能调试。

运行DDD调试工具,直接输入命令ddd就可以打开DDD工具。

技术分享

DDD工具打开后如下图所示,上面较大空白部分为代码区,和工具区,分割线下面是调试生成信息区。

技术分享

点击菜单栏上的“文件”----->“打开程序”,准备打开我们上面准备的testddd.c文件

在打开程序框中,定位到我们要调试的程序的目录下,在Files列表下选择我们要调试 信息,之后点击左下方的打开按钮。

技术分享

调试程序打开后,在代码区可以看到我们的代码,右边的一些按钮是我们调试要用的工具。

技术分享

在代码区点鼠标右键,会弹出如图所示的菜单:

技术分享

我们可以给程序设置断点等,点击工具区里面的Run按钮,可以执行程序,在下面的调试信息区可以看到程序的执行结果。

技术分享

如上图所示:在鼠标右键点击的地方设置了断点,在下方调试信息生成区显示了程序运行的输入信息。

PS:也可以在Terminal中输入ddd 文件名来直接打开ddd调试该文件的界面:

技术分享

在怀疑程序哪个变量为可疑变量时,可以在控制台输入如下命令

技术分享

或者在主窗口原程序中点击某个变量如sum选中该变量,右击后选择display sum 选项就会看到该变量的值在主窗口的上方。 接着往下单步运行,多次点击工具栏中的“Step”按钮,观察变量sum的结果。

技术分享

如果问题出在count上。这时点击命令工具栏上的“Kill”按钮将程序断掉,把初始化sum的那一句改正确。重新运行之后,发现结果正确,调试过程完毕。

 

(三)常用命令简介

run 执行程序

step 单步调试

kill 杀死正在运行的程序

interrupt 退出此次调试回到原始状态

DDD的数据显示功能非常强大。

对于固定大小的数组,用鼠标选中数组名,点击plot按钮即可画出图形。

对于变长数组,可以使用graph plot数组名[起始索引] @ 数组大小的命令来显示。

对于复杂的数据结构,DDD也可以用图形方式解析: DDD有一个detect aliases的选项,可以智能的判别数据是否会被重复显示。这种方式通过内存地址的检测来实现的。

 

五、段错误

  1. 定义:段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况。
  2. 段错误产生的原因

    (1) 访问不存在的内存地址

    (2) 访问系统保护的内存地址

    (3) 访问只读的内存地址

    (4) 栈溢出

下面以原因一访问不存在的内存地址为例,进行实践。

(一)使用gcc和gdb(对于简单代码)

首先,编写一段代码,访问不存在内存地址。编译后进入CGDB,运行程序(我这里使用CGDB,可以看到源代码,更加方便。):

技术分享

从输出中可以看出,程序收到SIGSEGV信号,触发段错误,并提示0x080483c4、调用main报的错,在Derro.c中23行。并且在代码窗口第23行被标记出来。

适用场景

  1. 仅当能确定程序一定会发生段错误的情况下使用。

  2. 当程序的源码可以获得的情况下,使用-g参数编译程序。

  3. 一般用于测试阶段,生产环境下gdb会有副作用:使程序运行减慢,运行不够稳定,等等。

  4. 即使在测试阶段,如果程序过于复杂,gdb也不能处理。

(二)使用core文件和gdb

提到段错误会触发SIGSEGV信号,通过man 7 signal,可以看到SIGSEGV默认的handler会打印段错误出错信息,并产生core文件,由此我们可以借助于程序异常退出时生成的core文件中的调试信息,使用gdb工具来调试程序中的段错误。

查看core文件发现不存在:

技术分享

查看系统core文件的大小限制,发现为0,这样不会自动生成core文件。把大小设置为1000。运行程序后再次查看可看到存在core文件:

技术分享

加载core文件,使用gdb工具进行调试。从输出中可以看出同样的段错误信息:

技术分享

GDB深入研究

原文:http://www.cnblogs.com/lhc-java/p/4999017.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!