http://blog.csdn.net/li4850729/article/details/8138555
初识GDB
GDB的出现减轻了开 发人员的负担,他们可以在程序运行的时候单步跟踪自己的代码,或者通过断点暂时中止程序的执行。此外,他们还能够随时察看变量和内存的当前状态,并监视关 键的数据结构是如何影响代码运行的。
调试方法
如果想对程序进行调试,必须先在用GCC编译源代码时加上-g选项,以便产生GDB所需要的调试符号信息。例如,debugme.c是一个存在错误程序,可以使用如下的命令对其进行编译,同时产生调试符号:
# gcc -g debugme.c -o debugme
如 果愿意的话,还可以在编译时使用“-ggdb”选
项来生成更多的调试信息。由于这些调试信息中的相当一部分是GDB所特有的,所以生成的代码将无法在其它
调试器中正常调试。对于大多数情况来说,普通的-g选项就足够了。需要注意的是,GCC虽然允许同时使用-g(调试)和-o(优化)选项,但优化会影响最
终生成的代码,导致程序源代码和二进制代码之间的关系变得复杂起来。如果不想为调试制造障碍,建议不要将-g和-o选项一同使用,并且只在程序彻底调试完
后才开始进行代码优化。这样调试过程将变得相对轻松和愉快。
基本应用
现在可以启动GDB来调试已经生成的可执行程序debugme,命令如下:
# gdb debugme GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb)
|
如果一切正常,GDB将被启动并在屏幕上输出版权信息,但如果使用了-q或--quiet选项则不会显示它们。启动GDB时另外一个有用的命令行选项是“-d dirname”,其中dirname是一个目录名。该目录名告诉GDB应该到哪里去寻找源代码。
一 旦出现GDB的命令提示符(gdb),就表明GDB已经准备好接收来自用户的各种调试命令了。如果想在调试环境下运行这个程序,可以使用GDB提供的 “run”命令,而程序在正常运行时所需的各种参数可以作为“run”命令的参数传入,或者使用单独的“set
args”命令进行设置。如果在执行“run”命令时没有给出任何参数,GDB将使用上一次“run”或“set
args”命令指定的参数。如果想取消上次设置的参数,可以执行不带任何参数的“set
args”命令。下面尝试在调试器中运行这个程序:
(gdb) run …… Program received signal SIGSEGV, Segmentation fault. 0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2
|
最后一行输出表明程序在调用动态链接库/lib/ld-linux.so.2中的_dl_fini() 函数时出现了错误,地址是0x4000c6ac。这些对调试是非常重要的线索。另外还有一种信息对调试也很重要,就是错误发生时的函数调用层级关系,可以
通过执行“backtrace”命令来获得。在使用GDB调试命令时,用户可以不必输入完整的命令名称,使用任何惟一的缩写都可以。例如 “backtrace”命令就可以缩写成“back”甚至“bt”。GDB还支持很多常用的Shell命令编辑特征,比如可以像在bash或tcsh中那
样按Tab键补齐命令。如果相关命令不惟一的话,则列出所有可能的匹配项。此外键盘上的方向键可用来翻动历史命令。
GDB是一个源代码级的调试器,使用“list”命令可以查看当前调试对象的源代码。该命令的通用格式为“list
[m,n]”,表示显示从m行开始到n行结束的代码段,而不带任何参数的“list”命令将显示最近10行源代码。
设置断点
在 调试有问题的代码时,在某一点停止运行往往很管用。这样程序运行到此外时会暂时挂起,等待用户的进一步输入。GDB允许在几种不同的代码结构上设置断点, 包括行号和函数名等,并且还允许设置条件断点,让程序只有在满足一定的条件时才停止执行。要根据行号设置断点,可以使用“ break linenum”命令。要根据函数名设置断点,则应该使用“break
funcname”命令。
在以上两种情况中,GDB将 在执行指定的行号或进入指定的函数之前停止执行程序。此时可以使用“print”显示变量的值,或者使用“list”查看将要执行的代码。对于由多个源文
件组成的项目,如果想在执行到非当前源文件的某行或某个函数时停止执行,可以使用如下形式的命令:
# break 20041126110727.htm:linenum # break 20041126110727.htm:funcname
|
条件断点允许当一定条件满足时暂时停止程序的执行。它对于调试来讲非常有用。设置条件断点的正确语法如下:
break linenum if expr ;break funcname if expr
|
其中expr是一个逻辑表达式。当该表达式的值为真时,程序将在该断点处暂时挂起。例如,下面的命令将在debugme程序的第38行设置一个条件断点。当程序运行到该行时,如果count的值等于3,就将暂时停止执行:
(gdb) break 38 if count==3
设置断点是调试程序时最常用到的一种手段。它可以中断程序的运行,给程序员一个单步跟踪的机会。使用命令“ break main”在main函数上设置断点可以在程序启动时就开始进行跟踪。
接
下去使用“continue”命令继续执行程序,直到遇到下一个断点。如果在调试时设置了很多断点,可以随时使用“info
breakpoints”命令来查看设置的断点。此外,开发人员还可以使用“delete”命令删除断点,或者使用“disable”命令来使设置的断点
暂时无效。被设置为无效的断点在需要的时候可以用“enable”命令使其重新生效。
观察变量
GDB
最有用的特性之一是能够显示被调试程序中几乎任何表达式、变量或数组的类型和值,并且能够用编写程序所用的语言打印出任何合法表达式的值。查看数据最简单
的办法是使用“print”命令,只需在“print”命令后面加上变量表达式,就可以打印出此变量表达式的当前值,示例如下:
(gdb) print str $1 = 0x40015360 "Happy new year!/n"
|
从输出信息中可以看出,输入字符串被正确地存储在了字符指针str所指向的内存缓冲区中。除了给出变量 表达式的值外,“print”命令的输出信息中还包含变量标号($1)和对应的内存地址(0x40015360)。变量标号保存着被检查数值的历史记录,
如果此后还想访问这些值,就可以直接使用别名而不用重新输入变量表达式。
如果想知道变量的类型,可以使用“whatis”命令,示例如下:
(gdb) whatis str type = char *
|
对于第一次调试别人的代码,或者面对的是一个异常复杂的系统时,“whatis”命令的作用不容忽视。
单步执行
为了单步跟踪代码,可以使用单步跟踪命令“step”,它每次执行源代码中的一行。
在GDB中可以使用许多方法来简化操作,除了可以将“step”命令简化为“s”之外,还可以直接输入回车键来重复执行前面一条命令。
除了可以用“step”命令来单步运行程序之外,GDB还提供了另外一条单步调试命令“next”。两者功能非常相似,差别在于如果将要被执行的代码行中包含函数调用,使用step命令将跟踪进入函数体内,而使用next命令则不进入函数体内。
在进入下一部分之前,使用下面的命令退出GDB:
(gdb) quit
分析核心(core)文件
在
程序发生崩溃时,有时可能无法直接运行GDB来进行调试。比如程序可能是在另外一台机器上运行的,或者因为程序对时间比较敏感,所以手动跟踪调试会产生无
法接受的延迟等。遇到这些情况,就只能等到程序运行结束后才能判断崩溃的原因了。这时需要用到Linux提供的core
dump机制。当程序中出现内存操作错误时,会发生崩溃并产生核心文件。使用GDB可以对产生的核心文件进行分析,找出程序是在什么时候崩溃的和在崩溃之
前程序都做了些什么。当然,如果要用GDB来分析核心文件,也必须在编译时加上-g选项来产生调试符号表。
在分析核心文件之前必须确认系统是否允许生成核心文件,很多Linux发行版在默认时禁止生成核心文件。为了生成核心文件,首先必须执行下面的命令:
# ulimit -c unlimited
然后就可以生成核心文件了。这里仍以前面的debugme程序为例,再次执行下面命令将产生核心文件:
# ./debugme Enter a string to count words:Happy new year! The number of words is 3. Segmentation fault (core dumped)
|
生成的核心文件名根据系统配置的不同会有所差异。要在GDB中分析核心文件,除了要给出核心文件的文件名外,还必须给出生成该核心文件的可执行程序的名称,示例如下:
#gdb debugme core.547 …… Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. ……
|
从GDB的输出信息中可以看出,产生这个核心文件的原因是因为程序收到了序号为11的信号。如果想知道程序在崩溃之前运行到了哪里,可以使用“backtrace”或“info stack”命令查看一下堆栈的历史记录。示例如下:
(gdb)
info stack #0 0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2 #1
0x40057940 in exit () from /lib/libc.so.6 #2 0x4004291f in
_libc_start_main () from /lib/libc.so.6
|
由上可知,程序崩溃时正处于_dl_fini()函数之中。但很多时候程序员感兴趣的可能并不是这个,
而是exit()或_libc_start_main()函数,因为它们才可能是问题真正的症结所在。GDB提供的“frame”命令可以用来在不同的调
用上下文中切换。例如下面的命令可以查看exit()函数在执行时的状况:
(gdb) frame 1 #1 0x40057940 in exit () from /lib/libc.so.6
|
此外还可以用“up”或“down”命令在不同的函数调用上下文中切换。开发人员使用这三条命令可以很轻松地实现调用栈的遍历。在分析核心文件时,通过将遍历栈的命令和检查变量值的“print”命令结合起来,就能够复原程序运行时的全部景象。
调试其它进程
有
时会遇到一种很特殊的调试需求,对当前正在运行的其它进程进行调试。这种情况有可能发生在那些无法直接在调试器中运行的进程身上,例如有的进程只能在系统
启动时运行。另外如果需要对进程产生的子进程进行调试的话,也只能采用这种方式。GDB可以对正在执行的程序进行调度,它允许开发人员中断程序并查看其状
态,之后还能让这个程序正常地继续执行。
GDB提供了两种方式来调试正在运行的进程:一种是在GDB命令行上指定进程的PID,另一种是在GDB中使用“attach”命令。例如,开发人员可以先启动debugme程序,让其开始等待用户的输入。示例如下:
#./debugme Enter a string to count words:
|
接下去在另一个虚拟控制台中用下面的命令查出该进程对应的进程号:
# ps -ax | grep debugme 555 pts/1 S 0:00 ./debugme
|
得到进程的PID后,就可以使用GDB对其进行调试了:
#
gdb debugme 555 GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Attaching to program: /home/xiaowp/debugme, process 555 Reading symbols
from /lib/libc.so.6...done. ……
|
在上面的输出信息中,以Attaching to program开始的行表明GDB已经成功地附加在PID为555的进程上了。另外一种连接到其它进程的方法是先用file命令加载调试时所需的符号表,然后再通过“attaché”命令进行连接:
(gdb) file /home/xiaowp/debugme Reading symbols from /home/xiaowp/debugme...done. (gdb) attach 555 ……
|
如果想知道程序现在运行到了哪里,同样可以使用“backtrace”命令。当然也可以使用“step”命令对程序进行单步调试。
在完成调试之后,不要忘记用detach命令断开连接,让被调试的进程可以继续正常运行:
GDB是Linux下一个最基本的调试器,其功能非常丰富。完整地介绍GDB的功能可能需要几百页,本文只涵盖了GDB的一些最常见的用法。作为一个合格的Linux程序员,花在GDB上的功夫和时间越多,从调试中获得的益处就越多。
命 令 描 述
file 装入想要调试的可执行文件.
kill 终止正在调试的程序.
list 列出产生执行文件的源代码的一部分.
next 执行一行源代码但不进入函数内部.
step 执行一行源代码而且进入函数内部.
run 执行当前被调试的程序
quit 终止 gdb
watch 使你能监视一个变量的值而不管它何时被改变.
break 在代码里设置断点, 这将使程序执行到这里时被挂起.
make 使你能不退出 gdb 就可以重新产生可执行文件.
shell 使你能不离开 gdb 就执行 UNIX shell 命令.
下面列出了将被调试的程序. 这个程序被称为 greeting , 它显示一个简单的问候, 再用反序将它列出.
#include <stdio.h>
main ()
{
char my_string[] = "hello there";
my_print (my_string);
my_print2 (my_string);
}
void my_print (char *string)
{
printf ("The string is %s/n", string);
}
void my_print2 (char *string)
{
char *string2;
int size, i;
size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size - i] = string[i];
string2[size+1] = `/0';
printf ("The string printed backward is %s/n", string2);
}
用下面的命令编译它:
gcc -o test test.c
这个程序执行时显示如下结果:
The string is hello there
The string printed backward is
输出的第一行是正确的, 但第二行打印出的东西并不是我们所期望的. 我们所设想的输出应该是:
The string printed backward is ereht olleh
由于某些原因, my_print2 函数没有正常工作. 让我们用 gdb 看看问题究竟出在哪儿, 先键入如下命令:
gdb greeting
--------------------------------------------------------------------------------
注意: 记得在编译 greeting 程序时把调试选项打开.
--------------------------------------------------------------------------------
如果你在输入命令时忘了把要调试的程序作为参数传给 gdb , 你可以在 gdb 提示符下用 file 命令来载入它:
(gdb) file greeting
这个命令将载入 greeting 可执行文件就象你在 gdb 命令行里装入它一样.
这时你能用 gdb 的 run 命令来运行 greeting 了. 当它在 gdb 里被运行后结果大约会象这样:
(gdb) run
Starting program: /root/greeting
The string is hello there
The string printed backward is
Program exited with code 041
这个输出和在 gdb 外面运行的结果一样. 问题是, 为什么反序打印没有工作? 为了找出症结所在, 我们可以在 my_print2 函数的 for 语句后设一个断点, 具体的做法是在 gdb 提示符下键入 list 命令三次, 列出源代码:
(gdb) list
(gdb) list
(gdb) list
--------------------------------------------------------------------------------
技巧: 在 gdb 提示符下按回车健将重复上一个命令.
--------------------------------------------------------------------------------
第一次键入 list 命令的输出如下:
1 #include <stdio.h>
2
3 main ()
4 {
5 char my_string[] = "hello there";
6
7 my_print (my_string);
8 my_print2 (my_string);
9 }
10
如果按下回车, gdb 将再执行一次 list 命令, 给出下列输出:
11 my_print (char *string)
12 {
13 printf ("The string is %s/n", string);
14 }
15
16 my_print2 (char *string)
17 {
18 char *string2;
19 int size, i;
20
再按一次回车将列出 greeting 程序的剩余部分:
21 size = strlen (string);
22 string2 = (char *) malloc (size + 1);
23 for (i = 0; i < size; i++)
24 string2[size - i] = string[i];
25 string2[size+1] = `/0';
26 printf ("The string printed backward is %s/n", string2);
27 }
根据列出的源程序, 你能看到要设断点的地方在第24行, 在 gdb 命令行提示符下键入如下命令设置断点:
(gdb) break 24
gdb 将作出如下的响应:
Breakpoint 1 at 0x139: file greeting.c, line 24
(gdb)
现在再键入 run 命令, 将产生如下的输出:
Starting program: /root/greeting
The string is hello there
Breakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :24
24 string2[size-i]=string[i]
你能通过设置一个观察 string2[size - i] 变量的值的观察点来看出错误是怎样产生的, 做法是键入:
(gdb) watch string2[size - i]
gdb 将作出如下回应:
Watchpoint 2: string2[size - i]
现在可以用 next 命令来一步步的执行 for 循环了:
(gdb) next
经过第一次循环后, gdb 告诉我们 string2[size - i] 的值是 `h`. gdb 用如下的显示来告诉你这个信息:
Watchpoint 2, string2[size - i]
Old value = 0 `/000'
New value = 104 `h'
my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23
23 for (i=0; i<size; i++)
这个值正是期望的. 后来的数次循环的结果都是正确的. 当 i=10 时, 表达式 string2[size - i] 的值等于 `e`, size - i 的值等于 1, 最后一个字符已经拷到新串里了.
如果你再把循环执行下去, 你会看到已经没有值分配给 string2[0] 了, 而它是新串的第一个字符, 因为 malloc 函数在分配内存时把它们初始化为空(null)字符. 所以 string2 的第一个字符是空字符. 这解释了为什么在打印 string2 时没有任何输出了.
现在找出了问题出在哪里, 修正这个错误是很容易的. 你得把代码里写入 string2
的第一个字符的的偏移量改为 size - 1 而不是 size. 这是因为 string2 的大小为 12, 但起始偏移量是 0,
串内的字符从偏移量 0 到 偏移量 10, 偏移量 11 为空字符保留.
为了使代码正常工作有很多种修改办法. 一种是另设一个比串的实际大小小 1 的变量. 这是这种解决办法的代码:
#include <stdio.h>
main ()
{
char my_string[] = "hello there";
my_print (my_string);
my_print2 (my_string);
}
my_print (char *string)
{
printf ("The string is %s/n", string);
}
my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = `/0';
printf ("The string printed backward is %s/n", string2);
}
在Linux下调试程序一般用GDB来执行。
这里简要介绍一下是否gdb调试程序的方法:
(1)进入gdb调试:
gdb + 已经编译通过的可执行程序 -》 就进入调试模式。例如:gdb MiddlePublisher
(2)r + 运行时的参数 -》 开始运行可执行程序。例如 r -lxml2 -f refile
(3)b + 断点 -》设置调试的断点。两种:一种是:b
CMSTask.cpp:200 表示在CMSTask.cpp文件的第200行设置断点。另一种:b
TaskManager::buildPubWinTask 表示在执行buildPubWinTask这个函数的时候停止。
(4)取消断点:
dis 1 表示取消第一个断点
dis 2 表示取消第二个断点
(5)查看设置断点信息: info b
(6)在断点停止处查看所在代码的详细信息:l
(7)可以在gdb中直接编译,然后再重新运行时,gdb会直接执行新编译好的可执行程序。例如:直接在gdb下执行make后再重庆运行。
(8)跟进一个函数:s
如果设置的断点是在一个函数入口。到达该断点时,键入s就可以进入该函数内部进行调试。如果有多个函数就多次键入S来进入内部的函数。
PS:
1、在SecureCRT远程登录界面上开启多个窗口。在窗口之间切换时用:Alt+1,Alt+2.....表示切换到第1个,第2个窗口。
2、同样在在SecureCRT远程登录界面上要粘贴复制好的内容用:Shift+Insert。
检查一切
memcpy, strcpy, strcat sprintf 动态数组下标。
这种问题多半世内存访问错误或者缓冲区溢出覆盖堆栈造成的。
调试方法:
gdb 调试程序或者gdb调试core文件
编译时加入-g调试选项,去掉-Ox选项
使用gdb运行,如果中断退出,使用bt命令查看调用堆栈,如果不是可以
通过thr n (n表示线程号,用 info thr查看)切换,然后bt看堆栈
以上方法在kernel 2.6+gdb 6中有问题
一:列文件清单
1. List
(gdb) list line1,line2
二:执行程序
要想运行准备调试的程序,可使用run命令,在它后面可以跟随发给该程序的任何参数,包括标准输入和标准输出说明符(<和>)和外壳通配符(*、?、[、])在内。
如果你使用不带参数的run命令,gdb就再次使用你给予前一条run命令的参数,这是很有用的。
利用set args 命令就可以修改发送给程序的参数,而使用show args 命令就可以查看其缺省参数的列表。
(gdb)set args –b –x
(gdb) show args
backtrace命令为堆栈提供向后跟踪功能。
Backtrace 命令产生一张列表,包含着从最近的过程开始的所以有效过程和调用这些过程的参数。
三:显示数据
利用print 命令可以检查各个变量的值。
(gdb) print p (p为变量名)
whatis 命令可以显示某个变量的类型
(gdb) whatis p
type = int *
print 是gdb的一个功能很强的命令,利用它可以显示被调试的语言中任何有效的表达式。表达式除了包含你程序中的变量外,还可以包含以下内容:
l 对程序中函数的调用
(gdb) print find_entry(1,0)
l 数据结构和其他复杂对象
(gdb) print *table_start
$8={e=reference=’/000’,location=0x0,next=0x0}
l 值的历史成分
(gdb)print $1 ($1为历史记录变量,在以后可以直接引用 $1 的值)
l 人为数组
人为数组提供了一种去显示存储器块(数组节或动态分配的存储区)内容的方法。早期的调试程序没有很好的方法将任意的指针换成一个数组。就像对待参数一样,让我们查看内存中在变量h后面的10个整数,一个动态数组的语法如下所示:
base@length