基于c语言中调试工具的用法汇总(不包含gdb)_C 语言

是不是只有编译的时候才知道程序写了错误?有没有在未编译的时候就让机器帮你检查错误的工具呢?
答案是:有!!

splint工具.用一个最简单的HELLO WORLD来表述:
=====================================

复制代码 代码如下:

/*错误很明显*/
#include <stdio.h>

int main(void)
{
   print("hello world\n", s);
   return
}

-----------------------------------------------------
casio$ splint -strict foo.c
Splint 3.1.1 --- 03 Nov 2006

foo.c: (in function main)
foo.c:5:2: Unrecognized identifier: print <-------找到print不是printf
Identifier used in code has not been declared. (Use -unrecog to inhibit
warning)
foo.c:5:25: Unrecognized identifier: s <-------未定义变量s
foo.c:5:2: Statement has no effect (possible undected modification through call
to unconstrained function print): print("hello wor... <---------不存在prinf函数
Statement has no visible effect --- no values are modified. It may modify
something through a call to an unconstrained function. (Use -noeffectuncon to
inhibit warning)
foo.c:7:2: Parse Error. (For help on parse errors, see splint -help <------对应return语法错误
parseerrors.)
*** Cannot continue.

=============================================

cxref

cxref程序分析C源代码并且生成一个交叉引用。他显示了每一个符号在程序中何处被提到。他使用标记星号的每一个符号定义位置生成一个排序列表,如下所示:

SYMBOL FILE FUNCTION LINE
BASENID prog.c — *12 *96 124 126 146 156 166
BINSIZE prog.c — *30 197 198 199 206
BUFMAX prog.c — *44 45 90
BUFSIZ /usr/include/stdio.h — *4
EOF /usr/include/stdio.h — *27
argc prog.c — 36
prog.c main *37 61 81
argv prog.c — 36
prog.c main *38 61
calldata prog.c — *5
prog.c main 64 188
calls prog.c — *19
prog.c main 54

在作者的机子上,前面的输入在程序的源码目录中使用下面的命令来生成的:

$ cxref *.c *.h

但是实际的语法因为版本的不同而不同。查看我们系统的文档或是man手册可以得到更多的信息。

cflow <使用时输入cflow *.c就可以了.可以马上搞清除什么函数调用了什么.>

cflow程序会输出一个函数调用树,这是一个显示函数调用关系的图表。这对于查看程序结构来了解他是如何操作的以及了解对于一个函数有哪些影响是十分有用的。一些版本的cflow可以同时作用于目标文件与源代码。查看手册页我们可以了解更为详细的操作。

下面是由一个cflow版本(cflow-2.0)所获得的例子输出,这个版本的cflow版本是由Marty Leisner维护的,并且可以网上得到。

1 file_ungetc {prcc.c 997}
2 main {prcc.c 70}
3 getopt {}
4 show_all_lists {prcc.c 1070}
5 display_list {prcc.c 1056}
6 printf {}
7 exit {}
8 exit {}
9 usage {prcc.c 59}
10 fprintf {}
11 exit {}

从这个输出中我们可以看到main函数调用show_all_lists,而show_all_lists调用display_list,display_list本身调用printf。

这个版本cflow的一个选项就是-i,这会生成一个反转的流程图。对于每一个函数,cflow列出调用他的其他函数。这听起来有些复杂,但是实际上并不是这样。下面是一个例子。

19 display_list {prcc.c 1056}
20 show_all_lists {prcc.c 1070}
21 exit {}
22 main {prcc.c 70}
23 show_all_lists {prcc.c 1070}
24 usage {prcc.c 59}
...
74 printf {}
75 display_list {prcc.c 1056}
76 maketag {prcc.c 487}
77 show_all_lists {prcc.c 1070}
78 main {prcc.c 70}
...
99 usage {prcc.c 59}
100 main {prcc.c 70}

例如,这告诉我们调用exit的函数有main,show_all_lists与usage。

使用prof/gprof执行性能测试

当我们试着追踪一个程序的性能问题时一个十分有用的技术就是执行性能测试(execution profiling)。通常被特殊的编译器选项以及辅助程序所支持,一个程序的性能显示他在哪里花费时间。

prof程序(以及其GNU版本gprof)会由性能测试程序运行时所生成的执行追踪文件中输出报告。一个可执行的性能测试是由指定-p选项(对prof)或是-pg选项(对gprof)所生成的:

$ cc -pg -o program program.c

这个程序是使用一个特殊版本的C库进行链接的并且被修改来包含监视代码。对于不同的系统结果也许不同,但是通常是由安排频繁被中断的程序以及记录执行位置来做到的。监视数据被写入当前目录中的一个文件,mon.out(对于gprof为gmon.out)。

$ ./program
$ ls -ls
2 -rw-r--r-- 1 neil users 1294 Feb 4 11:48 gmon.out

然后用命令:gprof ./program可以查看到下面的报告

prof/gprof程序读取这些监视数据并且生成一个报告。查看其手册页可以详细了解其程序选项。下面以gprof输出作为一个例子:

cumulative self self total
time seconds seconds calls ms/call ms/call name
18.5 0.10 0.10 8664 0.01 0.03 _doscan [4]
18.5 0.20 0.10 mcount (60)
14.8 0.28 0.08 43320 0.00 0.00 _number [5]
9.3 0.33 0.05 8664 0.01 0.01 _format_arg [6]
7.4 0.37 0.04 112632 0.00 0.00 _ungetc [8]
7.4 0.41 0.04 8757 0.00 0.00 _memccpy [9]
7.4 0.45 0.04 1 40.00 390.02 _main [2]
3.7 0.47 0.02 53 0.38 0.38 _read [12]
3.7 0.49 0.02 w4str [10]
1.9 0.50 0.01 26034 0.00 0.00 _strlen [16]
1.9 0.51 0.01 8664 0.00 0.00 strncmp [17]

内存调试

富含bug而且难于跟踪调试的一个区域就是动态内存分配。如果我们编译一个使用malloc与free来分配内存的程序,很重要的一点就是我们要跟踪我们所分配的内存块,并且保证不要使用已经释放的内存块。

通常,内存是由malloc分配并且赋给一个指针变量的。如果指针变量被修改了,而又没有其他的指针来指向这个内存块,他就会变为不可访问的内存块。这就是一个内存泄露,而且会使得我们程序尺寸变大。如果我们泄露了大量的内存,那么我们的系统就会变慢并且会最终用尽内存。

如 果我们在超出一个分配的内存块的结束部分(或是在一个内存块的开始部分)写入数据,我们很有可能会破坏malloc库来跟踪分配所用的数据结构。在这种情 况下,在将来的某个时刻,调用malloc,或者甚至是free,就会引起段错误,而我们的程序就会崩溃。跟踪错误发生的精确点是非常困难的,因为很可能 他在引起崩溃的事件发生以前很一段时间就已经发生了。

不必奇怪的是,有一些工具,商业或是自由的,可以有助于处理这两种问题类型。例如,有许多不同的malloc与free版本,其中的一些包含额外的代码在分配与回收上进行检测尝试检测一个内存块被释放两次或是其他一些滥用类型的情况。

ElectricFence

ElectricFence 库是由Bruce Perens开发的,并且在一些Linux发行版本中作为一个可选的组件来提供,例如RedHat,而且已经可以在网络上获得。他尝试使用Linux的虚 拟内存工具来保护malloc与free所使用的内存,从而在内存被破坏时终止程序。

试验--ElectricFence

下面的程序,efence.c,使用malloc分配一个内存块,然后在超出块结束处写入数据。让我们看一下会发生什么情况。

复制代码 代码如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{
   char *ptr = (char *) malloc(1024);
   ptr[0] = 0;
   /* Now write beyond the block */
   ptr[1024] = 0;/*写非法*/
   exit(0);
}

当我们编译运行这个程序时,我们并不会看到进一步的行为。然而,似乎malloc所分配的内存区域有一些问题,而我们实际上已经遇到了麻烦。

$ cc -o efence efence.c
$ ./efence
$

然而,如果我们使用ElectricFence库,libefence.a来链接这个程序,我们就会得到一个即时的响应。

$ cc -o efence efence.c -lefence
$ ./efence
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
Segmentation fault
$

在调试器下运行可以定位这个问题:

$ cc -g -o efence efence.c -lefence
$ gdb efence
(gdb) run
Starting program: /home/neil/BLP3/chapter10/efence
[New Thread 1024 (LWP 1869)]
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1024 (LWP 1869)]
0x080484ad in main () at efence.c:10
10 ptr[1024] = 0;
(gdb)

