GDB 调试工具
在Sublime Text上非常难配置,网上也只有vs code配置方式,非常麻烦!一般程序调试,尤其是C语言,很多bug 都是内存问题,一般的逻辑问题不太多,反正非法访问内存等十分常见。通常的DEBUG方式就是输出,看程序能跑到哪一行就知道出错地方。
GDB是GNU开源组织发布的一个强大的Unix/Linux下的程序调试工具,没有图形化的友好界面(专业程序员),但是它的强大功能也足与微软的VC等工具媲美。
GDB 作用
- 启动用户程序后,可以按照用户的要求随意运行程序。
- 可让被调试的程序在用户所设定的断点处停住。
- 当程序被停住时,可以检查此时用户程序中所发生的事。
- 可动态改变用户程序的执行环境。
GDB 使用
gcc -g [,..] [file_name]:
- gdb进行调试是可执行文件而不是源代码。
- 对
.c文件进行编译一定要加上”-g”选项,这样编译出的可执行文件才包含调试信息。
使用 gdb 编译输出的可执行文件
| 调试命令 | 含义 |
|---|---|
l(list) |
查看所载入文件(头文件?) |
b(break) |
设置断点,程序运行到断点即可停止。 |
info b |
查看断点情况。 |
r(run) |
从第一行开始运行代码,或者指定行开始,即r row |
p(print) n |
查看变量 n 的值。 |
n(next) |
单步运行下一行代码(但是遇到函数调用不会进入函数)。 |
s(step) |
单步运行下一行代码,遇到函数调用进入函数。 |
c(continue) |
恢复程序运行,执行完剩余程序。(调试结束或不想调试执行完) |
接下来使用下面的代码进行 GDB 调试:
#include <stdio.h>/* gdb的调试代码 */int factorial(int n) {int fact = 1;for (int i = 2; i <= n; i++) {fact *= i;}return fact;}int main() {printf("Please input the n of factorial: ");int n;scanf("%d", &n);printf("factorial:%d\n", factorial(n));return 0;}
进行 gdb 调试,正常情况下编译选项加上-g即可。
b12@PC:~$ gcc -g -o sum sum.cb12@PC:~$ gdb ./sum # 使用方式GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1Copyright (C) 2020 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>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 "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...Reading symbols from ./sum...(gdb)
l/list
使用-l查看当前载入文件,就是查看源代码意思,一般默认显示 10 行,按回车后进行另外 10 行展示,直到代码结束。
(gdb) l # list
1 #include <stdio.h>
2 /* gdb的调试代码 */
3 int factorial(int n) {
4 int fact = 1;
5 for (int i = 2; i <= n; i++) {
6 fact *= i;
7 }
8 return fact;
9 }
10
(gdb) # 回车一下
11 int main() {
12 printf("Please input the n of factorial: ");
13 int n;
14 scanf("%d", &n);
15 printf("factorial:%d\n", factorial(n));
16 return 0;
17 }
18
(gdb) # 回车一下超界结束
Line number 19 out of range; sum.c has 18 lines.
查看某行附近代码情况:l[ist] row,非常简单,同样默认是上下共十行内代码(row前有5行,row,row后有4行)。
(gdb) list 11
6 fact *= i;
7 }
8 return fact;
9 }
10
11 int main() {
12 printf("Please input the n of factorial: ");
13 int n;
14 scanf("%d", &n);
15 printf("factorial:%d\n", factorial(n));
r/run
默认情况下,不设置断点,整个程序如同正常编译(非-g选项)注意:它只是运行完这个程序,但是没有退出 gdb 调试界面!!
(gdb) run # r
Starting program: /home/b12/sum
Please input the n of factorial: 6 # 程序等待scanf输入
factorial:720
[Inferior 1 (process 351) exited normally] # 正常结束标志
然而这样通过 scanf 函数停止等待输入是不能测试太多,还不如 printf 函数输出,因此设置断点就可以使得程序在任意行号停止(没有 GUI,要一行行看行号来设置断点,确实对于几万行不方便)。
b/break row/function
设置断点方式必须跟上行号或函数名字,所以前面的l/list查看代码行号显得尤为重要!
- 设置行号/函数断点
上述函数名设置断点,其是在函数定义处进行设置,而不是在调用处进行?那么可能会有的情况就是函数没调用或者函数/行号不存在? ```shell(gdb) list 1 1 #include <stdio.h> 2 /* gdb的调试代码 */ 3 int factorial(int n) { 4 int fact = 1; 5 for (int i = 2; i <= n; i++) { 6 fact *= i; 7 } 8 return fact; 9 } 10 (gdb) 11 int main() { 12 printf("Please input the n of factorial: "); 13 int n; 14 scanf("%d", &n); 15 printf("factorial:%d\n", factorial(n)); 16 return 0; 17 } 18 (gdb) break 15 # 在printf语句设置断点 Breakpoint 1 at 0x8001203: file sum.c, line 15. (gdb) b factorial # 在阶乘函数设置断点(注意不是调用处) Breakpoint 2 at 0x8001189: file sum.c, line 3.断点不存在的情况下
(gdb) b 100 No line 100 in the current file. Make breakpoint pending on future shared library load? (y or [n]) n (gdb) b fact Function “fact” not defined. Make breakpoint pending on future shared library load? (y or [n]) n
使用 info b/break 查看断点信息
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000008001203 in main at sum.c:15
2 breakpoint keep y 0x0000000008001189 in factorial at sum.c:3
运行程序:
```shell
(gdb) run
Starting program: /home/b12/sum
Please input the n of factorial: 5
Breakpoint 1, main () at sum.c:15
15 printf("factorial:%d\n", factorial(n));
# 跑到下一个断点处(默认程序结尾有一个断点)
(gdb) continue
Continuing.
p/print variant
查看变量的值,这个步骤往往就是在设置断点停止过程中进行的操作,不然整个程序运行,你连输入机会都没有。
(gdb) list
1 #include <stdio.h>
2 /* gdb的调试代码 */
3 int factorial(int n) {
4 int fact = 1;
5 for (int i = 2; i <= n; i++) {
6 fact *= i;
7 }
8 return fact;
9 }
10
(gdb)
11 int main() {
12 printf("Please input the n of factorial: ");
13 int n;
14 scanf("%d", &n);
15 printf("factorial:%d\n", factorial(n));
16 return 0;
17 }
18
# 1. 设置两个断点
(gdb) break 15
Breakpoint 1 at 0x1203: file sum.c, line 15.
(gdb) break factorial
Breakpoint 2 at 0x1189: file sum.c, line 3.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001203 in main at sum.c:15
2 breakpoint keep y 0x0000000000001189 in factorial at sum.c:3
# 2. 调试程序
(gdb) run
Starting program: /home/b12/sum
Please input the n of factorial: 5
Breakpoint 1, main () at sum.c:15
15 printf("factorial:%d\n", factorial(n));
(gdb) print n # 查看输入变量的值
$1 = 5
step/next n
step和next都是单步运行命令,但是对调用函数时两者不相同:
step在断点之后单步运行,如果遇到函数,跑到函数体内(当前函数不再源文件会去其他地方找,也就是详细过程,比如printf函数非常多找文件过程)next不会进入函数(其实是背地里做完这些,其始终在断点处的函数内,而给用户的感觉就不会跳跃到其他函数内)。
但是如果函数体内有断点,那么它们行为都是相同的。为了方便区别这两者的关系,我们重新用一个案例进行解释。
#include <stdio.h>
int sum(int n) {
int ans = 0;
for (int i = 1; i <= n; ++i) {
ans = ans + i;
}
return ans;
}
int main() {
printf("please input n: ");
int n;
scanf("%d", &n);
int ans = sum(n);
printf("sum: %d\n", ans);
return 0;
}
编译:gcc -Wall -g -o sum sum.c,然后使用 gdb ./sum调试可执行文件。
(gdb) list
1 #include <stdio.h>
2
3 int sum(int n) {
4 int ans = 0;
5 for (int i = 1; i <= n; ++i) {
6 ans = ans + i;
7 }
8 return ans;
9 }
10
(gdb)
11 int main() {
12 printf("please input n: ");
13 int n;
14 scanf("%d", &n);
15 int ans = sum(n);
16 printf("sum: %d\n", ans);
17 return 0;
18 }
(gdb)
Line number 19 out of range; src/sum.c has 18 lines.
在函数处(本例中第15行代码)设置调用断点:
(gdb) break 15
Breakpoint 1 at 0x788: file src/sum.c, line 15.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000000788 in main at src/sum.c:15
next:遇到函数调用不会进入函数调试
(gdb) run
Starting program: /home/b07/c/chapter3/bin/sum
please input n: 7
Breakpoint 1, main () at src/sum.c:15
15 int ans = sum(n);
(gdb) print n
$1 = 7
(gdb) next
16 printf("sum: %d\n", ans);
(gdb) next
sum: 28
17 return 0;
(gdb) next
18 }
(gdb) next
__libc_start_main (main=0x8000748 <main>, argc=1, argv=0x7ffffffee1e8,
init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7ffffffee1d8)
at ../csu/libc-start.c:344
344 ../csu/libc-start.c: No such file or directory.
(gdb) next
[Inferior 1 (process 190) exited normally]
step:遇到函数调用进入函数体内部进行单步调试
(gdb) run
Starting program: /home/b07/c/chapter3/bin/sum
please input n: 8
Breakpoint 1, main () at src/sum.c:15
15 int ans = sum(n);
(gdb) print n
$2 = 8
(gdb) step
sum (n=8) at src/sum.c:4
4 int ans = 0;
(gdb) step
5 for (int i = 1; i <= n; ++i) {
(gdb) step 6
5 for (int i = 1; i <= n; ++i) {
(gdb) print ans
$3 = 6
(gdb) step 11
8 return ans;
(gdb) print ans
$4 = 36
(gdb) step
9 }
(gdb) step
main () at src/sum.c:16
16 printf("sum: %d\n", ans);
(gdb) step
__printf (format=0x8000868 "sum: %d\n") at printf.c:28
28 printf.c: No such file or directory.
(gdb) step
32 in printf.c
(gdb) step
33 in printf.c
(gdb) step
32 in printf.c
(gdb) step
_IO_vfprintf_internal (s=0x7fffff3ec760 <_IO_2_1_stdout_>,
format=0x8000868 "sum: %d\n", ap=ap@entry=0x7ffffffee010)
at vfprintf.c:1244
1244 vfprintf.c: No such file or directory.
(gdb) continue
Continuing.
sum: 36
[Inferior 1 (process 194) exited normally]
总结:
- 通常使用
printf进行 debug,因为这样可以直接定位代码具体出错地方。 - 在局部建议使用 GDB 进行调试,因为我们可以模拟整个程序的执行过程。
- 虽然 GDB 比较难用,因为他没有 GUI,而如今大多数编辑器都含有断点 DEBUG 功能。但是其在 CLI 上也是非常强大且实用的调试工具,如同 vi 编辑器一样,不得不学习掌握。
