1.基本命令操作
2.调试方式启动运行无参程序
以下是linux下GDB调试的一个实例,先给出一个示例用的小程序,C语言代码:
main.c
#include
void Print(int i){
printf("hello,程序猿编码 %d\n", i);
}
int main(int argc, char const *argv[]){
int i = 0;
for (i = 1; i < 3; i++){
Print(i);
}
return 0;
}
编译:
gcc -g main.c -o main
gdb
file main //载入被调试程序
//输出 Reading symbols from /home/minger/share/tencent/gdb/main…done. 表示已经加载成功。
r //运行
3.调试启动带参程序
假设有以下程序,启动时需要带参数:
#include
int main(int argc, char const *argv[]){
if (1 >= argc){
printf("usage:hello name\n");
return 0;
}
printf("hello,程序猿编码 %s\n", argv[1]);
return 0;
}
gcc -g test.c -o test
//这种情况如何启动调试呢?只需要r的时候带上参数即可。
r min //min可以为任意参数
4.调试core文件
Core Dump:Core的意思是内存,Dump的意思是扔出来,堆出来(段错误)。开发和使用Unix程序时,有时程序莫名其妙的down了,却没有任何的提示(有时候会提示core dumped),这时候可以查看一下有没有形如core.进程号的文件生成,这个文件便是操作系统把程序down掉时的内存内容扔出来生成的, 它可以做为调试程序的参考,能够很大程序帮助我们定位问题。那怎么生成Core文件呢?
生成Core方法
产生coredump的条件,首先需要确认当前会话的ulimit –c,若为0,则不会产生对应的coredump,需要进行修改和设置。
即便程序core dump了也不会有core文件留下。我们需要让core文件能够产生,设置core大小为无限:
ulimit -c unlimited
更改core dump生成路径
因为core dump默认会生成在程序的工作目录,但是有些程序存在切换目录的情况,导致core dump生成的路径没有规律,
所以最好是自己建立一个文件夹,存放生成的core文件。
我建立一个 /data/coredump 文件夹,在根目录data里的coredump文件夹。
调用如下命令:
echo /data/coredump/core.%e.%p> /proc/sys/kernel/core_pattern
将更改core文件生成路径,自动放在这个/data/coredump文件夹里。
%e表示程序名, %p表示进程id
参考代码:
/*
#include
int main(int argc, char const *argv[]){
if (1 >= argc){
printf("usage:hello name\n");
return 0;
}
printf("hello,程序猿编码 %s\n", argv[1]);
return 0;
}
*/
#include
int main(int argc, char const *argv[])
{
int i = 0;
scanf("%d",i);
printf("hello,程序猿编码 %d\n",i );
return 0;
}
5.断点设置与查看源码
5.1通过行号设置断点
break [行号]
break 行号,断点设置在该行开始处,注意:该行代码未被执行
如果你的程序是用c或者c++写的,那么你可以使用“文件名:行号”的形式设置断点。示例如下:
//test.c
#include
void judge_sd(int num){
if ((num & 1) == 0){
printf("%d is even\n",num);
return;
}else{
printf("%d is odd\n",num);
return;
}
}
int main(int argc, char const *argv[]){
judge_sd(0);
judge_sd(1);
judge_sd(4);
return 0;
}
编译:
gcc -g test.c -o test
gdb test
示例中的(gdb) b test.c:18
使用r命令执行脚本时,当运行到18行时就会暂停。注意:该行代码未被执行
5.2通过函数设置断点
break [函数名]
break 函数名,断点设置在该函数的开始处,断点所在行未被执行:
同样可以将断点设置在函数处:
b judge_sd
5.3设置条件断点
如果按上面的方法设置断点后,每次执行到断点位置都会暂停。有时候非常讨厌。我们只想在指定条件下才暂停。这时候根据条件设置断点就有了用武之地。设置条件断点的形式,就是在设置断点的基本形式后面增加 if条件。示例如下:
break test.c:6 if num>0
当在num>0时,程序将会在第6行断住。
5.4查看断点
info breakpoints
可以使用info breakpoints查看断点的情况。包含都设置了那些断点,断点被命中的次数等信息。示例如下:
它将会列出所有已设置的断点,每一个断点都有一个标号,用来代表这个断点。
5.5删除断点
delete breakpoint
对于无用的断点我们可以删除。删除的命令格式为 delete breakpoint 断点编号。info breakpoint命令显示结果中的num列就是编号。删除断点的示例如下:
5.6查看源码
断点设置完后,当程序运行到断点处就会暂停。暂停的时候,我们可以查看断点附近的代码。查看代码的子命令是list,缩写形式为l。
5.7指定行号查看代码
list first,last
例如,要列出6到21行之间的源码:
5.8列出指定文件的源码
前面执行l命令时,默认列出test.c的源码,如果想要看指定文件的源码呢?可以
list 【文件名加行号或函数名】
6.单步调试与查看变量
断点附近的代码你了解后,这时候你就可以使用单步执行一条一条语句的去执行。可以随时查看执行后的结果。接下来你可能会想知道程序运行的一些情况,就需要查看变量的值。下面介绍单步调试与设置变量。
单步调试
居然是调试代码,还是老规矩,先上代码:
//test.c
#include
void judge_sd(int num){
if ((num & 1) == 0){
printf("%d is even\n",num);
return;
}else{
printf("%d is odd\n",num);
return;
}
}
int main(int argc, char const *argv[]){
judge_sd(0);
judge_sd(1);
judge_sd(4);
return 0;
}
编译:
gcc -g test.c -o test
程序的功能比较简单,这里不多做解释。断点附近的代码你了解后,这时候你就可以使用单步执行一条一条语句的去执行。可以随时查看执行后的结果。单步执行有两个命令,分别是step和next。我们可能打了多处断点,或者断点打在循环内,这个时候,可以使用continue命令。这三个命令的区别在于:
next命令(可简写为n)用于在程序断住后,继续执行下一条语句。
step命令(可简写为s),它可以单步跟踪到函数内部。
continue命令(可简写为c)或者fg,它会继续执行程序,直到再次遇到断点处。
6.1单步进入-step
step 一条语句一条语句的执行。它有一个别名,s。它可以单步跟踪到函数内部。
先用list(可简写为l)将源码列出来,例如:
先启动调试,然后把源码列出来。
从上面的过程可以看到,在5行设置断点,运行程序,可见,step命令进入到了被调用函数中judge_sd。使用step命令也会在这个方法中一行一行的单步执行。但是如果没有该函数源码,需要跳过该函数执行,可使用finish命令,继续后面的执行。
6.2单步执行-next
next命令示例:
next命令(可简写为n)用于在程序断住后,继续执行下一条语句。上面的信息在5行处打断点,然后运行到6行,然后输入 运行n 2,则会单步执行两行。可见,使用next命令只会在本方法中单步执行。
根据上面的信息可以看到,使用skip之后,将不会进入judge_sd函数。好处就是skip可以在step时跳过一些不想关注的函数或者某个文件。
如果想删除skip,使用skip delete [num] 。
6.4查看变量
现在你已经会设置断点,查看断点附近的代码,并可以单步执行和继续执行。接下来你可能会想知道程序运行的一些情况,如查看变量的值。print命令正好满足了你的需求。以帮助我们进一步定位问题。
print[变量名]
print(可简写为p)打印变量内容。示例代码如下:
//test.c
#include
#include //malloc,free,rand
int main(int argc, char const *argv[])
{
int input;
int i ;
printf("Please enter the length of the string:");
scanf("%d",&input);
char *buf = (char *) malloc(input + 1);//字符最后包含'\0'
if (buf == NULL)
{
printf("malloc failed!\n");
return -1;
}
//随机生成字符串
for ( i = 0; i < input; i++)
{
buf[i] = rand()%26 +'a';
}
buf[i] = '\0';
printf("A randomly generated string: %s\n",buf);
free(buf);
return 0;
}
gcc -g test.c -o test
先用list(可简写为l)将源码列出来,例如:
print命令的简写形式为p,使用它打印出变量的值。
打印出的变量i的值为80。
当然,多个函数或者多个文件会有同一个变量名,这个时候可以在前面加上文件名或者函数名来区分:
p 'testfile.c'::i
p 'sum'::i
在看看指针。
注意到了没有,如果使用上面的方式打印指针指向的内容,那么打印出来的只是指针地址而已。那怎么打印出指针指向的内容呢?
需要解引用,如下:
仅仅使用*只能打印第一个值,如果要打印多个值,后面跟上@并加上要打印的长度。
或者@后面跟上变量值:如下:
另外值得一提的是,$可表示上一个变量,在调试链表时时经常会用到的,它有next成员代表下一个节点,则可使用下面方式不断打印链表内容,举例:
p *linkNode #这里显示linkNode节点内容
p *$.next #这里显示linkNode节点下一个节点的内容
6.5设置变量
使用print命令查看了变量的值,如果感觉这个值不符合预期,想修改下这个值,再看下执行效果。这种情况下,我们该怎么办呢?通常情况下,我们会修改代码,再重新执行代码。使用gdb的set命令,一切将变得更简单。
set命令可以直接修改变量的值。
6.6设置观察点
设置观察点的作用就是:当被观察的变量发生变化后,程序就会暂停执行,并把变量的原值(Old)和新值(New)都会显示出来。设置观察点的命令是watch。
watch num
这个时候,让程序继续运行,如果num的值发生变化,则会打印相关内容,如:
Hardware watchpoint 3: num
Old value = 1
New value = 10
总结
通过上面的例子演示,我相信读者已经对于通过GDB调试C/C++程序有了基本的理解,如果你想获取更多的调试技巧请参考官方网站的GDB调试手册,还有GDB官方网站的手册。
参考:GDB TutorialA Walkthrough with Examples
GDB调试指南(入门,看这篇够了)_程序猿编码的博客-CSDN博客
Linux C ————16、gdb调试器__io_vfscanf_FLy_鹏程万里的博客-CSDN博客