工作原理

Electric替换malloc并且将函数与计算机处理器的虚拟内存特性相关联来阻止非法的内存访问。当这样的访问发生时,就会抛出一个段错误信息从而可以终止程序。

valgrind

valgrind是一个可以检测我们已经讨论过的许多问题的工具。事实上,他可以检测数据访问错误与内存泄露。也许他并没有被包含在我们的Linux发行版本中,但是我们可以在http://developer.kde.org/~sewardj处得到。

程序并不需要使用valgrind重新编译,而我们甚至可以调用一个正在运行的程序的内存访问。他很值得一看,他已经用在主要的开发上,包含KDE版本3。

试验--valgrind

下面的程序,checker.c,分配一些内存,读取超过那块内存限制的位置,在其结束处之外写入数据,然后使其不能访问。

复制代码 代码如下:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
   char *ptr = (char *) malloc(1024);
   char ch;
   /* Uninitialized read */
   ch = ptr[1024];/*读非法*/
   /* Write beyond the block */
   ptr[1024] = 0;/*写非法*/
   /* Orphan the block */
   ptr = 0;/*野指针*/
   exit(0);
}

要使用valgrind,我们只需要简单的运行valgrind命令,传递我们希望检测的选项,其后是使用其参数运行的程序。

当我们使用valgrind来运行我们的程序时,我们可以看到诊断出许多问题:

