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

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

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就会输出一条信息。在程序结束时,一个垃圾收集例程就会运行来检测是否在存在分配的内存块没有被释放。这些孤儿内存也会被报告。

(0)

相关推荐

  • gdb调试命令的使用及总结

    1.基本命令 1)进入GDB #gdb test test是要调试的程序,由gcc test.c -g -o test生成.进入后提示符变为(gdb) . 2)查看源码 (gdb) l 源码会进行行号提示. 如果需要查看在其他文件中定义的函数,在l后加上函数名即可定位到这个函数的定义及查看附近的其他源码.或者:使用断点或单步运行,到某个函数处使用s进入这个函数. 3)设置断点 (gdb) b 6 这样会在运行到源码第6行时停止,可以查看变量的值.堆栈情况等:这个行号是gdb的行号. 4)查看断点

  • 使用GDB调试多线程实例详解

    先写一段多线程程序. makefile 加上 -g参数生成可调式信息, 可以进行调试. pthread不是Linux下的默认的库,也就是在链接的时候,无法找到phread库中哥函数的入口地址,于是链接会失败.在gcc编译的时候,附加要加 -lpthread参数即可解决. gdb test 进入调试 需要调试的地方打下断点,run运行到断点处. r 运行到断点处,info thread可以查看被调试的线程. thread apply all bt 让所有线程打印堆栈信息 set scheduler

  • 基于Linux调试工具strace与gdb的常用命令总结

    strace和gdb是Linux环境下的两个常用调试工具,这里是个人在使用过程中对这两个工具常用参数的总结,留作日后查看使用.strace调试工具strace工具用于跟踪进程执行时的系统调用和所接收的信号,包括参数.返回值.执行时间.在Linux中,用户程序要访问系统设备,必须由用户态切换到内核态,这是通过系统调用发起并完成的.strace常用参数:-c 统计每种系统调用执行的时间.调用次数.出错次数,程序退出时给出报告-p pid 跟踪指定的进程,可以使用多个-p同时跟踪多个进程-o file

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

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

  • C/C++语言中的头文件汇总

    stdio.h就是指"standard input&output" 意思就是说标准输入输出头文件! 所以了,用到标准输入输出函数时,就要调用这个头文件 C/C++头文件 include <assert.h> //设定插入点 include <ctype.h> //字符处理 include <errno.h> //定义错误码 include <float.h> //浮点数处理 include <fstream.h> //

  • 浅谈c语言中转义字符的用法及注意事项

    c语言中的转义字符: \a 响铃符 \b 退格 \f 换页符 \n 换行符 \r 回车符(回到该行的首位置) \v 纵向制表符 \\ 反斜杠 \? 问号(?经vs10测试可以直接打印) \"(\') 双引号(单引号) \ooo 八进制数(ooo表示一个用8进制数表示出来的对应ANSII代码对应出字符,用此方法可以表示出所有ASCII字符.不过测试发现打不出%号,存疑!) \xhh 十六进制数(功能同八进制数,用hh表示一个十六进制数,如\x20表示空格) 注:使用转义字符的退格符,换行符,回车符

  • vue中watch的用法汇总

    在vue中,使用watch来响应数据的变化.watch的用法大致有三种. 1. 常用用法 <input type="text" v-model="name"/> new Vue({ el: '#app', data: { name: '咸鱼' }, watch: { name(newVal,oldVal) { // ... } } }) 直接写一个监听处理函数,当每次监听到 name 值发生改变时,执行函数.也可以在所监听的数据后面直接加字符串形式的方法

  • Vue3中插槽(slot)用法汇总(推荐)

    目录 什么是插槽 默认内容 具名插槽 动态插槽名 作用域插槽 作用域插槽 具名作用域插槽 写在最后 Vue中的插槽相信使用过Vue的小伙伴或多或少的都用过,但是你是否了解它全部用法呢?本篇文章就为大家带来Vue3中插槽的全部用法来帮助大家查漏补缺. 什么是插槽 简单来说就是子组件中的提供给父组件使用的一个 坑位 ,用 <slot></slot> 表示,父组件可以在这个坑位中填充任何模板代码然后子组件中 <slot></slot> 就会被替换成这些内容.比如一

  • 浅谈Go语言中的次方用法

    Go语言中符号 " ^ " 不再用于次方,而是表示"按位异或的运算" 具体的运算规则如下: 按位异或 ^ : 两位一个为 0, 一个为 1 ,结果为 1 ,否则为 0(位表示二进制的机器码) 例子如下: 所以Go语言中2^3 = 1 不是 8(注意:计算机都是按照补码进行运算) 那么Go语言中的次方是什么:(下图所示) (官方文档是个好帮手) 补充:leetcode golang实现一个数的整数次方 pow(x, n) 我就废话不多说了,大家还是直接看代码吧~ pa

  • js中typeof的用法汇总

    JavaScript中的typeof其实非常复杂,它可以用来做很多事情,但同时也有很多怪异的表现.本文列举出了它的多个用法,而且还指出了存在的问题以及解决办法. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FOperators%2Ftypeof > type

  • Android中Notification的用法汇总

    我们在用手机的时候,如果来了短信,而我们没有点击查看的话,是不是在手机的最上边的状态栏里有一个短信的小图标提示啊?你是不是也想实现这种功能呢?今天的Notification就是解决这个问题的. 我们也知道Android系统也是在不断升级的,有关Notification的用法也就有很多种,有的方法已经被android抛弃了,现在我实现了三种不同的方法,并适应不同的android版本.现在我就把代码公布出来,我喜欢把解释写在代码中,在这里我就不多说了,先看效果图: 再看代码,主要的代码如下: pac

  • C#语言中的修饰符汇总

    修饰符是用于限定类型以及类型成员的申明的一种符号. 下面主要从C#中的访问修饰符,作用于类和结构的修饰符,用在方法或变量的修饰符和特殊作用的修饰符四种,来给大家介绍. 1. 访问修饰符 指定声明的类型和类型成员的可访问性. (1) public:是类型和类型成员的访问修饰符.公共访问是允许的最高访问级别.对访问公共成员没有限制. (2) private:是一个成员访问修饰符.私有访问是允许的最低访问级别.私有成员只有在声明它们的类和结 构体中才是可访问的. (3) internal:是类型和类型

  • C语言中进制知识汇总

    1.什么是进制 进制是一种计数的方式,常用的有二进制.八进制.十进制.十六进制.任何数据在计算机内存中都是以二进制的形式存放的. 我对进制的个人理解,二进制数是以2为计算单元,满2进1位的数:八进制数是以8为计算单元,满8进1位的数. 对于任何一个数字,我们都可以用不同的进制来表示,比如,十进制数12,用二进制表示为1100,用八进制表示为14,用十六进制表示为0xC. 2.进制的转换规则 遵循满进制值进1位,个位数变为0的原理,下面我们以十进制数18为例,对1-18中每一个数值转换各种进制做一

随机推荐