这个笔记是我以前发表在电子科大的BBS上的,大家反应较好,特贴到这里和童鞋分享!笔记内容:1.背景介绍2.程序中常见的bug分类3.程序调试器(如gdb)有什么用4.段错误(Segmental fault)介绍5.gdb调试入门 一、背景介绍这个笔记主要介绍开源的程序调试器(gdb)的入门知识,目的是使unix/linux环境的编程新手能够快速学会使用gdb调试程序的方法,同时也是对我使用gdb的一个经验总结。本文假设你能使用简单的unix/linux命令并能用gcc(GNU C Compiler, GNU C 语言编译器)编译程序,当然有编程经验更好。:)为帮助你理解和操作,我将使用我遇到过的真实事例来演示使用gdb调试有缺陷(bug)的程序过程,你看过这篇笔记后能自己动手练一下最好。二、程序中常见的缺陷(bug)分类程序(编译型程序,perl、python,php等脚本程序除外)中常见的bug通常分为两类: 语法错误和逻辑错误,或者编译时错误和运行是错误。语法错误(编译时错误)是我们在编写源代码时没有按照相关的语言规范(如ANSI C标准)导致编译时出错,编译失败。这种错误的检查和调试一般是比较简单和直接的:因为编译器(如gcc)通常会明确告诉你错误的原因和大致的范围(注意不一定是准确的错误行)。例如下面的一个简单demo.c程序的第8行缺失了一个分号,gcc指示第10行前少了一个分号。这就是一个典型的语法错误。geekard@geekard:~/test$ cat -n demo.c 1 #include 2 3 int 4 main(){ 5 6 int n; 7 8 printf("the n is:%c", n) 9 10 return 0; 11 }geekard@geekard:~/test$ gcc demo.c -o demodemo.c: In function ‘main’:demo.c:10:error: expected ‘;’ before ‘return’添加了分号再编译一次,这下没有出现问题,运行程序的结果如下:geekard@geekard:~/test$ ./demo the n is:6680564另外注意这个程序中的变量n,我定义其为整型变量但并没有对其初始化赋值,这就是一个逻辑错误:编译器不会指示这个错误,只有在实际运行或测试时才能发现。这个小程序只是一个故意的编造,但在实际编程中无论你多高明,经验多丰富,难免会在此处犯些小错误(想想吧:当你需要编写或维护一个成千上万行的代码,这种小概率事件就是确定事件了,:)),而通常这些错误又是那么的浅显而易于消除,但是手工“除虫”(debug),往往是效率低下且让人厌烦的,本文将就" 段错误"这个内存访问越界的错误谈谈如何使用gdb快速定位这些"段错误"的语句。三、程序调试器(如gdb)有什么用?(参考自gdb的在线帮助手册, 可用命令:man gdb, 或 info gdb查看)程序调试器(如gdb)的主要目的是让你能够查看正在执行的程序其内部特性(如执行流程、变量值、函数调用、堆栈等),也可以程序崩溃时刻或以前都发生了什么。Gdb对程序的调试能力主要体现在以下四个方面(当然不止这些): . 启动你的程序,可以带任何影响其功能(或称行为)的参数。 . 能够使你的程序在指定条件下在指定的地方(断点)停止运行。 . 当你的程序在断点处停止时,你可以查看已执行的结果(如变量的值,函数之间的调用情况,执行到那一行代码,下一步该执行哪行代码) . 改变你的程序中,你可以实验这种改变所带来的影响(如bug消除了,或者情况变得更糟糕)使用gdb,你可以调试C,C++,以及Modula-2语言编写的程序。四、段错误(Segmental fault)介绍在用C/C++语言写程序的时侯,内存管理的绝大部分工作都是需要我们来做的。实际上,内存管理是一个比较繁琐的工作,所以像java和c#等语言采用了内存自动回收机制,避免了内存泄漏。如果程序试图往内存地址0处写东西时,内核就会向其发送段错误信号,如果程序没有捕获该信号,默认的操作时内核终止该程序的运行,例如我写的一个myls程序就遭遇了这种情况:luck@geekard:~/codes/12.21$ ./myls -ld .longlist 1, typelist 0, dirlist 1, filename .Segmentation faultluck@geekard:~/codes/12.21$ 常见的段错误原因如下:1)往受到系统保护的内存地址写数据有些内存是内核占用的或者是其他程序正在使用,为了保证系统正常工作,所以会受到系统的保护,而不能任意访问.2)内存越界(数组越界,变量类型不一致等)下面我以上面的myls程序出现的错误为例介绍用gdb进行调试的方法和过程。五、gdb调试入门 5.1 调试前的准备我们首先要启动linux内核提供核心转储(core dump)机制:当程序中出现内存操作错误时,会发生崩溃并产生核心文件(core文件)。使用GDB可以对产生的核心文件进行分析,找出程序是在什么时候崩溃的和在崩溃之前程序都做了些什么。首先,你的Segmentation Fault错误必须要能重现(废话…)。然后,依参照下面的步骤来操作:1)无论你是用Makefile来编译,还是直接在命令行手工输入命令来编译,都应该加上 -g 选项。如:luck@geekard:~/codes/12.21$ lsmyls-0.0.c myls-1.0.c myls-2.0.cluck@geekard:~/codes/12.21$ gcc -g -o myls myls-0.0.c luck@geekard:~/codes/12.21$ lsmyls myls-0.0.c myls-1.0.c myls-2.0.c加了-g选项后,gcc就会在生成的可执行文件(这里-o myls表示输出(output)的可执行文件名时myls)里添加一些调试符号(debugging symbols),有了这些调试符号后就可以在稍后用gdb调试时列出执行的程序的C源代码了。-g选项增大了文件体积,一般只是在刚开发出的程序调试时使用,当确定无误编译出实际使用的可执行文件时就不需要-g选项了。2)一般来说,在默认情况下,在程序崩溃时,core文件是不生成的(很多Linux发行版在默认时禁止生成核心文件)。所以,你必须修改这个默认选项,在命令行执行:ulimit -c unlimited //unlimited 表示不限制生成的core文件的大小。3)运行你的程序,不管用什么方法,使之重现Segmentation Fault错误。luck@geekard:~/codes/12.21$ ./myls -ld .longlist 1, typelist 0, dirlist 1, filename .Segmentation fault (core dumped)4)这时,你会发现在你程序同一目录下,生成了一个文件名为 core的文件,即核心文件。luck@geekard:~/codes/12.21$ lscore myls myls-0.0.c myls-1.0.c myls-2.0.cluck@geekard:~/codes/12.21$ 5)用GDB调试它,在命令行执行:luck@geekard:~/codes/12.21$ gdb ./myls 或者先启动gdb然后在gdb命令提示符中输入这两个文件:luck@geekard:~/codes/12.21$ gdb //不带参数启动gdb调试程序GNU gdb (GDB) 7.2-ubuntuCopyright (C) 2010 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type "show copying"and "show warranty" for details.This GDB was configured as "i686-linux-gnu".For bug reporting instructions, please see:.(gdb) file ./myls //输入file命令和你的可执行文件名和路径,这里为当前目录下的myls文件Reading symbols from /home/luck/codes/12.21/myls...done.(gdb) run -ld ./ //带参数(这里为 -ld ./)运行r(run)程序,这和在bash命令行上执行:./myls -ld ./效果时一致的。Starting program: /home/luck/codes/12.21/myls -ld ./longlist 1, typelist 0, dirlist 1, filename ./ //myls程序的输出Program received signal SIGSEGV, Segmentation fault. //出错后退出0x0016e78f in vfprintf () from /lib/libc.so.6(gdb)从这里我们还发现进程是由于收到了SIGSEGV信号而结束的。通过进一步的查阅文档(man 7 signal),我们知道SIGSEGV默认handler的动作是打印”段错误"的出错信息,并产生Core文件。查看一下我的当前目录,果然有core文件。luck@geekard:~/codes/12.21$ lscore myls myls-0.0.c myls-1.0.c myls-2.0.c下面我们就用刚才生成的分段错误产生的核心转储文件(core)再次调试程序。接着上一步的(gdb) 提示符,输入以下命令:(gdb) core core //输入core命令和分段错误产生的核心转储文件,这里为当前目录下的core文件A program is being debugged already. Kill it? (y or n) y //按y,重新调试[New Thread 24884]warning: Can't read pathname for load map: Input/output error.Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.Loaded symbols for /lib/libc.so.6Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.Loaded symbols for /lib/ld-linux.so.2Core was generated by `./myls -ld .'. //core文件记录了发生错误的程序执行的命令行参数Program terminated with signal 11, Segmentation fault.#0 0x002bb78f in vfprintf () from /lib/libc.so.6 //core文件记录了发生错误时程序的退出状态(gdb)从标号为0的行我们并不能看出程序到底在哪出错,所以下一步需要确定发生错误前程序中函数之间的调用关系(gdb) backtrace //显示程序的堆栈信息#0 0x0014f78f in vfprintf () from /lib/libc.so.6#1 0x0016f4dc in vsprintf () from /lib/libc.so.6#2 0x00157b4b in sprintf () from /lib/libc.so.6#3 0x08048c56 in finalprt (file=0x8a9b02b "..", dirlist=1, typelist=0, longlist=1) at myls-0.0.c:261#4 0x080487c3 in detailList (file=0xbfab684d ".", dirlist=1, typelist=0, longlist=1) at myls-0.0.c:132#5 0x08048712 in main (argc=3, argv=0xbfab4804) at myls-0.0.c:89(gdb)可以看出myls程序的函数调用关系为:main() ---> detailList() ---> finalprt然后在标号为0-2的行进入了系统的C库函数,所以产生错误的可能在标号3、4、5指明的函数中。我们先看一下最后调用finalprt()函数时可能发生错误的代码行:(gdb) frame 3 //上面以#开头的行称为帧(frame),这里指定查看第3帧#3 0x08048c56 in finalprt (file=0x8a9b02b "..", dirlist=1, typelist=0, longlist=1) at myls-0.0.c:261261 sprintf(str, "%c%d %d,%d %d %d %s", filetype, permission, uid, gid, size, mdate, file);可以看到在调用sprintf()函数时可能发生了分段错误(由非法引用内存引起),而sprintf()的原型为: int sprintf(char *str, const char *format, ...);最有可能引起错误的地方是其第一个参数:char *str,一个指向字符串数组的指针,我们先把疑点放在这,接下来看一下函数之间相互调用时传递的参数值和函数的内部变量值:(gdb) backtrace full //full参数表示完全显示函数之间相互调用时传递的参数值和函数的内部变量值#0 0x0014f78f in vfprintf () from /lib/libc.so.6No symbol table info available.#1 0x0016f4dc in vsprintf () from /lib/libc.so.6No symbol table info available.#2 0x00157b4b in sprintf () from /lib/libc.so.6No symbol table info available.#3 0x08048c56 in finalprt (file=0x8a9b02b "..", dirlist=1, typelist=0, longlist=1) at myls-0.0.c:261 str = 0x4d11faec 这个笔记是我以前发表在电子科大的BBS上的,大家反应较好,特贴到这里和童鞋分享!笔记内容:1.