gdb valgrind,c++ gdb调试

  gdb valgrind,c++ gdb调试

  用GDB和Valgrind调试c程序_二亩三分地四季五谷_百度空间

  用GDB和Valgrind调试c程序http://blog.chinaunix.net/u/4329/showart.php? id=1272512

  Linux程序员一直依赖于一小组基本的调试工具,许多更复杂的调试工具都是基于这些基本工具。GNU debugger (gdb)是迄今为止最流行的用于跟踪和调试应用程序的调试工具,但是如果您想做的不仅仅是插入几个断点和使用一些测试数据来监控应用程序的健康状况,它并不是您唯一的选择。例如,近年来,一些系统分析工具(如systemtap和frysk)将有助于增强应用程序的性能。

  GNU调试器

  GDB是最著名的自由和开源软件之一。它被大量的GNU软件项目和许多与GNU无关但希望拥有高质量调试器的第三方软件所使用。事实上,许多第三方工具合并了gdb,并将其作为其调试功能的基础,即使它们已经在gdb之上建立了各种级别的图形抽象。你可能见过GDB——,但你可能完全没有意识到。

  GDB基于一个基本概念,即任何调试器都有两个组件。首先,GDB的底层处理单个进程或线程的启动和关闭,跟踪代码执行,以及在运行的代码中插入和删除断点。GDB支持大量不同的平台和机制来在各种架构上实现这些(看似简单的)操作。由于底层硬件功能的影响,其特定功能可能会偶尔发生变化。

  GDB甚至支持连接到硬件调试设备,如Abatron BDI2000。它是一个专用的CPU级调试器,可以通过停止和启动CPU本身来远程调试运行在另一个系统上的Linux内核。与其他拙劣的调试器设计技术相比,重新实现一个调试器,GDB更受欢迎。如果您是嵌入式开发人员,您可能会遇到这种用法。

  基于调试应用程序所必需的底层功能,需要程序员可以有效使用的高级接口。GDB提供了一个带有一组标准命令的接口,不管它运行在什么硬件上。这可能是它如此受欢迎的原因之一。你只需要学习一次核心命令。

  1.编译代码以供GDB使用

  GNU调试器以看似简单的命令行工具gdb的形式出现。它需要一个程序名作为它的参数之一,或者在执行前需要加载一个程序。然后,它将启动调试器环境并等待指令。直到你明确地告诉gdb在调试器环境中开始执行程序,程序才会开始执行。

  例如,我们创建了一个简单的Hello World程序供gdb使用:

  此示例代码使用一个函数来显示欢迎消息,因为这将导致主程序至少进行一次函数调用。当使用调试器时,您会注意到创建了一个新的堆栈框架,并且您可以看到函数调用的效果,这不仅仅是使用标准的测试程序。3354关键的一点是,在示例中使用函数调用是有原因的,因为稍后测试GDB的函数时,您将会用到它。

  您可以使用GCC来编译这个示例程序:

  请注意GCC命令行中指定的-g标记。它告诉GCC额外的调试符号和源代码级别的信息必须添加到最终的可执行文件中。与使用普通二进制文件相比,GDB能为前者提供更详细的信息。GDB确实需要这些额外的信息(为了节省空间,它们通常从Linux二进制文件中剥离出来),这样它就可以提供源文件的行号和其他相关信息。如果您在尝试编译以下示例时,没有在最终的可执行文件中包含调试信息,您很快就会看到它们的输出有所不同。

  除了-g标志,您可能还想在某些平台上提供其他GCC命令标志(尤其是那些出于性能原因优化机器代码指令调度的平台)。具有精简指令集的现代非x86处理器就是这种情况,因为当前的趋势是优化编译器,而不是使机器指令集复杂化。这意味着在实践中,关机指令的重新调度将导致有意义的GDB输出。请参考第2章对GCC命令行标签的描述。

  2.开始GDB

  一旦用正确的命令标记编译了应用程序,就可以通过调用GDB命令将hello程序加载到gdb中,如下所示:

  GDB将hello ELF二进制文件加载到内存中,并设置程序运行的环境。然后它会同时在程序的二进制文件和任何连接到程序的库文件中寻找有用的符号。run命令用于正常运行程序:

  一切都很好,只是从头到尾运行一个程序并没有特别大的用处。是时候让GDB强大的指挥系统发挥作用了。下面将分别讨论其中的一些命令。你也可以通过GDB内置的帮助系统和现有的系统文档(以及GDB专著)找到一套完整的命令及其说明。GDB的帮助系统将命令集分为以下几类:

  例如,如果您想获得控制程序执行的命令列表,可以在提示符下输入help running。请花点时间浏览一下GDB可用的命令集,这样以后需要用到的时候就不用经常查命令名了。

  l设置断点

  仅用GDB运行一个程序并不特别令人兴奋,除非你还能在程序运行时控制它的执行。此时,是break命令发挥作用的时候了。通过在运行时在程序中插入断点,可以让GDB在程序指令到达特定点时停止程序的执行。在程序执行的开始(即在主函数的入口处)设置这样的断点是很常见的(甚至是习惯性的):

  您也可以使用break命令的简短形式:

  也会达到同样的效果。

  然后使用run命令运行程序:

  请注意GDB是如何在指定的断点处暂停程序的执行,然后由你来决定是继续运行还是执行另一个操作。您可以使用step和next命令继续执行程序。step命令会继续执行程序,直到到达程序源代码的新一行,而stepi命令只向前执行一条机器指令(对于处理器来说,源代码中的一行语句可能意味着很多条机器代码指令)。

  下一个命令和nexti变体的行为与上面两个命令类似,但它们在遇到函数调用时不会进入——内部的函数,以便您在调试代码时不需要担心函数调用。这个命令非常有用,因为C函数库可能没有与您正在调试的应用程序相同级别的调试信息。因此,遵循标准的C库函数可能没有意义,即使您有兴趣了解这些函数的实现。

  请注意,从技术上来说,在主函数的入口插入断点并不会一开始就停止程序的执行,因为所有运行在Linux上的基于C语言的程序都会利用GNU C函数库来做运行时调用主函数的安排。因此,当到达断点时,许多额外的库函数已经执行了大量的设置工作。

  3.显示数据

  你可以在GDB使用打印命令来查询程序中存储的数据。例如,您可以显示通过run命令传递给示例程序的可选参数。传递给run命令的参数将作为argv参数列表传递给被调用的程序。下面显示了当我们使用添加的参数调用一个简单程序时,GDB生成的输出:

  可以看到程序的参数列表,空字符在argv列表的末尾。示例程序是否使用这个参数表并不重要。——它总是存在的,你可以调用GDB的打印命令来返回它的值,如上面的输出所示。

  4.回溯

  GDB提供了许多有用的堆栈帧管理命令,包括几个专门用于查看程序如何到达其当前位置的命令。最有用的命令之一是backtrace (bt),它可以用来查看程序运行到当前位置之前的所有堆栈帧。以下是backtrace命令提供的信息示例:

  在上面的GDB会话中,可以清楚地看到程序是如何进入print_hello函数并导致生成bt(backtrace)命令列出的两个堆栈帧的。第一个(也是最里面的一个)是当前堆栈框架,由print_hello函数使用,而外部堆栈框架由全局函数main在调用print_hello之前用来保存其局部变量。

  5.一个有错误的例子

  到目前为止,您已经看到了一些在GDB环境中使用命令的例子,但是您还没有尝试调试一个有实际错误的真实程序!以下示例将向您展示如何使用GDB来完成日常调试任务:定位错误的指针引用、程序崩溃后的回溯以及其他相关操作。

  您可以通过下面的简单示例程序开始GDB的调试之旅。源文件buggy.c中的代码定义了一个linked_list结构,并通过使用head-first链表插入算法来分配10个元素。不幸的是,链表中的数据元素被分配给了未分配的内存(strncpy不是用预分配的内存调用的)。更糟糕的是,程序没有释放分配的内存3354。我们将在本节的后面介绍更多关于检测内存泄漏的内容。

  下面是一个有错误的简单程序:

  您可以使用GCC编译并运行这个程序,如下所示:

  如您所见,该程序不幸由于一个错误而崩溃,这意味着它试图访问分配的数据存储之外的内存。通常,发生这种情况是因为指向非法位置而不是合法内存位置的指针被解引用(通常没有简单的方法来知道存储在内存位置的数字是否是真正的指针,并且可能为时已晚)。在此示例中,发生错误是因为代码试图将数据写入尚未分配内存的字符串。

  您可以将这个有错误的程序装入GDB,然后再次运行:

  这一次,程序崩溃被GDB发现了,它准备调试程序。现在,您可以使用bt(backtrace)命令来找出程序在由于错误的内存取消引用而收到致命信号时做了什么。缩写的bt命令可以加快输入速度:

  根据上面backtrace命令的输出,我们可以看到bug源程序的第26行导致调用C库函数strncpy,此时程序崩溃。显然,我们需要查看源程序中的那行代码来找出问题所在:

  这一行将test_string复制到链表元素tmp的数据成员中,但是数据成员事先没有初始化,所以使用一个随机的内存位置来存储字符串。这个类的内存误用将很快导致预期的并且可能不可避免的程序崩溃。我们需要做的就是在strncpy之前做一个简单的malloc调用(或者使用一个调用malloc的字符串复制函数)。在这种情况下,您只需要将数据成员设置为指向静态字符串test_string,以避免任何字符串复制操作。

  要解决这个错误,您可以将示例源代码中的strncpy调用替换为:

  这个程序现在可以正常编译和运行了:

  6.调试核心转储文件。

  传统上,当程序崩溃时,UNIX和类UNIX系统将执行核心转储或提供程序状态的二进制输出。现在很多Linux发行版为了节省磁盘空间,关闭了普通用户创建核心转储文件的功能(让这些普通用户可能一无所知的核心转储文件散落在磁盘周围,既浪费空间,又让用户感到不安)。在正常情况下,特定的Linux发行版将使用ulimit(用户限制)命令来控制核心转储文件的创建。

  您可以使用ulimit命令查看当前的用户限制:

  如您所见,核心转储文件的大小被设置为0(禁用)。您可以通过向ulimit命令的-c可选标志传递新值来重置该值。它应该设置为磁盘块中的最大核心转储文件大小。下面是一个重置核心转储文件大小以服务于上述示例程序的示例(为简洁起见,输出已被简化):

  请注意,在您的Linux发行版上,这可能不足以重置核心转储文件的大小,这取决于您的Linux系统的特定配置。

  当设置了适当的核心转储文件大小时,重新运行示例程序将导致真正的核心转储:

  您将在当前目录中看到一个新的核心转储文件:

  值得注意的是,在这个特定的Linux发行版中,核心转储文件的文件名包含原始程序的进程号。您的Linux发行版提供的内核配置中可能没有启用这个可选特性。

  GDB可以读取核心转储文件,并基于它启动调试会话。由于核心转储文件是由一个不再运行的程序生成的,因此并非所有gdb命令都可以使用——。例如,试图在一个不再运行的程序中执行单步执行操作是没有意义的。但是核心转储文件还是很有用的,因为你可以离线调试,所以你的用户可以通过邮件把核心转储文件及其本地机器环境发给你[]方便你远程调试。

  您可以对示例核心转储文件运行GDB:

  然后,您可以使用我们之前介绍的相同方法继续调试会话。只要记住程序流控制命令不能用,因为程序已经停止运行了。

  Valgrind

  Valgrind是一个运行时诊断工具,可以监控指定程序的活动,并通知您代码中可能存在的各种内存管理问题。它类似于旧的电动围栏工具(用自己的函数代替标准的内存分配函数来提高诊断能力),但它被认为更容易使用,并在许多方面提供了更丰富的功能。3354和现在大多数主流的Linux发行版都提供了这个工具,所以在你的系统中使用它并不需要花费太多时间,你只需要安装它的软件包就可以了。

  典型的Valgrind操作可能如下所示:

  输出80字节的内存在程序结束时丢失。通过指定泄漏检查选项,我们可以找出泄漏的内存来自哪里:

  如果可能的话,您应该养成使用Valgrind之类的工具的习惯,以便自动执行查找和修复内存泄漏和其他编程错误的过程。因为这里只简单介绍了Valgrind,所以你需要查阅它的在线文档,对它的功能有更全面的了解。事实上,越来越多的开源项目依赖Valgrind作为其回归测试的一部分(任何具有相当规模的软件项目的重要部分)。

  自动化代码分析

  有越来越多的第三方工具可以用来执行自动化代码分析,并找到软件中各种典型类型的缺陷。这种代码覆盖工具通常提供静态、动态或混合形式的代码分析。这意味着该工具可能只是检查源代码以确定潜在的缺陷,或者它可能试图挂钩到一些其他的过程以获得必要的数据来确定软件中缺陷的可能位置。

  基于斯坦福大学checker的商业代码分析工具Coverity常用于Linux系统。它挂接在编译过程中,提取了很多有用的信息,可以用来发现很多潜在的问题。事实上,Coverity为越来越多的开源项目提供免费的代码分析。它甚至在Linux内核中发现了不少以前未被发现的错误。这些问题被发现后立即得到了解决。

  静态代码分析的一个有趣用途是发现源代码中是否有非法使用GPL代码的情况。黑鸭软件提供了这样一个工具,它可以帮助你扫描你的大型软件项目,找到从开源项目借用的源代码,确定处理方法。这对于兼容性测试和您的法律团队可能提醒您的其他活动非常有用。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: