《软件调试的艺术》学习笔记——GDB使用技巧摘要
《软件调试的艺术》学习笔记——GDB使用技巧摘要
《软件调试的艺术》,因为名是The Art of Debugging with GDB, DDD, and Eclipse. 作者是美国的Norman Matloff和Peter Jay Salzman,中文版由张云翻译。是人邮出版社图灵程序设计丛书初版。
这里称为"艺术",个人觉得有点过了,但是其中关于gdb以及在gdb基础之上集成的DDD和Eclipse调试技巧的整理确实是做的很好,对于Linux/开源社区下的开发人员以及其他平台技术gdb调试的开发人员都是很有帮助。毕竟有句话叫做:程序是调出来的,不是写出来的。这句话的意思没有抹杀程序设计思想、框架、优良编码风格的意味,只是谁都知道,软件怎么可能没有bug呢?
下面是整理的内容:
(DDD和Eclipse在调试时确实有其独特之处,但是我都没有怎么用过,所以就没有阅读其中的内容,自然也没有整理)
调试的原则
调试的本质:确认原则
其他原则
从简单工作开始调试
使用自顶向下的方法
使用调试工具确定段错误的位置
通过发出中断确定无限循环的位置
使用二分搜索
调试的原则是和具体调试工具无关的,是程序员对代码世界的分析、认识的一种方法学和思路,是对程序运行时状态变化作出的一种习惯、相对正确的反应。
修改充满错误的程序(很多年轻的程序员开始都认为自己的程序是没有错误的,其实很多细节、编码规范都可能存在问题),就是逐个去确认你认为正确的代码确实是正确的。当你发现其中某个假设不成立时,就表示已经找到了关于程序错误所在位置的线索(甚至是错误本身)。这就是确认原则。
这里无意讨论基于文本的调试工具和基于GUI的调试工具的差别,应该说两者各有千秋,用在合适的地方就是好,在合适的人手里就是合适的武器。
值得介绍的是GDB有个-tui选项,和Ctrl+X+A组合键可以在文本模式和TUI模式之间切换。CGDB (http://cgdb.sf.net) 是对其的一种增强和改善。
开始介绍GDB的知识点。
要用gdb调试能够有代码显示,必须用-g选项进行编译源码。另外使用gdb的时候,尽量多用tab键。
• 断点
下断点是break(缩写是b),后面可以接行号(或者文件名:行号)、函数名、内存指令地址;
tbreak是临时断点,有限次数为一次;
对应的高级技巧有条件断点。比如
(gdb) break 30 if num_y == 1
这表示在第三十行下条件断点,条件为num_y==1是中端程序运行
删除断点用delete,禁用/启用断点为disable/enable
到断点了可以step/next/continue等命令执行,还有clear、condition等命令可以使用,多看help
• 观察点
还可以用观察点来查看变量、内存、表达式的值是否改变,同样可以加条件。多试试就记住了。命令是watch
• 查看变量
查看变量是print 后面接变量名,也可以是表达式,函数等,灰常强大
调试的时候一般开两个终端,一个是gdb调试终端,一个编辑、编译终端,这样gdb发现问题的时候就不用退出,直接在另外一个终端修改好编译后直接在gdb里面重新ruan程序就行。这样的好处是之前设置的断点、宏等都还存在。
command命令可以在每次发生断点的时候自动执行一系列命令,解放程序员的双手。
见示例:
(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end"
>silent
>printf "fibonacci was passed %d./n", n
> continue
>end
(gdb) run
Starting program: fibonacci
fibonacci was passed 3.
fibonacci was passed 2.
fibonacci was passed 1.
fibonacci was passed 0.
fibonacci was passed 1.
Fibonacci(3) is 3.
Program exited normally.
(gdb)
《软件调试的艺术》学习笔记——GDB使用技巧摘要——程序崩溃处理
程序为什么会崩溃
内存中的程序布局
当某个错误导致程序突然和异常地停止执行时,程序崩溃。迄今为止最为常见的导致程序崩溃的原因是试图在未经允许的情况下访问一个内存位置。硬件会感知这件事,并执行对操作系统的跳转。
Unix系列的平台上,操作系统一般会宣布程序导致了段错误(seg fault),并停止程序运行。
在微软的windows系统上,对应的术语是一般保护错误(general protection fault)。无论是哪个名称,硬件都必须支持虚拟内存,而且操作系统必须使用虚拟内存才会发生这个错误。
虽然这是如今的通用计算机的标准,但是读者应记住,专用的小型计算机一般没有这种情况,比如用来控制机器的嵌入式计算机。
程序在内存中是如何分布的?
在Unix平台上,为程序分配的虚拟地址的布局通常如下:
.text
.data
.bss
堆内存
↓
未使用
↑
栈内存
env
0地址
这里虚拟地址0在最下方,箭头显示了其中两个组件(堆和栈)的增长方向,当它们增长时,消耗掉未使用的自由区域。
各个部分的作用如下:
• 文本区域(.text),由程序源代码中的编译器产生的机器指令组成。这一组件包括静态链接代码,包括做初始化工作然后调用main()的系统代码/usr/lib/crt0.o
• .data 数据区域,包含在编译时分配的所有程序变量,即全局变量。如 int x = 5;
• .bss 数据区域,包含的是存放未初始化数据的全局变量,如 int y;
• 当程序在运行时从操作系统请求额外的内存时(例如,C语言中调用malloc,或者C++中的new),请求的内存在名为堆(heap)的区域中分配。如果堆空间不足,可以通过调用brk()来扩展堆(这正是malloc及相关函数所做的事情)
• 栈区域(stack),是用来动态分配数据的空间。函数调用的数据(包括参数、局部变量和返回地址)都存储在栈上。每次进行函数调用时栈都会增长,每次函数返回到其调用者时栈都会收缩。
• 上图没有显示程序的动态链接代码,它的位置与平台相关,但是它确实在某个地方存在,而且在每种操作系统上,虚拟内存中关于动态链接代码的地址是有规律的。
int q[ 200 ] ;
int main( void ) {
int i,n, *p;
p = malloc( sizeof ( int ) ) ;
scanf( "%d" , &n) ;
for (i = 0 ; i < 200 ; i++ )
q[i] = i;
printf ( "%x %x %x %x %x/n" , main, q, p, &i, scanf) ;
return 0 ;
}
虽然上述程序本身作用不大,但是可以编写成一个工具来非正式地探索虚拟地址空间的布局。运行结果可能如下:
% a.out
5
80483f4 80496a0 9835008 bfb3abec 8048304
分别对应于文本区域、数据区域、堆、栈和动态链接函数位置。
另外可以通过查看这一过程的maps文件来得到程序在linux上的精确内存布局情况。加入进程号是21111,那么查看的文件应该是/proc/21111/maps
页的概念
虚拟地址空间是通过坐直成成为页(page)的块来查看的。在pentium硬件上,默认的页大小是4096字节。物理内存(包括RAM和ROM)也都是分成页来查看的。当程序被加载到内存中执行时,操作系统会安排程序的部分页存储在物理内存的页中。这些页成为被"驻留",其余部分存储在磁盘上。
在执行期间的各个阶段,将需要一些当前没有驻留的程序页。当发生这种情况的时候,硬件会感知到,将控制权转移给操作系统。后者将所需页带到内存中,可能会替换掉当前驻留的另一个程序页(如果没有可用的自由内存页),然后将控制权返回给程序。如果有被驱逐的程序页,就会变成非驻留页,被存储在磁盘上。
为了管理所有这些操作,操作系统为每个过程设立了一个页表(page table)。(pentium的页表有一个层次结构,但是为了简单起见,假定只有一层,而且这里讨论的大多数内容都不是pentium特有的。)这一过程的每个虚拟页在表中都有对应的一个项(entry),其中包括如下信息:
这个页在内存中或者磁盘上的当前物理位置。如果是在磁盘上,页表对应的项会指示页是非驻留的,可能包含一个指针,指向最终导致磁盘上的物理位置的一个列表。例如,它可能显示:程序的虚拟页12是驻留的,位于内存的物理页200中。
该页的权限分为3种:读、写和执行
注意:操作系统不会将不完整的页分配给程序。例如,如果要运行的程序总共有10 000字节,如果完全加载,会占用3个内存页(3*4096),不会是占用2.5个页,因为页是虚拟内存系统能够操作的最小内存单元。这是调试时要记住的很重要的一点,因为这一点暗示了程序的一些错误内存访问不会触发段错误。换言之,在调试会话期间,不能这么想:"这行代码一定没有问题,因为它没有引起段错误。"
产生段错误的真正原因:权限不匹配
在程序的运行期间,生成的地址会是虚拟的。当程序试图访问某个虚拟地址处的内存时,比如y,硬件就会将其转化为虚拟页号v,它等于y除以4096(其中除法是整除算法,舍去余数)。然后硬件会检查页表中的页表项v来查看该页的权限是否与要执行的操作匹配。如果匹配,硬件会从这个表项中得到所需位置的实际物理页号,然后完成请求的内存。但是如果该表项显示请求的操作不具有恰当的权限,硬件就会执行内部中断。这会导致跳转到操作系统的错误处理例程。然后,操作系统一般会宣告一个内存访问违例,并停止程序的执行(即从进程表和内存中去掉程序)。
程序中的错误会导致权限不匹配,并在上面列出的某个类型的内存访问周期生成段错误。
段错误可以发生在数据区域、堆栈等位置。报告错误的地方,往往不是问题本质所在,需要在附近回溯一下。
常用的调试方法有核心文件(core file)或者signal.h文件中提供的signal()或者sigaction()两个系统调用来捕获。
《软件调试的艺术》学习笔记——GDB使用技巧摘要——Pthread线程调试
Unix下最普遍的线程包是POSIX标准的Pthreads。Pthreads使用的抢占式线程管理策略,程序中的一个线程可能在任何时候被另一个线程中断。所以,使用Pthreads开发的应用程序有些错误不太容易重现。
GDB线程相关命令汇总
info threads 给出关于当前所有线程的信息
thread n 改为线程n,或者说是进入线程n的栈中进行观察
break line_num thread n 表示当线程n到达源码行line_num时停止执行
break line_num thread n if expression 上一命令增加条件断点而已
加入怀疑线程之间有死锁,可以用gdb进行调试定位。流程大致是:
• 用gdb启动或者插入待调试程序
• 当程序挂起时候,通过按下Ctrl+C组合键中断它;
• 这个时候用info threads查看所有线程都在干嘛,然后找到自己的工作线程(注意排除main线程和pthreads的管理线程)
• 分别查看自己的工作线程在干嘛,用bt(backtrace)查看对应的帧,记得用thread n切换进入对应线程的帧
• 关注像__pthread_wait_for_restart_signal()和lock等函数,如果有源码的话,会比较方便地定位到具体的问题代码位置
下面是一个简单的例子。如果在worker线程里面,上锁和解锁没有匹配,则会发生死锁
// finds the primes between 2 and n; uses the Sieve of Eratosthenes,
// deleting all multiples of 2, all multiples of 3, all multiples of 5,
// etc.; not efficient, e.g. each thread should do deleting for a whole
// block of values of base before going to nextbase for more
// usage: sieve nthreads n
// where nthreads is the number of worker threads
#include <stdio.h>
#include <math.h>
#include <pthread.h>
#define MAX_N 100000000
#define MAX_THREADS 100
// shared variables
int nthreads, // number of threads (not counting main())
n, // upper bound of range in which to find primes
prime[MAX_N+1], // in the end, prime[i] = 1 if i prime, else 0
nextbase; // next sieve multiplier to be used
int work[MAX_THREADS]; // to measure how much work each thread does,
// in terms of number of sieve multipliers checked
// lock index for the shared variable nextbase
pthread_mutex_t nextbaselock = PTHREAD_MUTEX_INITIALIZER;
// ID structs for the threads
pthread_t id[MAX_THREADS];
// "crosses out" all multiples of k, from k*k on
void crossout(int k)
{ int i;
for (i = k; i*k <= n; i++) {
prime[i*k] = 0;
}
}
// worker thread routine
void *worker(int tn) // tn is the thread number (0,1,...)
{ int lim,base;
// no need to check multipliers bigger than sqrt(n)
lim = sqrt(n);
do {
// get next sieve multiplier, avoiding duplication across threads
pthread_mutex_lock(&nextbaselock);
base = nextbase += 2;
pthread_mutex_unlock(&nextbaselock);
if (base <= lim) {
work[tn]++; // log work done by this thread
// don't bother with crossing out if base is known to be
// composite
if (prime[base])
crossout(base);
}
else return;
} while (1);
}
int main(int argc, char **argv)
{ int nprimes, // number of primes found
totwork, // number of base values checked
i;
void *p;
n = atoi(argv[1]);
nthreads = atoi(argv[2]);
for (i = 2; i <= n; i++)
prime[i] = 1;
crossout(2);
nextbase = 1;
// get threads started
for (i = 0; i < nthreads; i++) {
pthread_create(&id[i],NULL,(void *) worker,(void *) i);
}
// wait for all done
totwork = 0;
for (i = 0; i < nthreads; i++) {
pthread_join(id[i],&p);
printf(" %d
values of base done
/n
",work[i]);
totwork += work[i];
}
printf("
%d
total values of base done
/n
",totwork);
// report results
nprimes = 0;
for (i = 2; i <= n; i++)
if (prime[i]) nprimes++;
printf("the number of primes found was %d
/n
",nprimes);
}
软件调试的艺术 The Art of Debugging with GDB, DDD, and Eclipse
马特洛夫(Matloff, N.), (美)萨尔兹曼(Salzman, P J.)著 张云译
第1章是概览。简单却有用的通用准则。
第2章着重介绍 一断点
第3章 重点是介绍当到达断点时如何方便地显示树中节点的内容。 用图形显示树和其他链接数据结构的DDD功能。
第4章包括了由于段错误(即内存访问错误)而而产生的致命运行时错误。崩溃时在底层情况,包括程序的内存分配以及硬件与操作系统的协同作用。
第5章不但介绍并行编程,而且包拒活网络代码。L述基本内容:分时、进程与线程\竞争条件等。 使用线程的技术细节,要记住的通用原则,比如发生线程上下文切换时的时间选择随机性。用流行的MPI 和OpenMP程序包进行并行编程.
第6章包括其他一些重要主题。乡 上理由于缺少必要的库造成的连接失败问题,库的类型以及如何将库与主要代码连挂接。显示如何让GDB, DD和Eclipse与curses窗口中的事件交互。
第7章介绍了部分辅助工 具,全书主要介绍的是C一编程的调试;
第8章则谈到了其他语言,包括,Java, Python, Perl和 汇编.
Norm Matloff.与Pete Salzman 于2008年6月9日
GDB yGNU项目调试器(GNU Project Debugger)诊下载GCC编译
DDD (Data Display Debugger,数据显示调试器)用户通过GUI发出命令,GUI将这些命令传递给 GDB .
Fedora Linux: yum install ddd 在Ubuntu Linux上,可以使用命令apt-get.
1.3调试的原则 虽然调试是一门艺术而非科学,但是仍然有一些明确的原则来指导调试的实践.
确认的基本原则(Fundamental Principle of Confirmation) 在本质上 是相当正式的原则。
1.3.2,调试的本质:确认原则 。确认的基本原则
修正充满错误的程序,就是逐个确认,你自认为正确的许多事情所对应的代码确实是正确的。 当你发现其中某个假设不成立时,就表示已经找到了关于程序错误所在位置(可能并不是准确的 位置)的线索。
当你认为关于程序的某件事情是正确的,而在确认佑的过程中却失败了,你就会感到惊讶。 这种惊讶是好事,因为这种发现会引导你找到程序错误所在的位置
1 .3.3其他调试原则
0从简单工作开始调试
A使用自顶向下的方法
.使用调试工具确定段错误的位置
。通过发出中断确定无限循环的位置
0使用二分搜索
1.4.2折中方法
从版本6.1以来,GDB己经以名为TUI (Terminal User Interface,终端用户界面)的捞模式提供 了基于文本交互和图形用户交互之间的折中方法。在这一模式GDB将终端屏幕划分为类似于 DDD的源文本窗口和控制台的多个子窗口:可以在类似于源文本窗口的子窗口中跟踪程序执进展过程,同时在类似于控制台的子窗口中发出GDB命令。为了以TUI模式运行GDB,可以在调用GDB时在命令行上指定一tui选项,或者处于非TUI模 式时在GDB中使用Ctrl+X+A组合键。
另一个可用的GDB界面是CGDB,该界面可以从http://cgdb.sourceforge.net/获得。CGDB也提 供了一种基于文本的方法与GUI方法之间的折中方案。
在调用GDB时可以指定启动文件; $gdb -command=z x 表示要在可执行文件上运行GDB,首先要从文件:中读取命令。
第2章 停下来环顾程序
调试器的好处在于:可以通知它暂停程序的执行。暂停以后,调试器 提供了检查变量、跟踪执行路径等的机会。
2.1暂停机制
断点:通知GDB在程序中的特定位置暂停执行,
监视点:通知GDB当特定内存位置(或者涉及一个或多个位置的表达式)的值发生变化时暂停执行。
捕获点:通知GDB当特定事件发生时暂停执行。
delete命令删除断点、监视点和捕获点!
命令列表非常有用,但是将它们与条件中断合并后,将具有更大的威力。
如果你在处理线程代码,监视点的用处就有限;(GDB只能监视单个线程中的变量。
GDB实际上是在va。的内存位置改变值时中断。一般情况下, 是否使用监视点监视变量或变量的地址并没有关系,但是在特殊情况下这一点可能很重要,比如 当处理指向指针的指针时。
第4章程序崩溃处理
不能根据没有发生段错误来得出内存操作是正确的结论。
4.1.6,段错误与Unix信号
信号(signal)表示在程序执行期间服告的异常情况,允许操作系统(或程序员自己的代码) 反映多种事件。信号可能在某个进程上由系统的底层硬件抛出(SIGSEGV或,IGFPE ),或者由另一个进程抛出(SIGUSR1或SIGUSSR2),甚至可能由该 进程本身发送(通过raise()库调用)。
一个进程上可以发出很多种不同的信号。;在Linux中,可以通过在shell提示符后面键入如下 代码来查看完整的信号列表。
man 7 signal
虽然有些信号处理程序不能被重写,但是在很多情况下可以编写自己的处理程序来替换操作 系统提供的默认处理程序。
4.2核心文件
转储核心。
4.2.1一核心文件的创建方式
核心文件包含程序崩溃时对程序状态的详细描述:栈的内容(fir者,如果程序是多线程的, 叭是各个线程的栈),CPU寄存器的内容(同样,如果程序是多线程的,则是每饿程上的一组 寄存器值),程序的静态分配变量的值(全局与stati。变量),等等 。
Unix命令file有助于指出转储这个特 定核心文件的可执行文件的名称
4.2.2某些shell可能禁止创建核心文件
在bash中,可以使用。limit命令控制核心文件的创建。
没有得到核心文件,对于bash使用ulimit -c检查当前核心文件的限制, 对于tcsh或csh则使用limit -c检查。
4.3.2在调试会话期间不要退出GDB
可以保留我们的断点
调试会话是相当实用的,包括了调试的很多方面: 确认原则; 】使用核心文件进行崩溃进程的“死后”分析; 1纠正、编译并重新运行程序,甚至不需要退出GDB; print代)风格调试的不足之处; 尔的智慧,这是无可替代的。
5.2调试多线程代码
在真正复杂的网络调试情况中,可以使用开源Ethereal程序跟踪单个TCP/IP分组。
机roux系统上,可以通过运行命令ps axH来查看系统上当前的所有i进程和线程 .
虽然有非抢占线程系统,但}pthreads;使用的是抢占线程管理策略,程序中的一个线程可能 在任何时候被另一个线程中断。
5.3 -,调试并行应用程序
并行编程架构主要有两种:共享内存和消息传递。
术语共享内存的确切含义是:多个CPU都具有对某些共同的物理内存的访问权限。
多线程编程变 为 在消息传递环境下,在各个CPU上运行的代码只能访问该CPU的本地内存,它通过通 信媒介上发送称为“消息”的字节串来与其他CPU上的代码通信。通常这是某种网纵通通用协议(比如TCP/IP)或者适用于消息传递应用程序的专门软件基础结构。
5.3.1, 消息传递系统
先以流行的MPI (Message Passing Interface)包为例,讨论消息传递。多 这里使用 的是MPICH实现,但是同样的原理也适用于LAM和其他MPI实现。
GDB允许使用进程号动态地将 调试器附加到己经运行的进程上。
debugwait的值敢自用户提供的命令行,,表示等、,2表示不等待。
5.3.2共享内存系统
将真正的共享内存机制与软件分布式共享内-A 设置的情况分开介绍。
1.真正的共享内为存
在真正的共享内存环境中,通常使用线程来开发应用程序。 OpenMP这样的机器上流行编程环境。 OpenmP指伶的多线 程实现对程序员基本上是透明的。
2,软件分布式共享内存系统
有多个处理器的大型共享内存仍然 狗替代品是工作站网络((network of workstations, NOW架构使用了可以造成共享内存错觉的底层库。这个库对应用程序员基本上是透明 的,它主要从事维护不同节点之间共享变量的副本统一性这样的网络事务。
这种方法称为软件分布式共享内存(SDSM) ,另一个优秀的软件包是JIAJIA,可以从Chinese Academy of Sciences (http://www-users.cs.umn.edu/~tianhe/paper/dist.htm )的站点下载.
5.4.1、OpenMP概述
本质上是线程管理操作的高级并行编程接口。
由于OpenMP指令需要预处理,因此总是有失去原来的行号和变量及函数名的潜在问题。
从版本4.2起,GCC也能处理OpenMP代 码了。只要在GCC命令行上添加-fopenmp标记即可。
omni编译器 http://www.hpcc.jp/omni/),
二分搜索原则对于查找未知位置的语法错误非常有帮助。
6.1.2;缺少库
在Unix系统上,按惯例是在静态库文件名后面加上后全缀.a代表archive.另外,任何库的 名称一般都以lib开头。
首先,使用ldd命令检查程序需要哪个库,如果有,操作系统可以在何处找到它们。
解决这种问题的一种方式是向该操作系统的正常搜索路径中添加 ,如果要添加几个目录,将目录名连同冒号的字符串作为分隔符。
$LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/Debug/z
$export LD_LIBRARY_PATH
2.开源软件中库的用法
源代码配 套的构建脚本(通常称为配置文件)找不到某些必需的库。试图通过设置LD_LIBRARY PATH环环境 变量可能会失败。这种问题的根源常常在于配置文件调用的名为pkgconfig的程序。这个程序会从某些元数据文 件处接收关于库的信息。这样的文件后缀为.PC,前缀是库的名称。搜索..PC文件的默认目录取决pkgconfig本身的位置。为了解决这个问题, 设置环境变量PKG CONFIG PATH。在C:,,jTC shell中,执行如下shell命令。
%setenv PKG_CONFIG_PATH /usr/lib/pkgconfig:/usr/local/lib/pkgconfig像Vim和Emacs这样的文本编辑器就是用curses编写的。
如果使用的是Vim,我们推荐Steve Outlline所著的Vi IMproved-Vim (New Riders , 2001)。
7.2 充分利用编译器
如果不使用一Wall,就几乎没有必要使用GCC.‘要慎用一Wmissing一prototypes和-Wmissing一declarations。
7.3 C语言中的错误报告
用errno
系统与库调用的失败通常是由于设置了名为errn。的全局定义整数变量。在大多数GNU/ Linux系统上,errn。是在/usr/include/errno.h上声明的,根据惯例,当库函数或系统调用失败时,它将errno 设置为一个描述调用失败原因的值。
在程序中开始使用errn。之前,需要注意一些问题。
首先,使用errn。的代码可能不是完全可移植的。例 例如,ISO C标准仅定义了少量错误代码, POSIX标准定义了很多错误代码。是规定了符号错误代码,它 们是一些以E为前缀的宏常量,在errno头文件中定义(或者在errno标题所包括的文件中)。
使用errn。最安全的方式如下:
(1)执行对库或系统函数的调用。
(2)使用函数的返回值判断是否发生了某个错误。
(3)如果发生了某个错误,使用errno确定为什么发生这个错误。
E有两个函数使得错误代砰 的解释更容易:perror()和strerror()。
perror()的输出是一个标准错误。
另一个有助于将errno代码翻译成描述性消息的函数是strerror().
7.4更好地使用strace和ltrace
了解库函数和系统调用之间的区别很重要。库strace实用程序输出程序进行的各个系统调用及其参数和返回值。
strace输出的每一行对应于一个系统调用。
将所有输出保存在 一个文件中比试图在屏幕上查看要方便得多。坐 可以使用重定向stder。的方式,但是也可以 B-o LogfiLe切换,使strace将其所有输出都写到一个日志文件中。另外 。为了强制strace将字符串截短为N个字符,可 使用一s N选项。最IfF :,如果在具有子进程分支的程序上运行strace,可以用一0 LOG -ff切换, 单个子进程的strace输出捕获到一个名为LOG二的文件中,其中二是子进程ID.
还有一个名为ltrace的实用工具,它类似于strace,但它显示了库调用而不是系统调用。
strace和ltrace对于跟踪程序错误和解决棘手且会引起很多麻烦的奇 怪行为非常有用。
7.5静态代码检查器:lint与其衍生
描代码的工具,不编译代码,仅仅警告错误、可能的错误和与严 格C语言编码标准的差距;这样的称为静态代码检查器。C语言的规范静态检查器由S. C. Johnson 乏编写,称为lint.splint的目标是帮助编写大部分有防御性、安全和尽可能少出错的程序。如果没有++weak开关,splint通常因为太挑剔而用处不大。I +weak,弱检查,通常用于无注解的C代码; O +standard,默认模式; 。+checks,中度严格检查; 1 +strict,高度严格检查。 splint是在GNU GPL下发布的,其主页为http://www.splint.org/
7.6.1检测DAM问题的的策
Electric Fence,这是对分配的内存地址实施“栅栏”功能的库。访问这些栅栏外的 内存通常会导致段错误和核心转储。
本节还会讨论两个GNU工具mtrace()和MALLOC一 CHEC戈,它 们向标准库分配函数中添加钩子,以保持关于当前分配内存的记录。
这样,库就可以对要读、写 或释放的内存执行检查。在使用几个软件工具时要小心,每个工具都使用钩子进行与堆相 关的函数调用,因为一个工具可以在已经安装的钩子上再安装一个钩子。
7.6.2 Electric Fence
当EFence链接到代码中时,导致程序在发生下列情况之一时立即发生段错误并转 储核心在DAM边界之外执行读或写操作。
对己经释放的DAM执行读或写操作。
对没有指向malloc()分配的DAM的指针执行free()(包括重复释放的特殊情况)。
7.6.3用GNU C库工具调试以M问题
GNU C库提供了一个名为MALLOC CHECK的环境变量,像EFence一样,可用来捕获DAM访问 违反,但是对它的使用不需要重新编译程序。
这些设置及其效果如下所示。
0--关闭所有DAM检查(如果没有定义变量,也是这种情况)。
1--当检侧到堆损坏时,显示关于stderr的诊断消息。
2--当检测到堆损坏时,立即异常中断程序并转储内存。
3--1和2的综合效果。
虽然MALLOC_CHECK比EFence用起来更方便,但是它有几个严重的缺陷。不知道有问题代码的源文件和行号,而且经常甚至不知 道是哪个指针引起了问题。
其次,。这暗示了如果访问错误发生后没有调用与堆相关的函数,那么MALLOC CHECK根本不 会报告错误。 再次,MALLOC CHECK_错误消息似乎含义不是那么明显。最后,对于setuidRsetgidN序是禁用MALLOC_ CHECK的,因为怀有恶意的人可以利用这种功能 组合进行安全攻击。
总而言之,MALLOC_CHECK是一种方便的工具,在代码开发期间用来捕获与堆相关的编程故障。 然而,如果怀疑有DAM问题,或者要仔细扫描代码查找可能存在的DAM问题,应当使用另一个实用工具.
2.使用mcheck()Z具
UW,型为: #include <mcheck.h>
int mcheck (void (*ABORTHANDLER) (enum mcheck status STATUS))
在调用任何与堆相关的函数前必须调用mcheck(),否则对mcheck()的调用会失败。
3.使用mtrace()捕获内存泄漏和重复释放
mtrace()工具是GNU C库的一部分,用来捕获C和C++程序中的内存泄漏和重复释放。
mtrace()的使用涉及5个步骤:
(1)将环境变量MALLOC TRAC“设置成有效的文件名。这; 这是mtrace()在其中放置消息的文件 名。
(2)包括mcheck.h头文件。
(3)在程序最上方调用mtrace()。
(4)运行程序。如果检测到任何问题,会用一种非人类可读的形式将它们记到MALLOC_ TRACE 所指向的文件中。另外,为了安全起见,mtrace()不会对setuid或setgid-If执行文件做任何事情。
(5)mtrace()配备了一个称为mtrace的Perl脚本,用来分析日志文件,并将其内容显示为人类 可读的标准输出形式。
第8章 对某他语言使用GDB/DDD/Eclipse
用GNU的人也提供了一款J瓢泼大雨编译器:GCJ
DDD经常可用来作为其他语言特有的调试器的前端。
8.1.1直接使用GDB调试Java
赶用GNU的GCJ编译器,可以将Java源代码编译为 本地机器代码。
为了在Eclipse中开发Perl代码,我们需要PadWalker Perl软件包(可以从CPAN下载),以及Perl 的EPIC Eclipse插件。
8.3.2在Eclipse中调试Python
需要安装Pydev插件。安装后,选择Window-Preferences-Pydev,并将Python释器的位 置(比如/lusrlbin/python)通知Eclipse.
!使用Pydev Package Explorer作为导航器透视图。
因为没有创建永久字节码文件,所以没有构建过程。
在建立运行/调试对话框时,注意如下内容:用希望从中开始执行的源文件名填充Main Module。
在Debug透视图中,变量的值只能通过Variables视图访问,而且仅限于访问局部变量。
Eclipse优于DDD的一个主要优点是,DDD使用的底层调试引擎PDB不适用于多线程程序, 而Eclipse适用于这种程序。
8.4调试SWIG代码
SWIG (Simplified Wrapper and Interface Generator)是一种流行的开源工具,用来将Java, Pert,Python和若干其他解释语言与C/C++接合。它允许使用解释语言编写应用程序的大部分代码,并与程序员用C/C++编写的特定部分结 合,从而增强性能。