跳至主要内容

【转】linux下的调试工具

转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli at hotmail dot com>

 

随着XP的流行,人们越来越注重软件的前期设计、后期的实现,以及贯穿于其中的测试工作,经过这个过程出来的自然是高质量的软件。甚至有人声称XP 会淘汰调试器!这当然是有一定道理的,然而就目前的现实来看,这还是一种理想。在日常工作中,调试工具还是必不可少的。在Linux下,调试工具并非只有 gdb,还有很多其它调试工具,它们都各有所长,侧重方面也有所不同。本文介绍几种笔者常用的调试工具:

 

1. mtrace

在linux下开发应用程序,用C/C++语言的居多。内存泄露和内存越界等内存错误,无疑是其中最头疼的问题之一。glibc为解决内存错误提供了两种方案:

一种是hook内存管理函数。hook内存管理函数后,你可以通过记下内存分配的历史记录,在程序终止时查看是否有内存泄露,这样就可以找出内存泄露的地方了。你也可以通过在所分配内存的首尾写入特殊的标志,在释放内存时检查该标志是否被破坏了,这样就可以达到检查内存越界问题的目的。

另外一种方法更简单,glibc已经为第一种方案提供了默认的实现,你要做的只是在特定的位置调用mtrace/muntrace两个函数,它们的函数原型如下:

#include

void mtrace(void);

void muntrace(void);

你可能会问,在哪里调这两种函数最好?这没有固定的答案,要视具体情况而定。对于小程序来说,在进入main时调用mtrace,在退出main函数时调用muntrace。对于大型软件,这样做可能会记录过多的信息,分析这些记录会比较慢,这时可以在你所怀疑代码的两端调用。

另外,还需要设置一个环境变量MALLOC_TRACE,它是一个文件名,要保证当前用户有权限创建和写入该文件。glibc的内存管理器会把内存分配的历史信息写入到MALLOC_TRACE指定的文件中。

程序运行完毕后,使用mtrace工具分析这些内存分配历史信息,可以查出内存错误的位置(mtrace在glibc-utils软件包里)。

 

2. strace

在编程时,检查函数的返回值是一种好习惯。对于像glibc等标准C的函数,光检查返回值是不够的,还需要检查errno的值。这样的程序往往显得冗长,不够简洁。同时也可能是出于偷懒的原因,大多数程序里并没有做这样的检查。

这样的程序,一旦出现错误,用调试器一步一步定位错误,然后想法查出错误的原因,也是可以的,不过比较麻烦,对调试器来说有些大材小用,不太可取。这时,用strace命令可能会更方便一点。它可以显示各个系统调用/信号的执行过程和结果。比如文件打开出错,一眼就看出来了,连错误的原因 (errno)都知道。

 

3. binutil

binutil是一系列的工具,你可能根本不知道它们的存在,但是没有它们你却寸步难行。Binutil包括下列工具:

* ld - the GNU linker.
* as - the GNU assembler.
* addr2line - Converts addresses into filenames and line numbers.
* ar - A utility for creating, modifying and extracting from archives.
* c++filt - Filter to demangle encoded C++ symbols.
* gprof - Displays profiling information.
* nlmconv - Converts object code into an NLM.
* nm - Lists symbols from object files.
* objcopy - Copys and translates object files.
* objdump - Displays information from object files.
* ranlib - Generates an index to the contents of an archive.
* readelf - Displays information from any ELF format object file.
* size - Lists the section sizes of an object or archive file.
* strings - Lists printable strings from files.
* strip - Discards symbols.
* windres - A compiler for Windows resource files.

其中部分工具对调试极有帮助,如:

你可以用objdump反汇编,查看目标文件或可执行文件内部信息。

你可以用addr2line把机器地址转换到代码对应的位置。

你可以用nm查看目标文件或可执行文件中的各种符号。

你可以用gprof分析各个函数的使用情况,找出性能的瓶颈所在(这需要加编译选项)。

 

4. ld-linux

现在加载ELF可执行文件的工作,已经落到ld-linux.so.2头上了。你可能会问,这与有调试程序有关系吗?有的。比如,在linux中,共享库里所有非static的函数/全局变量都是export的,更糟的是C语言中没有名字空间这个概念,导致函数名极易冲突。在多个共享库中,名字冲突引起的BUG是比较难查的。这时,你可以通过设置LD_ DEBUG环境变量,来观察ld-linux.so加载可执行文件的过程,从中可以得到不少帮助信息。LD_ DEBUG的取值如下:

* libs display library search paths
* reloc display relocation processing
* files display progress for input file
* symbols display symbol table processing
* bindings display information about symbol binding
* versions display version dependencies
* all all previous options combined
* statistics display relocation statistics
* unused determined unused DSOs
* help display this help message and exit

 

5. gdb

对于真正意义的调试器来说,gdb在linux下是独一无二的。它有多种包装,有字符界面的,也有图形界面的,有单独运行的,也有集成到IDE中的。gdb功能强大,图形界面的gdb容易上手一点,但功能无疑受到了一些限制,相信大部分高手还是愿意使用字符界面的。Gdb太常用了,这里不再多说。

 

6. gcc/boundschecker

相信很多人用过win32下的BoundsChecker(Compuware公司)和Purify(IBM公司)两个工具吧。它们的功能实在太强大了,绝非能通过重载内存管理函数就可以做到,它们在编译时插入了自己的调试代码。

gcc也有个扩展,通过在编译时插入调试代码,来实现更强大的检查功能。当然这要求重新编译gcc,你可以到http: //sourceforge.net/projects/boundschecking/ 下载gcc的补丁。它的可移植性非常好,笔者曾一个ARM 平台项目里使用过,效果不错。

 

7. valgrind

