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.c
b12@PC:~$ gdb ./sum # 使用方式
GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1
Copyright (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 编辑器一样,不得不学习掌握。