GDB作为这个调试的利器,是非常值得我们去深入学习的。当我们的程序出现这个语法错误我们可以通过这个编译器的保存进行解决但是如果出现了逻辑错误,我们用肉眼是很难看出来。此时调试工具就排上用场了。实际场景中解决逻辑错误最高效的方法,就是借助调试工具对程序进行调试。
所谓调试(Debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。比如,可以让程序停在某个地方,查看当前所有变量的值,或者内存中的数据;也可以让程序一次只执行一条或者几条语句,看看程序到底执行了哪些代码。也就是说,通过调试程序,我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码。
各位老铁可以查看自己的机器上是否有gdb。如果没有的话,如果是再centos这个云服务器上可以使用下面这条命令进行安装
yum -y install gdb
首先我们需要知道程序的发布方式有两种,debug模式和release模式。Linux gcc/g++出来的二进制程序,默认是release模式要使用gdb调试,必须在源代码生成二进制程序的时候, 加上 -g 选项。
首先我们再编译成员的时候需要使用-g选项,下面博主以目录下的这个文件为例子演示一下
g++ mytest.cc -o mytest -g
启动gdb的命令如下,在这里我们的可执行程序是mytest。各位老铁记得用自己的可执行程序
gdb mytest
gdb -q mytest表示不打印gdb版本信息,界面较为干净;
测试结果如下:
[ksy@VM-4-17-centos scripts]$ clear
[ksy@VM-4-17-centos scripts]$ gdb mytest
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 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-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb)
而这个gdb -q mytest就不会打印这些调试信息看起来就比较舒服
[ksy@VM-4-17-centos scripts]$ clear
[ksy@VM-4-17-centos scripts]$ gdb -q mytest
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb)
在命令行的情况下,我们调试肯定是需要看这个源码的,如果查看源码了?我们可以使用这个
list指令,简写成l.下面我们来演示一下如何查看这个源码,首先启动gdb上面已经说过了老铁可以看看在这里我就直接启动了。
(gdb)
Line number 19 out of range; mytest.cc has 18 lines.
(gdb) quit
[ksy@VM-4-17-centos scripts]$ clear
[ksy@VM-4-17-centos scripts]$ gdb -q mytest
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb) l
4 {
5 int sum=0;
6 for(int i=0;i<=top;i++){
7 sum+=i;
8 }
9 return sum;
10 }
11 int main()
12 {
13 int top=100;
(gdb)
14 int result=SumToTop(top);
15 cout<<"结果是"<<result<<endl;
16 return 0;
17
18 }
(gdb)
这个gdb默认可能只显示几行,并没有全部显示出来,我们可以敲回车让其把源码全部显示出来。同时我们也发现了这个并不是从第一行开始的如果我们想要从第一行开始查看我们可以使用 list n
[ksy@VM-4-17-centos scripts]$ gdb -q mytest
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb) l 0
1 #include<iostream>
2 using namespace std;
3 int SumToTop(int top)
4 {
5 int sum=0;
6 for(int i=0;i<=top;i++){
7 sum+=i;
8 }
9 return sum;
10 }
(gdb)
11 int main()
12 {
13 int top=100;
14 int result=SumToTop(top);
15 cout<<"结果是"<<result<<endl;
16 return 0;
17
18 }
(gdb)
此时我们就可以把代码全部看到了,非常的舒服虽然没有Windows上图形化界面那么舒服。
在gdb 当中运行程序我们可以使用这个 run(简写成r)运行程序。运行程序直到遇到 结束或者遇到断点等待下一个命令。在这里博主写把博主的测试代码拿出来给大家看看。
#include
using namespace std;
int SumToTop(int top)
{
int sum=0;
for(int i=0;i<=top;i++){
sum+=i;
}
return sum;
}
int main()
{
int top=100;
int result=SumToTop(top);
cout<<"结果是"<<result<<endl;
return 0;
}
下面来演示一下如何运行
[ksy@VM-4-17-centos scripts]$ gdb -q mytest
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb) run
Starting program: /home/ksy/scripts/mytest
结果是5050
[Inferior 1 (process 31476) exited normally]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64
(gdb)
此时由于我们没有设置这个断点所以这个他就直接跑完了。直接就拿到了结果了。
在Linux下我们可以使用break (简写b)来设置这个断点 .其格式为这个 b(简写)行号。我们也可以同info指定查看断点信息。其指令为 info breakpoints(查看所有断点信息)或者简写为info b 下面我们来演示一下如何设置断点
[ksy@VM-4-17-centos scripts]$ gdb -q mytest
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb) l 0
1 #include<iostream>
2 using namespace std;
3 int SumToTop(int top)
4 {
5 int sum=0;
6 for(int i=0;i<=top;i++){
7 sum+=i;
8 }
9 return sum;
10 }
(gdb)
11 int main()
12 {
13 int top=100;
14 int result=SumToTop(top);
15 cout<<"结果是"<<result<<endl;
16 return 0;
17
18 }
(gdb) b 13
Breakpoint 1 at 0x40082d: file mytest.cc, line 13.
(gdb) b 15
Breakpoint 2 at 0x400841: file mytest.cc, line 15.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040082d in main() at mytest.cc:13
2 breakpoint keep y 0x0000000000400841 in main() at mytest.cc:15
(gdb)
在上面我们打了两个断点,我们通过了info b查看了这个断点的信息,下面我们来解释一下这些断点的信息。
在上面我们已经学习到了如何打断点,下面我们来学习一些如何删除或者禁用断点。我们可以使用找个 delete breakpoints或者我们简写为 d 加断点编号。下面我们来演示一下
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040082d in main() at mytest.cc:13
2 breakpoint keep y 0x0000000000400841 in main() at mytest.cc:15
(gdb) d 2
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040082d in main() at mytest.cc:13
(gdb)
那如果我们不想删掉某个断点只是想暂时禁用它。我们只是想要暂时禁用它而已我们可以使用 disable 断点编号。同意的我们想要恢复采用的是eabale 断点编号
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040082d in main() at mytest.cc:13
(gdb) disable 1
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep n 0x000000000040082d in main() at mytest.cc:13
(gdb) enable 1
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040082d in main() at mytest.cc:13
(gdb)
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040082d in main() at mytest.cc:13
(gdb)
单步调试是一个非常重要的技能必须要掌握,因为我们肯定是一步一步的调试理解程序执行的过程,下面介绍一下如何进行单步调试。在gdb当中我们可以使用 step (逐语句简写为 s),next(逐过程简写为n).下面来进行演示一下
[ksy@VM-4-17-centos scripts]$ clear
[ksy@VM-4-17-centos scripts]$ gdb -q mytest
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb) l 0
1 #include<iostream>
2 using namespace std;
3 int SumToTop(int top)
4 {
5 int sum=0;
6 for(int i=0;i<=top;i++){
7 sum+=i;
8 }
9 return sum;
10 }
(gdb)
11 int main()
12 {
13 int top=100;
14 int result=SumToTop(top);
15 cout<<"结果是"<<result<<endl;
16 return 0;
17
18 }
(gdb) b 13
Breakpoint 1 at 0x40082d: file mytest.cc, line 13.
(gdb) run
Starting program: /home/ksy/scripts/mytest
Breakpoint 1, main () at mytest.cc:13
13 int top=100;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64
(gdb) s
14 int result=SumToTop(top);
(gdb) n
15 cout<<"结果是"<<result<<endl;
(gdb) n
结果是5050
16 return 0;
(gdb)
如果我们想我们想要进入找个函数的话我们可以使用step(s)。此时如果我们进入这个函数之后我们发现我们想要跳过这个 for循环我们可以使用 until 行号就可以在一个函数内部直接执行到指定的行号。又或者我们进入到这个函数我们突然才想起来这个函数是没有问题的此时我们想要执行完这个函数此时我们可以使用finish .但是如果我们想要他不经结束还要自动往下执行到第二个断点处我们可以使用这个continue(简写c).在这里不一一演示演示一步份就可以了
[ksy@VM-4-17-centos scripts]$ gdb -q mytest
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb) l 0
1 #include<iostream>
2 using namespace std;
3 int SumToTop(int top)
4 {
5 int sum=0;
6 for(int i=0;i<=top;i++){
7 sum+=i;
8 }
9 return sum;
10 }
(gdb)
11 int main()
12 {
13 int top=100;
14 int result=SumToTop(top);
15 cout<<"结果是"<<result<<endl;
16 return 0;
17
18 }
(gdb) b 13
Breakpoint 1 at 0x40082d: file mytest.cc, line 13.
(gdb) run
Starting program: /home/ksy/scripts/mytest
Breakpoint 1, main () at mytest.cc:13
13 int top=100;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64
(gdb) s
14 int result=SumToTop(top);
(gdb) n
15 cout<<"结果是"<<result<<endl;
(gdb) n
结果是5050
16 return 0;
(gdb) q
A debugging session is active.
Inferior 1 [process 8271] will be killed.
Quit anyway? (y or n) y
[ksy@VM-4-17-centos scripts]$ clear
[ksy@VM-4-17-centos scripts]$ gdb -q mytest
Reading symbols from /home/ksy/scripts/mytest...done.
(gdb) l 0
1 #include<iostream>
2 using namespace std;
3 int SumToTop(int top)
4 {
5 int sum=0;
6 for(int i=0;i<=top;i++){
7 sum+=i;
8 }
9 return sum;
10 }
(gdb)
11 int main()
12 {
13 int top=100;
14 int result=SumToTop(top);
15 cout<<"结果是"<<result<<endl;
16 return 0;
17
18 }
(gdb) b 14
Breakpoint 1 at 0x400834: file mytest.cc, line 14.
(gdb) b 15
Breakpoint 2 at 0x400841: file mytest.cc, line 15.
(gdb) run
Starting program: /home/ksy/scripts/mytest
Breakpoint 1, main () at mytest.cc:14
14 int result=SumToTop(top);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64
(gdb) s
SumToTop (top=100) at mytest.cc:5
5 int sum=0;
(gdb) until 9
SumToTop (top=100) at mytest.cc:9
9 return sum;
(gdb) p sum
$1 = 5050
(gdb) continue
Continuing.
Breakpoint 2, main () at mytest.cc:15
15 cout<<"结果是"<<result<<endl;
(gdb)
在gdb 当中如果我们想要查看这个变量的值我们可以使用这个 display 变量名 或者使用 print(简写为p) 变量名.(display是常显示 而这个 print是这个只显示一次。如果我们想要取消常显示可以使用这个undisplay 加编号)在这里简单的演示一下。
(gdb) b 13
Breakpoint 1 at 0x40082d: file mytest.cc, line 13.
(gdb) run
Starting program: /home/ksy/scripts/mytest
Breakpoint 1, main () at mytest.cc:13
13 int top=100;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64
(gdb) p top
$1 = 0
(gdb) display top
1: top = 0
(gdb) s
14 int result=SumToTop(top);
1: top = 100
(gdb)
我们的程序在运行时如果突然挂掉了,有可能会生成这个**.core**(和信号有关铁子可以去看看我的博客).但是在我们的云服务器上默认是关闭了这个功能的。此时我们需要通过以下命令打开
ulimit -a //查看这个系统参数
ulimit -c //unlimit 把core文件设置为无限制
下面我们来将其打开
[ksy@VM-4-17-centos scripts]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7265
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 100001
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7265
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
[ksy@VM-4-17-centos scripts]$ ulimit -c 4096
[ksy@VM-4-17-centos scripts]$ ulimit -a
core file size (blocks, -c) 4096
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7265
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 100001
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7265
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
下面我们来编写一段代码来演示这个情况
int main()
{
int*ptr=NULL;
*ptr=0;//制造空指针解引用
return 0;
}
下面我们编译并运行这个程序我们发现在当前路径下多了一个.core 文件
[ksy@VM-4-17-centos scripts]$ ./mytest
Segmentation fault (core dumped)
[ksy@VM-4-17-centos scripts]$ ls
core.14308 mytest mytest.cc
程序崩溃了我们最想知道的是程序到底是那里崩溃了,这是我们最关心的下面我们通过gdb 来看出一下那么崩溃了。我们只需要使用gdb mytest core.14308既可以进行调试
[ksy@VM-4-17-centos scripts]$ gdb -q mytest core.14308
Reading symbols from /home/ksy/scripts/mytest...done.
[New LWP 14308]
Core was generated by `./mytest'.
Program terminated with signal 11, Segmentation fault.
#0 0x0000000000400627 in main () at mytest.cc:6
6 *ptr=0;//制造空指针解引用
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64
(gdb)
此时我们就知道程序原理是这里崩溃了,就能马上定位到这里。然后我们就可以分析自己的程序最后将问题解决了。
下面我们一起来看一下如何使用gdb来调试这个正在运行的程序。下面我们来说一下,当然在这里我们需要提交准备好这个测试代码
#include
#include
using namespace std;
void func2(){
for(int i=0;i<1000;i++){
sleep(1);
cout<<"i的值为:"<<i<<endl;
}
}
void func1(){
func2();
}
int main()
{
func1();
return 0;
}
然后我们使用g++进行编译注意需要带上-g选项因为我们需要调试。接下来我们需要知道这个进程的pid我们可以通过以下两个命令来获取
ps -u //查看当前用户的所有进程
ps - axj | grep mytest
下面我们使用这个 gdb -p pid或者ps -attach pid
此时我们发现刚才那个程序此时已经停下来了。那么此时程序执行到了那里了?我们可以使用
bt 查看这个函数调用堆栈
当然我们也可以使用n,s进行调试程序。其它的和上面的调试差不多
下面我们一起来看看如何调试找个多线程的找个程序,同样的我们可以提前准备下找个代码
#include
#include
#include
using namespace std;
int x=0;
int y=0;
void*func1(void*arg)
{
for(x=0;x<=1000;x++){
cout<<"x的值为:"<<x<<endl;
sleep(1);
}
pthread_exit(NULL);
}
void*func2(void*arg)
{
for(y=0;y<=1000;y++){
cout<<"y的值为:"<<y<<endl;
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,func1,NULL);
pthread_create(&tid2,NULL,func2,NULL);
cout<<"主线程开始等待第一个线程"<<endl;
pthread_join(tid1,NULL);
cout<<"主线程开始等待第二个线程"<<endl;
pthread_join(tid2,NULL);
cout<<"main over"<<endl;
}
同样的我们首先将程序编译好生成可执行程序,博主在这里给出
g++ mytest.cc -o mytest -lpthread -g
将其运行之后我们可以在另外一个窗口执行以下命令
ps -aL | grep mytest //查看轻量级线程
pstree -p 主线程id //查看主线程和新线程关系
下面我们来调试他
首先我们将断点打好,并查看断点对应信息。然在run
我们可以使用info threads 查看这个轻量级线程注意前面有*号的代表正在调试这个线程
我们发现这个打印完全是乱的。当然我们可以使用 thread 加编号切分调试的线程·这样打印非常的乱。我们可以使用
set scheduler-locking on//只允许当前线程其他线程全部挂起
set scheduler -locking off //让其他程序继续跑起来
run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令。
continue (简写c ):继续执行,到下一个断点处(或运行结束)
next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。
step (简写s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
until:当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。
until+行号: 运行至某行,不仅仅用来跳出循环
finish: 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
call 函数(参数):调用程序中可见的函数,并传递“参数”,如:call gdb_test(55)
quit:简记为 q ,退出gdb
break n (简写b n):在第n行处设置断点
(可以带上代码路径和代码名称: b OAGUPDATE.cpp:578)
b fn1 if a>b:条件断点设置
break func(break缩写为b):在函数func()的入口处设置断点,如:break cb_button
delete 断点号n:删除第n个断点
disable 断点号n:暂停第n个断点
enable 断点号n:开启第n个断点
clear 行号n:清除第n行的断点
info b (info breakpoints) :显示当前程序的断点设置情况
delete breakpoints:清除所有断点:
list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。
list 行号:将显示当前文件以“行号”为中心的前后10行代码,如:list 12
list 函数名:将显示“函数名”所在函数的源代码,如:list main
list :不带参数,将接着上一次 list 命令的,输出下边的内容。
print 表达式:简记为 p ,其中“表达式”可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么“表达式”可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。
print a:将显示整数 a 的值
print ++a:将把 a 中的值加1,并显示出来
print name:将显示字符串 name 的值
print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数
print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数
display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值。如: display a
watch 表达式:设置一个监视点,一旦被监视的“表达式”的值改变,gdb将强行终止正在被调试的程序。如: watch a
whatis :查询变量或函数
info function: 查询函数
扩展info locals: 显示当前堆栈页的所有变量
where/bt :当前运行的堆栈列表;
bt backtrace 显示当前调用堆栈
up/down 改变堆栈显示的深度
set args 参数:指定运行时的参数
show args:查看设置好的参数
info program: 来查看程序的是否在运行,进程号,被暂停的原因。