最好的东西往往最后才见到。Valgrind是我的最爱,用习惯了,写的程序不在valgrind下跑一遍,就像没有写单元测试程序一样,有点放心不下。它有BoundsChecker/Purify的功能,而且速度更快。

有点遗憾的是valgrind目前只支持x86平台,当然,这对大多数情况已经足够了。

你可以到http://valgrind.org/ 下载最新版本。

评论

此博客中的热门博文

【转】AMBA、AHB、APB总线简介

AMBA 简介 随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大。数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用。在基于IP复用的SoC设计中,片上总线设计是最关键的问题。为此,业界出现了很多片上总线标准。其中,由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外围总线。   AMBA 片上总线        AMBA 2.0 规范包括四个部分:AHB、ASB、APB和Test Methodology。AHB的相互连接采用了传统的带有主模块和从模块的共享总线,接口与互连功能分离,这对芯片上模块之间的互连具有重要意义。AMBA已不仅是一种总线,更是一种带有接口模块的互连体系。下面将简要介绍比较重要的AHB和APB总线。 基于 AMBA 的片上系统        一个典型的基于AMBA总线的系统框图如图3所示。        大多数挂在总线上的模块(包括处理器)只是单一属性的功能模块:主模块或者从模块。主模块是向从模块发出读写操作的模块,如CPU,DSP等;从模块是接受命令并做出反应的模块,如片上的RAM,AHB/APB 桥等。另外,还有一些模块同时具有两种属性,例如直接存储器存取(DMA)在被编程时是从模块,但在系统读传输数据时必须是主模块。如果总线上存在多个主模块,就需要仲裁器来决定如何控制各种主模块对总线的访问。虽然仲裁规范是AMBA总线规范中的一部分,但具体使用的算法由RTL设计工程师决定,其中两个最常用的算法是固定优先级算法和循环制算法。AHB总线上最多可以有16个主模块和任意多个从模块,如果主模块数目大于16,则需再加一层结构(具体参阅ARM公司推出的Multi-layer AHB规范)。APB 桥既是APB总线上唯一的主模块,也是AHB系统总线上的从模块。其主要功能是锁存来自AHB系统总...

【转】VxWorks套接口

int m_socket;   // Open a socket        m_socket = socket(AF_INET, SOCK_STREAM, 0);   第一个参数 domain 说明我们网络程序所在的主机采用的通讯协族 (AF_UNIX 和 AF_INET 等 ). AF_UNIX 只能够用于单一的 Unix 系统进程间通信 , 而 AF_INET 是针对 Internet 的 , 因而可以允许在远程主机之间通信 . VxWorks 套接字仅支持 Internet 域地址族 , 不支持 UNIX 域地址族 . 因此在需要 domain 参数的函数中 , 使用 AF_INET 作为函数参数值 . 第二个参数 type 说明我们网络程序所采用的通讯协议 ( SOCK_STREAM , SOCK_DGRAM 等 ). SOCK_STREAM 表明我们用的是 TCP 协议 , 这样会提供按顺序的 , 可靠 , 双向 , 面向连接的比特流 . SOCK_DGRAM  表明我们用的是 UDP 协议 , 这样只会提供定长的 , 不可靠 , 无连接的通信 . 此外,还有 SOCK_RAW 代表是原始协议套接字 . 第三个参数 protocol, 由于我们指定了 type, 所以这个地方我们一般只要用 0 来代替就可以了 . socket 为网络通讯做基本的准备 , 成功打开则返回一个套接字描述符 , 如果失败则返回 ERROR. 套接字描述符是一个标准的 I/O 系统文件描述符 (fd, file descriptor), 可以被 close(), read(), write() 和 ioctl() 函数使用 .   // Make the socket sending alive messages when connected int flag = 1; setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, (char*)&flag, sizeof(flag));   // increase receive buffer siz...

【转】C++/CLI程序进程之间的通讯

 现在,把大型软件项目分解为一些相交互的小程序似乎变得越来越普遍,程序各部分之间的通讯可使用某种类型的通讯协议,这些程序可能运行在不同的机器上、不同的操作系统中、以不同的语言编写,但也有可能只在同一台机器上,实际上,这些程序可看成是同一程序中的不同线程。而本文主要讨论C++/CLI程序间的通讯,当然,在此是讨论进程间通讯,而不是网络通讯。    简介   试想一个包含数据库查询功能的应用,通常有一个被称为服务端的程序,等待另一个被称为客户端程序发送请求,当接收到请求时,服务端执行相应功能,并把结果(或者错误信息)返回给客户端。在许多情况中,有着多个客户端,所有的请求都会在同一时间发送到同一服务端,这就要求服务端程序要更加高级、完善。   在某些针对此任务的环境中,服务端程序可能只是众多程序中的一个程序,其他可能也是服务端或者客户端程序,实际上,如果我们的数据库服务端需要访问不存在于本机的文件,那么它就可能成为其他某个文件服务器的一个客户端。一个程序中可能会有一个服务线程及一个或多个客户线程,因此,我们需小心使用客户端及服务端这个术语,虽然它们表达了近似的抽象含义,但在具体实现上却大不相同。从一般的观点来看,客户端即为服务端所提供服务的"消费者",而服务端也能成为其他某些服务的客户端。    服务端套接字   让我们从一个具体有代表性的服务端程序开始(请看例1),此程序等待客户端发送一对整数,把它们相加之后返回结果给客户端。   例1: using namespace System; using namespace System::IO; using namespace System::Net; using namespace System::Net::Sockets; int main(array<String^>^ argv) { if (argv->Length != 1) { Console::WriteLine("Usage: Server port"); Environment::Exit(1); } int port = 0; try { port = Int32::Parse(argv[0]); } catch (FormatException^ e) { Console::Wri...