$ valgrind --leak-check=yes -v ./checker
==3436== valgrind-1.0.4, a memory error detector for x86 GNU/Linux.
==3436== Copyright (C) 2000-2002, and GNU GPL'd, by Julian Seward.
==3436== Estimated CPU clock rate is 452 MHz
==3436== For more details, rerun with: -v
==3436==
==3436== Invalid read of size 1
==3436== at 0x8048397: main (checker.c:10)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436== Address 0x42AD1424 is 0 bytes after a block of size 1024 alloc'd
==3436== at 0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436== by 0x8048389: main (checker.c:6)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436==
==3436== Invalid write of size 1
==3436== at 0x80483A4: main (checker.c:13)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436== Address 0x42AD1424 is 0 bytes after a block of size 1024 alloc'd
==3436== at 0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436== by 0x8048389: main (checker.c:6)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436==
==3436== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==3436== malloc/free: in use at exit: 1024 bytes in 1 blocks.
==3436== malloc/free: 1 allocs, 0 frees, 1024 bytes allocated.
==3436== For counts of detected errors, rerun with: -v
==3436== searching for pointers to 1 not-freed blocks.
==3436== checked 3468724 bytes.
==3436==
==3436== definitely lost: 1024 bytes in 1 blocks.
==3436== possibly lost: 0 bytes in 0 blocks.
==3436== still reachable: 0 bytes in 0 blocks.
==3436==
==3436== 1024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3436== at 0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436== by 0x8048389: main (checker.c:6)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436==
==3436== LEAK SUMMARY:
==3436== definitely lost: 1024 bytes in 1 blocks.
==3436== possibly lost: 0 bytes in 0 blocks.
==3436== still reachable: 0 bytes in 0 blocks.
==3436== Reachable blocks (those to which a pointer was found) are not shown.
==3436== To see them, rerun with: --show-reachable=yes
==3436== $

这里我们可以看到错误的读取与写入已经被捕获,而所关注的内存块与他们被分配的位置相关联。我们可以使用调试器在出错点断开程序。

valgrind 有许多选项,包含特定的错误类型表达式与内存泄露检测。要检测我们的例子泄露,我们必须使用一个传递给valgrind的选项。当程序结束时要检测内存泄 露,我们需要指定 --leak-check=yes。我们可以使用valgrind --help得到一个选项列表。

工作原理

我们的程序在valgrind的控制下执行,这会检测我们程序所执行的各种动作,并且执行许多检测,包括内存访问。如果程序访问一个已分配的内存块并且访问 是非法的,valgrind就会输出一条信息。在程序结束时,一个垃圾收集例程就会运行来检测是否在存在分配的内存块没有被释放。这些孤儿内存也会被报告。

时间: 2024-07-29 22:43:22

基于c语言中调试工具的用法汇总(不包含gdb)_C 语言的相关文章

基于c语言中调试工具的用法汇总(不包含gdb)【转】

转自:http://www.jb51.net/article/36829.htm 是不是只有编译的时候才知道程序写了错误?有没有在未编译的时候就让机器帮你检查错误的工具呢? 答案是:有!! splint工具.用一个最简单的HELLO WORLD来表述: ===================================== 复制代码 代码如下: /*错误很明显*/ #include <stdio.h> int main(void) { print("hello world\n&q

C语言中的结构体的入门学习教程_C 语言

