目录
(7)单步运行、继续运行:(gdb)next、continue
(9)变量查看(变量实时显示):(gdb)watch、display
(11)变量检测、赋值:(gdb)whatis、ptype、set variable=val
gdb是GNU开源组织发布的一款Linux系统下的程序调试工具。相比windows系统,虽然gdb调试工具没有windows系统下可视化调试页面(如windows的VC、VS等IDE调试页面),但gdb调试工具也具有windows调试工具几乎全部的调试功能,而且gdb调试工具还具有修复网络断点和恢复链接等功能。
gdb工具的调试功能主要有下面四点:
(1)启动调试程序,可以按照用户自定义的要求启动运行程序。
(2)使调试程序在指定断点处停止。(断点可以是设置的普通断点、条件表达式)
(3)当程序被停住时,可以检查此时程序的停止之前的运行过程。
(4)可以改变你的程序,将一个bug产生的影响修正从而测试其他的bug。
关于gdb的介绍可以参考gdb_百度百科 (baidu.com)
命令信息如下:
g++ -g main.cpp -o main //生成带有调试信息的可运行程序main,编译参数-g
linux下g++生成可运行程序的用法总结可参考linux下g++编译C++工程demo与g++命令简述_夜雨听萧瑟的博客-CSDN博客_g++编译命令linux
测试程序demo如下:
- #include
- #include
- using namespace std;
-
- int main()
- {
- cout <<"hellword" << endl;
- return 0;
- }
生成可执行文件命令如下:
- g++ testNoArg.cpp -o mainNoArgNoDebug //生成可执行文件不带调试信息
- g++ -g testNoArg.cpp -o MainNoArgDebug //生成可执行文件带有调试信息
结果如下:
方法1:
a> 输入gdb。
b>输入file mainNoArgNoDebug
没有调试信息的结果如下:
有调试信息的结果如下:
可以看出,没有调试信息的会提示:No debugging symbols found in....,有调试信息的会提示Reading symbols from ....。
方法2:
使用readelf命令来显示elf格式的目标文件格式。(elf文件有三种类型:a>可重定位文件:用户和其他目标一起创建可执行文件或者共享目标文件,如lib*.a文件。b>可执行文件:生成进程映像,载入内存运行,如可执行文件*.out。c> 共享目标文件:用于和其他共享目标文件或者可重定位文件一起生成可执行文件,如*.so。)
readelf的选项参数有-a,-h,-l,-S,-g,-t,-s,-e,-n,等,此处使用参数-S,查看elf文件的节头信息,节头是对可执行程序中代码段和数据位置和大小的描述。
没有调试信息结果如下:
有调试信息的结果如下:
关于readelf命令用法可参考readelf命令_Linux readelf 命令用法详解:用于显示elf格式文件的信息 (ailinux.net)
关于elf文件节头格式描述如下:ELF文件格式第三讲,节头(section header) (icode9.com)
关于elf文件格式内容参考:ELF(文件格式)_百度百科 (baidu.com)
ELF文件格式的详解_pingxiaozhao的博客-CSDN博客_elf 文件格式
******gdb调试目标程序方式(默认以不带参可执行程序为例,程序为(1)中code)start*****
- 在gdb中调试程序方式种类:
-
- //第一种:直接启动。命令:gdb filenName (程序未运行,运行起来后需要run)
- gdb ./MainNoArgDebug //直接加载带有调试信息的可执行文件
-
- //第二种:附加进程,命令:gdb attach pid (程序已运行)
- ./testMain //(1)运行可执行程序
- ps aux | grep testMain //(2)查看进程ID
- gdb attach ID //(3)关联程序id,进入调试.注意需要root账户
-
-
- //第三种:调试coredump文件,命令:gdb filename corename
- 查看linux是否开启了产生dump文件的信息,命令:ulimit -a
-
上面的第一种:调试未启动的程序方法,还有另一种:先加载gdb,再将待调试程序加载进来。
优点:可以file多次指定不同文件,重新启动不同的可执行文件
gdb //(1)加载gdb
file MainNoArgDebug //(2)加载带有调试信息可运行文件
注:上面第二种和第三种调试程序的方式会在其他博客介绍。本文主要以第一种为主。
关于这三种的有关介绍也可参考:gdb 笔记(02)— gdb 调试执行(启动调试、添加参数、附加到进程、调试 core 文件)-pudn.com
******gdb调试目标程序方式(默认以不带参可执行程序为例,程序为(1)中code)end*****
上面的第一种方式gdb filenName命令执行后程序是未运行的,需要使用run或start命令进行运行,它们的介绍如下:
run命令运行调试程序:运行程序,直到运行程序结束,或遇到断点停止(简写命令r)。运行如下:
start命令:启动可执行程序,运行到main函数的第一句出停止,如下:
************************************gdb带参程序方法start************************************
demo的代码如下:
-
- 1 #include
- 2 #include
- 3 using namespace std;
- 4 int main(int argc,char** argv)
- 5 {
- 6 std::cout << "argc value is " << argc << std::endl;
- 7 for(int i = 0; i < argc; i++)
- 8 {
- 9 std::cout << "i is " << i << " Value is " << argv[i] << std::endl;
- 10 }
- 11
- 12 return 0;
- 13 }
demo 代码简单回顾:
argc参数:统计参数格式,其中记录了命令行中命令和参数的个数;
argv:指向字符指针的指针,实质时数组。里面存的数据顺序是:命令,参数1,参数2,,,
关于main函数详细可参考main函数_百度百科 (baidu.com)
//非gdb下带参程序运行
a>生成可执行文件testArgDebug,命令: g++ -g testArgDebug.cpp -o testArgDebug
b>输入带参并运行代码,命令: ./testArgDebug 12 34
结果如下:
//gdb下带参程序运行
gdb下带参程序的运行有以下两种方式:
a> 先设置参数,再利用run运行程序。命令:set args
该命令需要注意:如果没有设置参数,则与前一个命令使用的参数相同,该命令详细介绍可参考:
参数(使用 GDB 调试) (sourceware.org)
该方法启动程序的过程如下:
b> 在run时添加参数,启动调试程序过程如下:
上面的两种方式都可以
************************************gdb带参程序方法end*************************************
gdb中显示源码的命令为list,需要注意的是可执行文件需要跟源码在同级目录。
测试的demo如下:
- 1 #include
- 2 #include
- 3 using namespace std;
- 4 void ConvertFunc(int &a,int &b)
- 5 {
- 6 a = a+b;
- 7 b = a - b;
- 8 a = a-b;
- 9 }
- 10
- 11 int FindMaxVal(int a[],int nNum)
- 12 {
- 13 int temp = 0;
- 14 for(int i = 0;i < nNum;i++)
- 15 {
- 16 if(a[i] > temp)
- 17 {
- 18 temp = a[i];
- 19 }
- 20
- 21 }
- 22 return temp;
- 23 }
- 24
- 25 int FindMinVal(int a[],int nNum)
- 26 {
- 27 int temp = 100000;
- 28 for(int i = 0;i < nNum;i++)
- 29 {
- 30 if(a[i] < temp)
- 31 {
- 32 temp = a[i];
- 33 }
- 34
- 35 }
- 36 return temp;
- 37 }
- 38
- 39 int main()
- 40 {
- 41 int nA = 10;
- 42 int nB = 20;
- 43 cout << "Before Switch nA is " << nA << " nB is " << nB << endl;
- 44 ConvertFunc(nA,nB);
- 45 cout << "After Switch nA is " << nA << " nB is " << nB << endl;
- 46
- 47 int arrnVal[] = {3,2,90,26,50};
- 48 int nMaxVal = 0;
- 49 nMaxVal = FindMaxVal(arrnVal,sizeof(arrnVal)/sizeof(arrnVal[0]));
- 50 int nMinVal = 0;
- 51 nMinVal = FindMinVal(arrnVal,sizeof(arrnVal)/sizeof(arrnVal[0]));
- 52 cout << "Arr Max val is " << nMaxVal << " Min val is " << nMinVal << endl;
- 53 return 0;
- 54 }
- 55
list相关命令如下(简写命令l):
a>list num:显示已num行为中心,前后共10行代码显示在屏幕上。
eg:
b>list 将当前显示行及后面的代码显示在屏幕上,默认显示10行。
eg:
c>list -:显示当前行前面的代码。
d>list function:显示名为function函数的源程序。
eg:
在查看代码过程中,按下Enter按键会自动往下切换显示代码,如下:
因为在gdb中,执行完一个命令后,不输入任何命令,直接回车,gdb会默认执行上一个命令。
show命令是描述gdb本身相关状态信息,通过在show后面+具体的参数可以获取对应信息。
a>show args 显示程序启动时的参数。
eg:
b>show environment 打印程序运行时的环境变量
eg:
show的参数还有更多,如 language、paths、code-cache等,可以自行查看。
使用测试的demo为(3)中的代码。
注:在启动时可以设置不显示gdb相关版本信息,命令为 gdb -q a.out
***设置断点break(简写b)
a>break function(type,type) /break class::function在函数function处设置断点.
eg:
b>break lineNum 在代码指定的行数设置断点
eg
c>设置条件断点,可以在行数处或者函数名function处设置条件断点,语法如下:
break line-or-function if expression //expression为表达式,值为true、false
eg:
注意条件变量的设置必须时该变量生命周期内,否则条件变量不会生效!
eg2:
- #include
-
- using namespace std;
- int nGlobal = 0;
- int Recursive(int n1)
- {
- if (n1 == 1)
- {
- return 1;
-
- }
- int tmp = n1;
- return Recursive(n1-1)*n1;
-
- }
- int Add(int a,int b)
- {
- return a + b;
- }
-
-
-
- int main()
- {
- #if 0
- for(int i = 0; i < 10; i++)//b 7 if i == 5不会生效,因为第7行i才声明
- { //b 8 if i == 5 ok
- std::cout << " i: " << i << std::endl;
- }
- #endif
- nGlobal = 10;
- Recursive(6);
- std::cout << Add(3,5) << std::endl;
-
- return 0;
- }
编译命令:
g++ -g main.cpp -o main
调试如下:
***查看断点 info break(简写info b)
eg:如上面c>中的图。
设置自动显示变量:display
继续运行到下一个断点命令:continue
***删除断点: delete breakpoints Num
eg:
关于断点设置的其他命令不再演示。
堆栈查看命令backtrace,简写bt。eg:
- (gdb)bt //打印当前被调用函数0号帧信息,即栈顶信息。
- (gdb)bt n //打印栈顶n层的栈信息
查看栈中某一层的信息,命令为frame,简写为f,eg:
(gdb)f n //查看帧中第n层的信息
代码如(5)中的eg2,调试demo如下:
上面的b 7 if n1 == 3,是在第7行处,n1 == 3时,触发断点。
堆栈信息的查看也可参考:GDB入门教程之查看函数调用堆栈_gdb查看调用栈_椰子1694的博客-CSDN博客
程序的功能是通过函数间的调用来实现的,栈帧记录了函数间的调用过程,该记录包括函数执行过程中的数据传递、局部变量的分配和释放的一系列环境数据。每一次函数调用会在栈上分配一个新的栈帧,在这次函数调用结束时释放其空间。
函数调用过程中栈区的变化可以参考:C++ 函数调用过程中栈区的变化——(栈帧、esp、ebp)_c++函数栈_JMW1407的博客-CSDN博客
大家都知道栈的增长方向是从高地址到低地址,栈是后进先出的机制,栈有栈顶(负责数据的压栈和出栈)和栈底,只能从栈顶端操作元素。
使用下面代码验证以下栈的增长方向。
- #include
-
- void func()
- {
- int a = 10;
- int b = 20;
- printf("&a is = %p\n",&a);
- printf("&b is = %p\n",&b);
- }
-
- int main()
- {
- printf("############ MAIN ###########\n");
- printf("&main is = %p\n",main);
- int m = 1;
- int n =2;
- printf("&m is = %p\n",&m);
- printf("&n is = %p\n",&n);
- printf("&func is = %p\n",func);
-
- printf("\n############ func ###########\n");
- func();
- return 0;
- }
运行结果如下:
由上面的运行结果中main函数地址与func函数地址可知,func函数地址低于main函数地址,同一个函数内部局部变量地址是从低地址向高地址增长;main函数内部局部变量的地址是高于被调用函数func局部变量地址。对于该demo可以看出,函数调用过程中栈帧的增长方向是从高地址向低地址;通过查找资料,函数内部局部变量的地址增长方向跟系统和编译有关。
关于栈增长方向也可参考:
堆、栈的地址高低? 栈的增长方向? - 知乎 (zhihu.com)
调试so文件,可以把断点设置在main函数处,然后r运行,当函数运行停在main函数处时,so的文件调试信息已加载完毕,此时可以设置断点。注意生成的so文件必须带有调试信息,即-g参数编译。
so调试的步骤也可参考:
GDB调试中 如何在so共享库中打断点、保存断点以及加载断点_gdb在so中加断点_globbo的博客-CSDN博客
附加知识
gdb的官网信息见:Top (Debugging with GDB) (sourceware.org)
gdb文档下载地址:Documentation for GDB version 12.1 (sourceware.org)
下载Top(Debugging withGDB)下的gdb.pdf
gdb的环境下载: Download GDB (sourceware.org)
gdb断点设置:gdb 笔记(03)— 某一行设置断点、为函数(单个唯一函数、多个同名函数、使用正则)设置断点、设置条件断点、设置临时断点_gdb breakpoint_wohu007的博客-CSDN博客
后续更新中...