写了很长时间的代码,一直是使用IDE的debug,一直想试一下这一款最原始最直接的debug工具,最近在弄一生一芯,所以打算认真学一下gdb,分享以下自己的学习经验。
man gdb
or
gdb --help
使用以上命令可以看到gdb工具的使用方法,在此只做简单介绍,后期不断补充。接下来将会介绍gdb使用中频繁会用到的功能。我们以一个简单程序为例进行介绍:
test.c
1 #include <iostream>
2 int a = 0;
3 void fun()
4 {
5 int c;
6 a += 1;
7 printf("%d\n", a);
8 }
9 int main()
10 {
11 printf("start\n");
12 a = 0;
13 int b=10;
14 for (int i = 0; i < 10; i++)
15 fun();
16 printf("end\n");
17 }
在编译时请使用 -g , -g请求编译器和链接器在可执行文件本身中生成并保留源级调试/符号信息。
g++ -g test.c
现在得到了可执行文件a.out,使用gdb开始调试可执行文件。
gdb a.out
1.设置断点
(gdb) b/breakpoint <location>
我们在程序运行的过程中检测某个变量的变化,除了在程序中间插入printf()打印以外,还可以使用gdb对中间变量打印检测,当然我们我们在gdb中也可以使用更复杂的脚本。
设置断点可以使用"b"或者"break“,断点的设置可以通过函数名、行号、文件名+函数名、文件名+行号以及偏移量、地址等进行设置。通过info break可以查看设置的断点。
格式为:
-
break 函数名
-
break 行号
-
break 文件名:函数名
-
break 文件名:行号
-
break +偏移量
-
break -偏移量
-
break *地址
(gdb) b 11
Breakpoint 1 at 0x11eb: file test.c, line 11.
(gdb) b 15
Breakpoint 2 at 0x1214: file test.c, line 15.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000011eb in main() at test.c:11
2 breakpoint keep y 0x0000000000001214 in main() at test.c:15
我们在第11行和15行设置了断点,我们开始运行测试程序,使用”run"或者"r",其作用是连续执行程序,直到遇到断点。
(gdb) r
Starting program: /home/hs/gdb_test/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, main () at test.c:11
11 printf("start\n");
我们可以看到测试程序在第11行停住了(请注意此处程序是停在了执行printf()之前),接下来介绍4个命令:
continue|c //继续执行程序,直到下个断点
next|n //执行下一行语句
step|s //进入正在执行的函数内部
finish //进入正在执行的函数内部
很容易理解它们的含义,假设我们现在想要运行printf()这条打印命令,只需要next即可。
(gdb) n
start
12 a = 0;
(gdb) n
13 int b=10;
(gdb) n
14 for (int i = 0; i < 10; i++)
(gdb) n
Breakpoint 2, main () at test.c:15
15 fun()
现在执行到了fun()函数,我们可以使用前文提到的“step|s” 进入正在执行的函数内部。
(gdb) s
fun () at test.c:6
6 a += 1;
若我们现在想知道a的结果,可以使用进行变量打印。
2.变量查看修改及打印
(gdb) p/print/inspect [/<显示格式>] <表达式>
我们可以在此处输出a的值
6 a += 1;
(gdb) p "a = %d\n", a
$1 = 0
(gdb) p "b = %d\n", b
$2 = {i = {0, 1068498944}, x = 0.0625, d = 0.0625}
(gdb) n
7 printf("%d\n", a);
(gdb) n
1
8 }
我们可以看到到执行完 a+=1之后,a由0变成了1。
我们当然我们也可以使用以下命令打印当前作用域的变量。
(gdb) i/info loc/locals
(gdb) i locals
c = <optimised out>
fun()函数的作用域中没有变量a,所以只输出了在fun()中定义的变量c,此时使用finish命令”退出“fun()函数,返回到main()函数,当然程序实际上不会跳过fun()函数的内部每一个步骤,只是我们在执行next的时候"fun()"命令只当做一步,而不会展开执行。
(gdb) fin
Run till exit from #0 fun () at test.c:8
main () at test.c:14
14 for (int i = 0; i < 10; i++)
(gdb) i locals
i = 0
b = 10
此时”退出“fun()函数后使用i locals 就能看到当前作用域中的局部变量i(存在于for循环中的局部变量)和b(main()中定义)。
3.设置监视点
(gdb) watch <addr/变量> // 设置观察点,当一个变量的值发生变化时,程序会停下来
(gdb) rwatch <addr/变量> // 设置观察点,当一个变量被读时,停止程序
(gdb) awatch <addr/变量> // 设置观察点,当一个变量被读或写时,停止程序
(gdb) info watch // 查看所有设置的观察点
(gdb) watch <addr/变量> if (expression) // 设置条件监视点
我们在for循环中设置监视点,检视变量i。
(gdb) watch i
Hardware watchpoint 3: i
(gdb) info watch
Num Type Disp Enb Address What
3 hw watchpoint keep y i
当检测到i发生变化时程序则会暂停。
(gdb) c
Continuing.
Hardware watchpoint 3: i
Old value = 0
New value = 1
0x000055555555521d in main () at test.c:14
14 for (int i = 0; i < 10; i++)
运行quit | q 或者 exit可以退出gdb的执行。
以上则是gdb的简单使用,我们可以看到作用域的概念在gdb中还是比较重要的,更多使用细节可以参考官方手册。
参考链接:
https://blog.csdn.net/StarRain2016/article/details/119779616
https://www.yanbinghu.com/2019/04/20/41283.html