这个笔记是我以前发表在学校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<stdio.h>
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 demo
demo.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 fault
luck@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$ ls
myls-0.0.c myls-1.0.c myls-2.0.c
luck@geekard:~/codes/12.21$ gcc -g -o myls myls-0.0.c
luck@geekard:~/codes/12.21$ ls
myls 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$ ls
core 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-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <

本文介绍了如何使用gdb调试程序,特别是针对段错误(Segmental fault)的排查。通过实例展示了从错误产生到使用gdb定位问题的过程,包括设置断点、查看堆栈信息和变量值,最终找到程序中导致段错误的数组越界问题。文章还提到了调试前的准备工作,如开启核心转储和编译时添加-g选项,以及如何通过自定义信号处理函数实现程序崩溃时自动启动gdb调试。
最低0.47元/天 解锁文章
918

被折叠的 条评论
为什么被折叠?



