在Linux应用程序开发中,最常用的调试器是gdb,它可以在程序中设置断点、查看变量值、一步一步跟踪程序的执行过程。
GDB(GNU symbolic debugger)简单地说就是一个调试工具。它是一个受通用公共许可证即GPL保护的自由软件。
像所有的调试器一样,GDB可以让你调试一个程序,包括让程序在你希望的地方停下,此时你可以查看变量、寄存器、内存及堆栈。更进一步你可以修改变量及内存值。GDB是一个功能很强大的调试器,它可以调试多种语言(Ada、C++、Java、Pascal)。还有一点要说明的是,GDB是一个调试器,而不像 VC 是一个集成环境。你可以使用一些前端工具如XXGDB、DDD等。他们都有图形化界面,因此使用更方便,但它们仅是GDB的一层外壳。因此,你仍应熟悉GDB命令。事实上,当你使用这些图形化界面时间较长时,你才会发现熟悉GDB命令的重要性。
一、启动和退出gdb
首先注意一点,gdb调试的对象是可执行文件,而不是程序的源代码,所以你要先用gcc生成可执行文件
另外,如果要使一个可执行文件可以被gdb调试,那么在使用编译器gcc编译程序时需要加入-g选项。-g选项告诉gcc在编译程序时加入调试信息,这样才可以调试这个被编译的程序。 例如:gcc -g hello.c -o hello(-o是命名编译出来的可执行文件)
(一)、调试一个程序的命令格式
格式:gdb <可执程序文件名>
例:#gdb hello
或
1、 #gdb →( 进入gdb环境)
2、 (gdb) file hello → (把hello可执行文件加载到gdb里面)
(二)、退出
(gdb) quit
二、显示和查找程序的源代码
在调试时,一般要查看程序的源代码。list 命令用于列出程序的源代码,使用格式如下:
(gdb) list 显示10行代码,若再次运行该命令则显示接下来的10行代码(gdb默认设置)
(gdb) list 5,10 显示源代文件test.c中的第5行到第10行的代码,
(gdb) list test.c:5,10 显示源文件test.c中第5行到第10行的代码,在调试含有多个源文件的程序时使用
(gdb) list get_sum 显示get_sum函数周围的代码
(gdb) list test.c:get_sum 显示源文件test.c中get_sum函数周围的代码,在调试含多个源文件的程序时使用
(gdb) search <字符串> 用来从当前行向后查找第一个匹配的字符串
(gdb)reverse-search<字符串> 用来从当前行向前查找第一个匹配的字符串
举例:
三、执行程序和获得帮助
使用gdb hello或(gdb) file hello只是装入程序,程序并没有运行,如果要使程序运行,将文件装入gdb以后,在gdb提示符下输入run即可。
(gdb) run
如果想要详细了解gdb某个命令的使用方法,可以使用help命令
(gdb)help list
(gdb)help all
四、设置和管理断点
在调试程序时,往往需要程序在运行到某行、某个函数或某个条件发生时暂停下来,然后查看此时程序的状态,如各个变量的值、某个表达式的值等。为此,可以设置断点(break)。断点使程序运行到某个位置时暂停下来,以便检查和分析程序。
1、 以行号设置断点
在gdb中,大部分都是使用break命令为程序设置断点。而指定断点时,最常用的是为某行设置断点。例:
(gdb) break 7
Breakpoint 1 at0x80483c0: file test.c, line 7.
然后我们输入run命令运行程序:
(gdb) run
Starting program:/tmp/test
Breakpoint 1,get_sum (n=100) at test.c:7
7 sum +=i;
可以看到,程序运行完第6行的指令后就暂停了,第7行的代码并没有执行而是被gdb的断点中断了。此时,我们可以查看各个变量和表达式的值,以了解程序的当前状态。
2、以函数名设置断点
在break命令后跟上函数名,就可以为函数设置断点。例:
(gdb) break get_sum
Breakpoint 1 at0x80483aa: file test.c, line 5.
(gdb) run
Starting program:/tmp/test
Breakpoint 1,get_sum (n=100) at test.c:5
5 int sum = 0,i;
可以看到,程序在第5行停了上来。
3、以条件表达式设置断点
程序在运行过程中,当某个条件满足时,程序在某行中断暂停执行
方法1 命令格式:break行号或函数名 if 条件
例:
(gdb) clear
Deleted breakpoint1
(gdb) break7 if i==99
Breakpoint 2 at0x80483c0: file test.c, line 7.
(gdb) run
The program beingdebugged has been started already.
Start it from thebeginning? (y or n) y
Starting program:/tmp/test
Breakpoint 2,get_sum (n=100) at test.c:7
7 sum +=i;
可以看到,运行程序后在i==99时,程序中断在第7行。
方法2 watch <条件表达式>
例:(gdb) watchi==99
No symbol"i" in current context.
(gdb) list 6
1 #include <stdio.h>
2
3 int get_sum(int n)
4 {
5 int sum = 0,i;
6 for (i=0;i<n;i++)
7 sum +=i;
8 return sum;
9 }
10
(gdb) break 6
Breakpoint 1 at0x80483b1: file test.c, line 6.
(gdb) run
Starting program:/tmp/test
Breakpoint 1,get_sum (n=100) at test.c:6
6 for (i=0;i<n;i++)
Gdb运行后,以命令watch i==99设置条件断点,但是失败了,gdb提示在当前程序的上下文中没有符号i,这是因为此时test程序没有运行,变量i还没有被定义。
为了解决这个问题,首先在第6行设置断点,然后使用run命令运行程序,程序暂停在第6行,此时第5行的语句已经被执行,所以变量i已经定义。这时就可以使用watch i==99设置断点了。
4、查看当前设置的断点
使用info breakpoints命令可以查看当前所有的中断点,例如:
(gdb) break 7
Breakpoint 1 at0x80483c0: file test.c, line 7.
(gdb) break 15 if result=5050
Breakpoint 2 at0x8048405: file test.c, line 15.
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c0 in get_sum at test.c:7
2 breakpoint keep y 0x08048405 in main at test.c:15
stop only if result = 5050
其中:
Num 表示断点的编号
Type 说明类型,类型为breakpoint是指中断
Disp 指明中断点在生效一次后是否失去作用,是则为disp,不是则为keep
Enb 说明中断点是否有效,有效则为y,无效则为n
Address列 说明中断所处的内存地址
What列 列出中断发生在哪个函数的第几行
Stop only if result==5050 说明这是一个条件中断
5、使中断失效或有效
disable <断点编号> 可以使某个断点失效,程序运行到该断点时不会停下来而是继续运行
enable <断点编号> 可以使某个断点恢复有效
例:
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c0 in get_sum at test.c:7
2 breakpoint keep y 0x08048405 in main at test.c:15
stop only if result = 5050
(gdb) disable 2
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c0 in get_sum at test.c:7
2 breakpoint keep n 0x08048405 in main at test.c:15
stop only if result = 5050
(gdb) enable 2
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c0 in get_sum at test.c:7
2 breakpoint keep y 0x08048405 in main at test.c:15
stop only if result = 5050
6、删除断点
(gdb) clear 删除程序中所有的断点
(gdb) clear<行号> 删除此行的断点
(gdb) clear<函数名> 删除该函数的断点
(gdb) delete<断点编号> 删除指定编号的断点。如果一次要删除多个断点,各个断点编号以空格隔开
例:
(gdb) break 6
Breakpoint 1 at0x80483b1: file test.c, line 6.
(gdb) break 7
Breakpoint 2 at0x80483c0: file test.c, line 7.
(gdb) break 8 ifsum==5050
Breakpoint 3 at0x80483cf: file test.c, line 8.
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483b1 in get_sum at test.c:6
2 breakpoint keep y 0x080483c0 in get_sum at test.c:7
3 breakpoint keep y 0x080483cf in get_sum at test.c:8
stop only if sum == 5050
(gdb) clear 6
Deleted breakpoint1
(gdb) info breakpoints
Num Type Disp Enb Address What
2 breakpoint keep y 0x080483c0 in get_sum at test.c:7
3 breakpoint keep y 0x080483cf in get_sum at test.c:8
stop only if sum == 5050
(gdb) delete 2 3
(gdb) info breakpoints
No breakpoints orwatchpoints.
7、display <表示式>
在每次程序停在断点位置时,自动显示表达式中的内容
8、commands
>命令
>end 结束命令输入
作用:指定在程序到达断点位置时需要执行的调试器命令
五、查看和设置变量的值
当程序执行到中断点暂停执行时,需要查看变量或表达式的值,借此了解程序的执行状态
1、print命令
作用:print命令一般用来打印变量或表达式的值,也可以用来打印内存中从某个变量开始的一段存区域的内容,还可以用来对某个变量进行赋值。
格式:
print <变量或表达式> 打印变量或表达式的当前值,gdb会用伪变量($n)来保存输出值以备用
Print <变量=值>; 对变量进行赋值
Print <表达式@要打印的值的个数n> 打印以表达式值开始的n个数
例:
(gdb) break 7
Breakpoint 4 at0x80483c0: file test.c, line 7.
(gdb) run
Starting program:/tmp/test
Breakpoint 4,get_sum (n=100) at test.c:7
7 sum += i;
(gdb)print i<n 打印出i<n表达式的值,显然这个表达式为真,因此值为1
$1 = 1
(gdb) print i
$2 = 0
(gdb) print sum
$3 = 0
(gdb) print i=200
$4 = 200
(gdb) continue
Continuing.
1+2+…+100=200
Program exitednormally.
2、whatis 命令
作用:用来显示某个变量或表达式值的数据类型
格式:whatis <变量或表达式>
例:
(gdb) break 7
Note: breakpoint 4also set at pc 0x80483c0.
Breakpoint 5 at0x80483c0: file test.c, line 7.
(gdb) run
Starting program:/tmp/test
Breakpoint 4,get_sum (n=100) at test.c:7
7 sum += i;
(gdb) whatis i
type = int
(gdb) whatis sum+0.5
type = double
3、set 命令
作用:用来给变量赋值
格式:set variable 变量=值 相当于print 变量=值
例:set variable i=200,相当于print i=200
六、控制程序的执行
当程序执行到指定的中断点,查看了变量或表达式的值后,可以让程序继续运行。也可以让程序一步一步地执行,或可以让程序地直运行下去直到下一个断点或运行完为止。
1、 continue命令
作用:让程序继续运行,直到下一个断点或运行完为止。
格式:continue
2、 kill命令
作用:用于结束当前程序的调试
例:(gdb) kill
Kill the program being debugged? (y or n) y
3、 next和step命令
作用:一次一条地执行程序代码
next和step的区别:
1)、如果遇到函数调用,next会把该函数调用当作一条语句来执行,再次输入next会执行函数调用后的语句
2)、step则会跟踪进入函数,一次一条地执行函数内的代码,直到函数的代码执行完,才执行函数调用后的语句
例:next语句的用法
(gdb) list 1,17
1 #include <stdio.h>
2
3 int get_sum(int n)
4 {
5 int sum = 0,i;
6 for(i=0;i<n;i++)
7 sum += i;
8 return sum;
9 }
10
11 int main()
12 {
13 int i=100,result;
14 result = get_sum(i);
15 printf("1+2+...+%d=%d\n",i,result);
16 return 0;
17 }
(gdb) break 13
Breakpoint 1 at 0x80483f0: file test.c, line13.
(gdb) run
Starting program: /tmp/test
Breakpoint 1, main () at test.c:13
13 int i=100,result;
(gdb) next
14 result = get_sum(i);
(gdb) next
15 printf("1+2+...+%d=%d\n",i,result);
(gdb) next
1+2+...+100=4950
16 return 0;
(gdb) next
17 }
(gdb) next
0x4003328b in __libc_start_main () from/lib/libc.so.6
(gdb) next
Single stepping until exit from function__libc_start_main,
which has no line number information.
Program exited normally.
例:step语句的用法
(gdb) list 1,17
1 #include <stdio.h>
2
3 int get_sum(int n)
4 {
5 int sum = 0,i;
6 for(i=0;i<n;i++)
7 sum += i;
8 return sum;
9 }
10
11 int main()
12 {
13 int i=100,result;
14 result = get_sum(i);
15 printf("1+2+...+%d=%d\n",i,result);
16 return 0;
17 }
(gdb) break 13
Breakpoint 1 at 0x80483f0: filetest.c, line 13.
(gdb) run
Starting program: /tmp/test
Breakpoint 1, main () at test.c:13
13 int i=100,result;
(gdb) step
14 result = get_sum(i);
(gdb) step
get_sum (n=100) at test.c:5
5 int sum = 0,i;
(gdb) step
6 for(i=0;i<n;i++)
(gdb) step
7 sum += i;
(gdb) step
6 for(i=0;i<n;i++)
(gdb) step
7 sum += i;
(gdb) step
6 for(i=0;i<n;i++)
(gdb) step
7 sum += i;
4、 nexti和stepi命令
作用:用来单步执行一条机器指令,注意不是单步执行一行语句。单步执行一行语句的命令是next和step命令
注意:
nexti和next类似,不会跟踪进入函数内部去执行
Stepi和step类似,跟踪进入函数执行。
参考:
https://blog.csdn.net/sanganqi_wusuierzi/article/details/54783958
https://sourceware.org/gdb/
https://blog.csdn.net/haoel/article/details/2879
https://blog.csdn.net/liigo/article/details/582231
https://www.cnblogs.com/chenmingjun/p/8280889.html
https://www.cnblogs.com/hanframe/p/3585622.html
总结:GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。