C语言中数组允许定义类型的变量,可容纳相同类型的多个数据项,但结构体在C语言编程中,它允许定义不同种类的数据项可供其他用户定义的数据类型. 结构是用来代表一个记录,假设要跟踪图书馆的书籍.可能要跟踪有关每本书以下属性: Title - 标题 Author - 作者 Subject - 科目 Book ID - 编号 定义结构体定义一个结构体,必须使用结构体的struct语句.该struct语句定义了一个新的数据类型,程序不止一个成员.struct语句的格式是这样的: struct [struct

对C语言中指针的理解与其基础使用实例_C 语言

C语言的指针,关键意思在于"指". "指"是什么意思? 其实完全可以理解为指示的意思.比如,有一个物体,我们称之为A.正是这个物体,有了这么个称谓,我们才能够进行脱离这个物体的实体而进行一系列的交流.将一个物体的指示,是对这个物体的抽象.有了这种抽象能力,才有所谓的智慧和文明.所以这就是"指示"这种抽象方法的威力. 退化到C语言的指针,指针是一段数据/指令(在冯诺易曼体系中,二者是相通,在同一空间中的)的指示.这是指示,也就是这段数据/指令的起始

详解C语言中结构体的自引用和相互引用_C 语言

结构体的自引用(self reference),就是在结构体内部,包含指向自身类型结构体的指针. 结构体的相互引用(mutual reference),就是说在多个结构体中,都包含指向其他结构体的指针.1. 自引用 结构体 1.1 不使用typedef时错误的方式: struct tag_1{ struct tag_1 A; /* 结构体 */ int value; };         这种声明是错误的,因为这种声明实际上是一个无限循环,成员b是一个结构体,b的内部还会有成员是结构体,依次下去

C语言中全局数组和局部数组的问题_C 语言

今天同学遇到一个在C语言中全局数组和局部数组的问题,卡了许久,我也没有第一时间看出问题,现在把问题梳理一下,并给出解决方案. 问题描述: 在全局声明的数组与在局部声明的数组有着不同的效果. 首先来看一个程序: 复制代码 代码如下: #include <stdio.h> #include <stdlib.h> #define MAX 10 char a[MAX]; int main() { int i; char b[MAX]; char *c=(char *)malloc(MAX

新手小心:c语言中强符号与弱符号的使用_C 语言

声明:下面的实例全部在linux下尝试,window下未尝试.有兴趣者可以试一下.文章针c初学者.c语言的强符号和弱符号是c初学者经常容易犯错的地方.而且很多时候,特别是多人配合开发的程序,它引起的问题往往非常行为怪异而且难以定位.什么是强符号和弱符号?在c语言中,函数和初始化的全局变量是强符号,未初始化的全局变量时弱符号.强符号和弱符号的定义是连接器用来处理多重定义符号的,它的规则是:不允许多个强符号:如果一个强符号和一个弱符号,这选择强符号:如果多个弱符号,则任意选一个.它的陷阱:上代码:

C语言中操作密码文件的一些函数总结_C 语言

C语言setpwent()函数:从头读取密码文件中的账号数据 头文件: #include <pwd.h> #include <sys/types.h> 定义函数: void setpwent(void); 函数说明:setpwent()用来将getpwent()的读写地址指回密码文件开头. 范例 #include <pwd.h> #include <sys/types.h> main() { struct passwd *user; int i; for(i

详解C语言中的符号常量、变量与算术表达式_C 语言

C语言中的符号常量在结束讨论温度转换程序前,我们再来看一下符号常量.在程序中使用 300.20 等类似的"幻数"并不是一个好习惯,它们几乎无法向以后阅读该程序的人提供什么信息,而且使程序的修改变得更加困难.处理这种幻数的一种方法是赋予它们有意义的名字.#define 指令可以把符号名(或称为符号常量)定义为一个特定的字符串: #define 名字 替换文本 在该定义之后,程序中出现的所有在 #define 中定义的名字(既没有用引号引起来,也不是其它名字的一部分)都将用相应的替换文本替

基于C++ map中key使用指针问题的详解_C 语言

C++实际开发的过程会经常使用到map.map是一个key-value值对,key唯一,可以用find进行快速的查找.其时间复杂度为O(logN),如果采用for循环进行遍历数据时间复杂度为O(N).如果map中的数据量比较少时,采用find和for循环遍历的效率基本没有太大的区别,但是在实际的开发过程中,存储在map中的数据往往是大量的,这个时候map采用find方式效率比遍历效率高的多. 确定采用find方式查找数据后,我们需要考虑存储map的空间复杂度,对于基础数据类型的数据(int ch