简介
在使用图形化ide进行程序开发时,通常编辑工具都会自带调试功能,可以打断点,一步一步的执行代码并随时查看每个变量的实时数值,但是在进行c和c++开发时,常常是在linux系统下进行,并且是通过vim或vi编辑器,没有图形化的界面,这是gdb调试工具就会派上了用场,它提供了我们日常调试程序时需要的大多功能,本篇文章是作者在阅读《Linux C编程:一站式学习》一书中关于GDB工具使用这一章节的学习笔记。
1.单步执行和跟踪函数调用
1.1 示例程序
首先阅读以下代码,该代码的主要功能是实现数的累加
#include <stdio.h>
int add_range(int low, int hight)
{
int i, sum;
for(i = low; i <= hight; i++)
{
sum += i;
}
return sum;
}
int main()
{
int res1, res2;
res1 = add_range(1, 10);
res2 = add_range(1, 100);
printf("res1 = %d, res2 = %d\n", res, res2);
return 0;
}
以下是书中给出的运行结果
res1 = 55, res2 = 5105
但是,有意思的是,作者亲自运行之后,发现并没有出现原书作者给出的输出结果,作者也知道不会出现,因为变量sum定义之后,没有初始化,书中的结果可能是作者为了将gdb调试专门设置的输出结果吧,不妨碍学习。以下是作者运行程序输出结果
zz@ubuntu:~/Project/gdb_study$ ./demo1
res1 = 21911, res2 = 26961
zz@ubuntu:~/Project/gdb_study$ ./demo1
res1 = 22009, res2 = 27059
zz@ubuntu:~/Project/gdb_study$ ./demo1
res1 = 22012, res2 = 27062
zz@ubuntu:~/Project/gdb_study$ ./demo1
res1 = 22051, res2 = 27101
zz@ubuntu:~/Project/gdb_study$ ./demo1
res1 = 22155, res2 = 27205
zz@ubuntu:~/Project/gdb_study$ ./demo1
res1 = 22001, res2 = 27051
zz@ubuntu:~/Project/gdb_study$ ./demo1
res1 = 22016, res2 = 27066
可以看到,结果是一直变化的,这非常符合变量未初始化就被使用的结果,我们给sum赋值为0之后,程序会正常运行输出我们想要的结果。这里假设我们不知道错在哪,程序运行输出之后,我们假装一脸懵逼,这时候gdb就该上场了。
1.2 常用命令
运行以下命令
gcc -g demo1.c -o demo1
gdb demo1
在编译的时候加上编译选项-g
表示是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。
1.2.1 help
帮助命令,可以查看命令的类别,也可以指定查看某一命令的具体说明
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands.
breakpoints -- Making program stop at certain points.
data -- Examining data.
files -- Specifying and examining files.
internals -- Maintenance commands.
obscure -- Obscure features.
running -- Running the program.
stack -- Examining the stack.
status -- Status inquiries.
support -- Support facilities.
tracepoints -- Tracing of program execution without stopping the program.
user-defined -- User-defined commands.
Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Type "apropos -v word" for full documentation of commands related to "word".
Command name abbreviations are allowed if unambiguous.
1.2.2 list (l)
列出源代码内容,运行一次命令,显示10行,需要继续浏览,只需要按回车键,默认执行上次的命令
(gdb) list
1 #include <stdio.h>
2
3 int add_range(int low, int hight)
4 {
5 int i, sum;
6 for(i = low; i <= hight; i++)
7 {
8 sum += i;
9 }
10 return sum;
(gdb)
11 }
12
13 int main()
14 {
15 int res1, res2;
16 res1 = add_range(1, 10);
17 res2 = add_range(1, 100);
18 printf("res1 = %d, res2 = %d\n", res1, res2);
19 return 0;
20 }
(gdb)
1.2.3 quit
退出命令,推出gdb调试环境
1.2.4 start
程序开始运行命令
(gdb) start
Temporary breakpoint 1 at 0x1176: file demo1.c, line 14.
Starting program: /home/zz/Project/gdb_study/demo1
Temporary breakpoint 1, main () at demo1.c:14
14 {
运行该命令之后可以发现,自动跳转到了main函数的开头部分,同时表明了所在行号。
1.2.5 next(n)
继续执行命令,控制程序执行下一行
(gdb) next
16 res1 = add_range(1, 10);
(gdb)
17 res2 = add_range(1, 100);
(gdb)
18 printf("res1 = %d, res2 = %d\n", res1, res2);
(gdb)
res1 = 21900, res2 = 26950
19 return 0;
(gdb)
20 }
(gdb)
1.2.6 step(s)
跟踪进入执行的函数
在一路调用next命令之后我们发现,并没有发现任何异常,可能程序出错点并不在主函数,而是实在主函数中调用的函数中,但是执行next命令,系统会自动跳过该函数,想要进入该函数一步一步跟踪程序就需要step命令了。
(gdb) start
Temporary breakpoint 2 at 0x555555555176: file demo1.c, line 14.
Starting program: /home/zz/Project/gdb_study/demo1
Temporary breakpoint 2, main () at demo1.c:14
14 {
(gdb) next
16 res1 = add_range(1, 10);
(gdb) step
add_range (low=21845, hight=1431654941) at demo1.c:4
4 {
从上面看到,进入add_range函数之后,形参都被赋予了乱七八糟的数值,和我们预料的不一样,和书上的说的也不一样,这里先不做说明,后面作者会有一些个人猜想。
1.2.7 backtrace(bt)
查看函数调用的栈帧
(gdb) bt
#0 add_range (low=21845, hight=1431654941) at demo1.c:4
#1 0x0000555555555191 in main () at demo1.c:16
1.2.8 info(i)
查看参数的值
- info locals 查看局部参数
- info arg 查看函数形参
(gdb) i locals
i = 1431654864
sum = 21845
(gdb) info locals
i = 1431654864
sum = 21845
(gdb) info arg
low = 21845
hight = 1431654941
可以看到函数的局部参数和形参都是乱起八糟的值,作者猜想,此时函数刚被压入栈中,内存刚刚被分配,形参和局部参数被创建,实参对形参的赋值还未完成,我们在往下执行一步。
(gdb) n
6 for(i = low; i <= hight; i++)
(gdb) info locals
i = 1431654864
sum = 21845
(gdb) info arg
low = 1
hight = 10
(gdb)
可以看到,当函数执行到第六行,局部参数是乱码是因为未被初始化也被被赋值,但是形参可以看到已经是正确的值了,说明实参成功为形参赋值。
1.2.9 frame(f)
函数栈帧切换
此时此刻,程序已经在add_range函数中,如果此时想要查看主函数中的参数信息该怎么办?使用frame命令可以机型函数栈帧的切换
(gdb) bt
#0 add_arrange (low=1, hight=10) at demo1.c:6
#1 0x0000555555555191 in main () at demo1.c:16
(gdb) frame 1
#1 0x0000555555555191 in main () at demo1.c:16
16 res1 = add_arrange(1, 10);
(gdb) i locals
res1 = 0
res2 = 0
(gdb) f 0
#0 add_arrange (low=1, hight=10) at demo1.c:6
6 for(i = low; i <= hight; i++)
可以看到,使用backtrace
命令,查看当前函数栈帧信息,使用frame 函数栈帧编号
命令切换到制定的函数栈帧,此时使用info locals
命令查看当前主函数中的局部参数的值,查看完之后,使用frame
命令切换到add_range函数,可以看到此时还是处于之前函数运行的地方。
1.2.10 print(p)
打印变量的值
(gdb) print sum
$1 = 21845
这里的$1
表示gdb保存着这些中间结果,$1
后面的编号会自动增长,在命令中可以用$1
、$2
、$3
等编号代替相应的值。
1.2.11 set var
设置变量为指定值
此时我们已经知道了,是因为sum没有被初始化的原因,导致程序的错误,为了验证我们的想法,我们将sum手动初始化,查看结果是否为我们所期待的结果。
(gdb) set var sum=0
(gdb) print sum
$4 = 0
1.2.12 finish
连续运行到当前函数返回为止,然后停下来等待命令
(gdb) finish
Run till exit from #0 add_arrange (low=1, hight=10) at demo1.c:6
0x0000555555555191 in main () at demo1.c:16
16 res1 = add_arrange(1, 10);
Value returned is $5 = 55
可以看到,函数成功运行到最后,同时返回值为55,说明我们的想法完全正确,程序出错是因为sum变量未初始化的原因。
1.3 命令小结
命令 | 描述 |
---|---|
backtrace(bt) | 查看各级函数调用及参数 |
finish | 连续运行到当前函数返回为止,然后停下来等待命令 |
frame(f) | 选择函数栈帧 |
info(i) | 查看当前参数信息(locals:查看局部变量参数;arg:查看函数形参数值;registers:查看当前寄存器数据) |
list(l) | 列出源代码,每次列出10行,有记忆性,接上次列出之后往下列出(后面加行号,从该行号开始列出代码;后面加函数名:列出该函数的代码) |
next(n) | 执行下一条语句 |
print(p) | 打印表达式的值,通过表达式可以修改变量的值或者调用函数 |
quit(q) | 退出gdb调试环境 |
set var | 修改变量的值 |
start | 开始执行程序,停留在main函数的第一行语句前面等待命令 |
step(s) | 执行下一条语句,如果有函数则进入到函数中 |