码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • Linux下gdb调试工具用法


    目录

    1、gdb介绍

    2、gdb用法介绍

    (1)生成带有调试信息的可运行程序

     a>判断可运行程序是否带有调试信息?

     b>判断可执行文件是否带有调试信息的两种方法:

    (2)运行调试程序:(gdb)run、start区别

     附加知识:启动带参可执行程序

    (3)源码查看:(gdb)list

    (4)运行参数查看show

    (5)断点设置

    设置断点、设置条件断点、查看断点、删除断点、禁用断点、运行某个断点:(gdb)break、break...if conditon、info b、delete break 1、disable break 1、enable break

    (6)堆栈

    a>堆栈查看命令

    b>栈帧(stack frame)介绍

    c>函数调用过程中栈区变化

    d>栈增长方向 

    (7)单步运行、继续运行:(gdb)next、continue

    (8)进入函数:(gdb)step

    (9)变量查看(变量实时显示):(gdb)watch、display 

    (10)窗口分割:(gdb)layout

    (11)变量检测、赋值:(gdb)whatis、ptype、set variable=val

    (12)查看函数调用堆栈:(gdb)backtrace、

    (13)信号用法:(gdb)Handle

    (14)终止调试:(gdb)quit

    (15)调试so文件:


    1、gdb介绍

            gdb是GNU开源组织发布的一款Linux系统下的程序调试工具。相比windows系统,虽然gdb调试工具没有windows系统下可视化调试页面(如windows的VC、VS等IDE调试页面),但gdb调试工具也具有windows调试工具几乎全部的调试功能,而且gdb调试工具还具有修复网络断点和恢复链接等功能。

            gdb工具的调试功能主要有下面四点:

            (1)启动调试程序,可以按照用户自定义的要求启动运行程序。

            (2)使调试程序在指定断点处停止。(断点可以是设置的普通断点、条件表达式)

            (3)当程序被停住时,可以检查此时程序的停止之前的运行过程。

            (4)可以改变你的程序,将一个bug产生的影响修正从而测试其他的bug。

    关于gdb的介绍可以参考gdb_百度百科 (baidu.com)

    2、gdb用法介绍

    (1)生成带有调试信息的可运行程序

    命令信息如下:

    g++ -g main.cpp -o main    //生成带有调试信息的可运行程序main,编译参数-g

     linux下g++生成可运行程序的用法总结可参考linux下g++编译C++工程demo与g++命令简述_夜雨听萧瑟的博客-CSDN博客_g++编译命令linux

     a>判断可运行程序是否带有调试信息?

    测试程序demo如下:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. cout <<"hellword" << endl;
    7. return 0;
    8. }

     生成可执行文件命令如下:

    1. g++ testNoArg.cpp -o mainNoArgNoDebug //生成可执行文件不带调试信息
    2. g++ -g testNoArg.cpp -o MainNoArgDebug //生成可执行文件带有调试信息

     结果如下:

     b>判断可执行文件是否带有调试信息的两种方法:

    方法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 文件格式

    (2)运行调试程序:(gdb)run、start区别

    ******gdb调试目标程序方式(默认以不带参可执行程序为例,程序为(1)中code)start*****

    1. 在gdb中调试程序方式种类:
    2. //第一种:直接启动。命令:gdb filenName (程序未运行,运行起来后需要run)
    3. gdb ./MainNoArgDebug //直接加载带有调试信息的可执行文件
    4. //第二种:附加进程,命令:gdb attach pid (程序已运行)
    5. ./testMain //(1)运行可执行程序
    6. ps aux | grep testMain //(2)查看进程ID
    7. gdb attach ID //(3)关联程序id,进入调试.注意需要root账户
    8. //第三种:调试coredump文件,命令:gdb filename corename
    9. 查看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. 1 #include
    2. 2 #include
    3. 3 using namespace std;
    4. 4 int main(int argc,char** argv)
    5. 5 {
    6. 6 std::cout << "argc value is " << argc << std::endl;
    7. 7 for(int i = 0; i < argc; i++)
    8. 8 {
    9. 9 std::cout << "i is " << i << " Value is " << argv[i] << std::endl;
    10. 10 }
    11. 11
    12. 12 return 0;
    13. 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*************************************

    (3)源码查看:(gdb)list

    gdb中显示源码的命令为list,需要注意的是可执行文件需要跟源码在同级目录。

    测试的demo如下:

    1. 1 #include
    2. 2 #include
    3. 3 using namespace std;
    4. 4 void ConvertFunc(int &a,int &b)
    5. 5 {
    6. 6 a = a+b;
    7. 7 b = a - b;
    8. 8 a = a-b;
    9. 9 }
    10. 10
    11. 11 int FindMaxVal(int a[],int nNum)
    12. 12 {
    13. 13 int temp = 0;
    14. 14 for(int i = 0;i < nNum;i++)
    15. 15 {
    16. 16 if(a[i] > temp)
    17. 17 {
    18. 18 temp = a[i];
    19. 19 }
    20. 20
    21. 21 }
    22. 22 return temp;
    23. 23 }
    24. 24
    25. 25 int FindMinVal(int a[],int nNum)
    26. 26 {
    27. 27 int temp = 100000;
    28. 28 for(int i = 0;i < nNum;i++)
    29. 29 {
    30. 30 if(a[i] < temp)
    31. 31 {
    32. 32 temp = a[i];
    33. 33 }
    34. 34
    35. 35 }
    36. 36 return temp;
    37. 37 }
    38. 38
    39. 39 int main()
    40. 40 {
    41. 41 int nA = 10;
    42. 42 int nB = 20;
    43. 43 cout << "Before Switch nA is " << nA << " nB is " << nB << endl;
    44. 44 ConvertFunc(nA,nB);
    45. 45 cout << "After Switch nA is " << nA << " nB is " << nB << endl;
    46. 46
    47. 47 int arrnVal[] = {3,2,90,26,50};
    48. 48 int nMaxVal = 0;
    49. 49 nMaxVal = FindMaxVal(arrnVal,sizeof(arrnVal)/sizeof(arrnVal[0]));
    50. 50 int nMinVal = 0;
    51. 51 nMinVal = FindMinVal(arrnVal,sizeof(arrnVal)/sizeof(arrnVal[0]));
    52. 52 cout << "Arr Max val is " << nMaxVal << " Min val is " << nMinVal << endl;
    53. 53 return 0;
    54. 54 }
    55. 55

    list相关命令如下(简写命令l):

    a>list num:显示已num行为中心,前后共10行代码显示在屏幕上。

    eg:

     b>list 将当前显示行及后面的代码显示在屏幕上,默认显示10行。

    eg:

     c>list -:显示当前行前面的代码。

     d>list function:显示名为function函数的源程序。

    eg:

     在查看代码过程中,按下Enter按键会自动往下切换显示代码,如下:

     因为在gdb中,执行完一个命令后,不输入任何命令,直接回车,gdb会默认执行上一个命令。

    (4)运行参数查看show

    show命令是描述gdb本身相关状态信息,通过在show后面+具体的参数可以获取对应信息。

    a>show args 显示程序启动时的参数。

    eg:

     b>show environment  打印程序运行时的环境变量

    eg:

     show的参数还有更多,如  language、paths、code-cache等,可以自行查看。

    (5)断点设置

    设置断点、设置条件断点、查看断点、删除断点、禁用断点、运行某个断点:(gdb)break、break...if conditon、info b、delete break 1、disable break 1、enable break

    使用测试的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:

    1. #include
    2. using namespace std;
    3. int nGlobal = 0;
    4. int Recursive(int n1)
    5. {
    6. if (n1 == 1)
    7. {
    8. return 1;
    9. }
    10. int tmp = n1;
    11. return Recursive(n1-1)*n1;
    12. }
    13. int Add(int a,int b)
    14. {
    15. return a + b;
    16. }
    17. int main()
    18. {
    19. #if 0
    20. for(int i = 0; i < 10; i++)//b 7 if i == 5不会生效,因为第7行i才声明
    21. { //b 8 if i == 5 ok
    22. std::cout << " i: " << i << std::endl;
    23. }
    24. #endif
    25. nGlobal = 10;
    26. Recursive(6);
    27. std::cout << Add(3,5) << std::endl;
    28. return 0;
    29. }

     编译命令:

    g++ -g main.cpp -o main

     调试如下:

     ***查看断点 info break(简写info b)

            eg:如上面c>中的图。

                    设置自动显示变量:display

                    继续运行到下一个断点命令:continue     

      ***删除断点: delete breakpoints Num

            eg:   

    关于断点设置的其他命令不再演示。

    (6)堆栈

    a>堆栈查看命令

    堆栈查看命令backtrace,简写bt。eg:

    1. (gdb)bt //打印当前被调用函数0号帧信息,即栈顶信息。
    2. (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博客

    b>栈帧(stack frame)介绍

    程序的功能是通过函数间的调用来实现的,栈帧记录了函数间的调用过程,该记录包括函数执行过程中的数据传递、局部变量的分配和释放的一系列环境数据。每一次函数调用会在栈上分配一个新的栈帧,在这次函数调用结束时释放其空间。

    c>函数调用过程中栈区变化

    函数调用过程中栈区的变化可以参考:C++ 函数调用过程中栈区的变化——(栈帧、esp、ebp)_c++函数栈_JMW1407的博客-CSDN博客

    d>栈增长方向 

         大家都知道栈的增长方向是从高地址到低地址,栈是后进先出的机制,栈有栈顶(负责数据的压栈和出栈)和栈底,只能从栈顶端操作元素。

    使用下面代码验证以下栈的增长方向。

    1. #include
    2. void func()
    3. {
    4. int a = 10;
    5. int b = 20;
    6. printf("&a is = %p\n",&a);
    7. printf("&b is = %p\n",&b);
    8. }
    9. int main()
    10. {
    11. printf("############ MAIN ###########\n");
    12. printf("&main is = %p\n",main);
    13. int m = 1;
    14. int n =2;
    15. printf("&m is = %p\n",&m);
    16. printf("&n is = %p\n",&n);
    17. printf("&func is = %p\n",func);
    18. printf("\n############ func ###########\n");
    19. func();
    20. return 0;
    21. }

    运行结果如下:

     

     由上面的运行结果中main函数地址与func函数地址可知,func函数地址低于main函数地址,同一个函数内部局部变量地址是从低地址向高地址增长;main函数内部局部变量的地址是高于被调用函数func局部变量地址。对于该demo可以看出,函数调用过程中栈帧的增长方向是从高地址向低地址;通过查找资料,函数内部局部变量的地址增长方向跟系统和编译有关。

      关于栈增长方向也可参考:

    栈中分配是从高地址往低地址,为什么局部变量的地址却是按定义顺序逐渐增大?从操作系统上进程的地址空间分布以及不同操作系统的不同、以及不同的数据结构来说明这个事情?_对于c++,栈是从高到低分配的,为什么局部变量是从低到高分配的_GK小卜的博客-CSDN博客

    堆、栈的地址高低? 栈的增长方向? - 知乎 (zhihu.com)

    (7)单步运行、继续运行:(gdb)next、continue

    (8)进入函数:(gdb)step

    (9)变量查看(变量实时显示):(gdb)watch、display 

    (10)窗口分割:(gdb)layout

    (11)变量检测、赋值:(gdb)whatis、ptype、set variable=val

    (12)查看函数调用堆栈:(gdb)backtrace、

    (13)信号用法:(gdb)Handle

    (14)终止调试:(gdb)quit

    (15)调试so文件:

            调试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博客

    后续更新中...

  • 相关阅读:
    springboot redisTemplate.opsForValue().setIfAbsent返回null原理
    面试题 16.10. 生存人数-差分数组
    DO280管理应用部署--pod调度控制
    【Java并发编程九】同步控制
    spring boot酒店会员点餐系统毕业设计源码072005
    2023年,PMP的知识是否过时了?
    备战蓝桥杯Day22 - 计数排序
    Util应用框架基础(二) - 对象到对象映射(AutoMapper)
    使用 Amazon Bedrock 和 RAG 构建 Text2SQL 行业数据查询助手
    k8s核心操作_构建一键上云001_K8S容器+harbor容器本地化搭建云原生环境_一键部署SpringCloud Alibaba 分布式微服务+Vue前端---分布式云原生部署架构搭建030
  • 原文地址:https://blog.csdn.net/hanxiaoyong_/article/details/126548015
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号