• 玩转Linux GDB & pdb


    一、GDB调试🐯

    1. watch -n指令的使用:

      # 每隔1s在终端打印一次当前系统内存使用情况
      watch -n 1 "cat /proc/meminfo"
      
      # 每隔1s查看当前系统中所有正在运行的进程
      # ps:查看系统进程; -e:显示所有进程;-f:全格式
      # ps -aux指令也用于查看进程。两者的输出风格不同,内容几乎无差别,一般推荐使用-elf
      watch -n 1 "ps -elf"
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    2. nm指令的使用:

      # 查看可执行文件或者动态链接库的符号表(函数、变量等)
      nm ***.exe 
      
      # 加上grep可以精确定位
      nm ***.exe | grep 待查名称
      
      • 1
      • 2
      • 3
      • 4
      • 5
    3. gdb指令:

      # 打开一个可视化的gdb调试终端,开始调试程序文件
      gdb -tui **.exe
      
      # 此外,也可以对正在运行中的进程,切入gdb调试
      gdb -p 进程PID
      # or
      gdb attach 进程PID
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 进入调试:
      (gdb)l 数字n     # (小写L)显示出代码从第n行开始的内容,默认显示10行;之后再次输入l,会再往后输出10行
      (gdb)set args model/yolo4_tf.xmodel 0 -t 8  # set args:设置程序启动参数
      (gdb)run    # run指令:进入主程序,立即开始执行,直到遇到断点或者程序结束
      (gdb)start  # start指令:进入主程序,停在main()主函数入口处;等待下一步指示(手动打断点等)
      
      (gdb)break n (简写b n) # 在第n行处设置断点(可以带上代码路径和代码名称: b /usr/codeprj/OAGUPDATE.cpp:578)
      
      (gdb)break func       # 在函数func()的入口处设置断点,如:break cb_button
      (gdb)info b           # 显示当前程序的断点设置情况
      
      (gdb)delete 断点号n    # 删除第n个断点
      (gdb)clear 行号n       # 清除第n行的断点,每行可能有多个不同的断点
      
      (gdb)disable 断点号n   # 暂停第n个断点
      (gdb)enable 断点号n    # 开启第n个断点
      (gdb)delete breakpoints     # 清除所有断点
      (gdb)quit        # 使用 quit 命令退出当前gdb调试进程;之后如果再次启动调试,会消除上一次调试操作中建立的所有断点
      
      (gdb)continue    # 继续执行代码,直到遇到下一个断点或者代码执行结束!
      
      # GDB 调试器共提供了3种可实现单步调试程序的方法,即使用 next、step 和 until 命令
      (gdb)next           # 当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next指令都会一步执行完
      (gdb)step           # 当step命令所执行的代码行中包含函数时,会进入该函数内部,在函数第一行代码处停止执行
      (gdb)finish/return  # 结束当前执行的函数:finish命令会执行函数到正常退出;而return命令是立即结束执行当前函数并返回,即剩下未执行的也不管了
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 打印变量:
      (gdb)print num                  # 输出或者修改指定变量或者表达式的值
      (gdb)print file::variable       # file用于指定具体的文件名,variable表示要查看的目标变量或表达式
      (gdb)print function::variable   # funciton 用于指定具体所在函数的函数名
      
      (gdb)p/x variable           # 按十六进制格式显示变量
      (gdb)p/d variable           # 按十进制显示
      (gdb)p/u variable           # 按十六进制显示无符号整型
      (gdb)p/o variable           # 按八进制显示变量
      (gdb)p/t variable           # 按二进制显示变量
      (gdb)p/c variable           # 按字符格式显示变量
      (gdb)p/f variable           # 按浮点数显示变量
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 打印数组:
      # 打印数组内容的同时显示出数组下标
      set print array-indexes on
      
      # 数组默认打印200个元素,但也可以设置具体个数
      set print elements 具体数字num   # num为0时,即不限制元素个数
      
      # 选择打印数组范围
      print *(arr+起始地址)@num
      # 打印arr+128后的6个元素
      print *(arr+128)@6
      
      print arr  # 打印arr数组
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 使用backtrace查看栈桢信息(函数调用的顺序):
        • 当我们阅读代码和查找BUG时,往往有一个烦恼。就是我们不知道函数的调用顺序。而这些函数调用顺序对应我们理解程序结构,程序运行过程是很有帮助的。关于函数的信息都存放在栈中。

          # 栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等  bt:backtrace
          (gdb)bt           # 查看当前所处函数栈帧的数据
          (gdb)bt full      # 打印当前所处函数栈帧的所有参数信息
          
           # 定位栈异常的指令:
          (gdb)frame N       # 切换到栈编号为N的栈帧  N:bt对应的栈编号
          (gdb)info frame    # 打印当前函数(N)所处的栈桢信息
          (gdb)info locals   # 打印当前函数内的局部变量
          (gdb)info args     # 查看当前函数参数
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
        • 示例:

          //frame.c
          #include 
          
          
          int sum(int n)
          {
              int ret = 0;
          
              if( n > 0 )
              {
                  ret = n + sum(n-1);
              }
          
              return ret;
          }
          
          
          int main()
          {
              int s = 0;
          
              s = sum(10);
          
              printf("sum = %d\n", s);
          
              return 0;
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 示例代码的调试过程:

            1. 设置断点:设置到递归结束标志的位置

            (gdb) start
            The program being debugged has been started already.
            Start it from the beginning? (y or n) y
            Temporary breakpoint 4 at 0x80483f9: file frame.c, line 19.
            Starting program: /home/delphi/workspace/test.out 
            Temporary breakpoint 4, main () at frame.c:19
            19       int s = 0;
            
            (gdb) break sum if n==0  # 设置sum函数中, n==0 时的数据断点。
            Breakpoint 5 at 0x80483ca: file frame.c, line 6.
            
            (gdb) info break         # 查看断点信息
            Num     Type           Disp Enb Address    What
            5       breakpoint     keep y   0x080483ca in sum at frame.c:6
             stop only if n==0
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
            • 10
            • 11
            • 12
            • 13
            • 14
            • 15

            2. 查看函数调用过程

            (gdb) continue 
            Continuing.
            
            Breakpoint 5, sum (n=0) at frame.c:6
            6       int ret = 0;
            
            # 通过栈桢号判断函数的调用顺序:上一行的函数被下一行的函数调用
            (gdb) backtrace  # 查看函数调用的顺序
            #0  sum (n=0) at frame.c:6
            #1  0x080483e5 in sum (n=1) at frame.c:10
            #2  0x080483e5 in sum (n=2) at frame.c:10
            #3  0x080483e5 in sum (n=3) at frame.c:10
            #4  0x080483e5 in sum (n=4) at frame.c:10
            #5  0x080483e5 in sum (n=5) at frame.c:10
            #6  0x080483e5 in sum (n=6) at frame.c:10
            #7  0x080483e5 in sum (n=7) at frame.c:10
            #8  0x080483e5 in sum (n=8) at frame.c:10
            #9  0x080483e5 in sum (n=9) at frame.c:10
            #10 0x080483e5 in sum (n=10) at frame.c:10
            #11 0x0804840d in main () at frame.c:21
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
            • 10
            • 11
            • 12
            • 13
            • 14
            • 15
            • 16
            • 17
            • 18
            • 19
            • 20

            3. 分析函数调用过程

            (gdb) next        # 单步执行,不进入函数
            8       if( n > 0 )
            
            (gdb) next
            13      return ret;
            
            (gdb) info args   # 查看当前函数参数的值
            n = 0
            
            (gdb) frame 7     # 切换栈编号为7的上下文中
            #7  0x080483e5 in sum (n=7) at frame.c:10
            10          ret = n + sum(n-1);
            
            (gdb) info args   # 查看栈编号为7时函数参数的值
            n = 7
            
            (gdb) info locals  # 查看当前局部变量ret的值
            ret = 0  # 计算结果
            
            (gdb) frame 0
            #0  sum (n=0) at frame.c:13
            13      return ret;
            
            (gdb) info registers  # 查看当前寄存器的值
            eax            0x0  0
            ecx            0x241be83d   605808701
            edx            0x1  1
            ebx            0x287ff4 2654196
            esp            0xbffff070   0xbffff070
            ebp            0xbffff098   0xbffff098
            esi            0x0  0
            edi            0x0  0
            eip            0x80483eb    0x80483eb <sum+39>
            eflags         0x200246 [ PF ZF IF ID ]
            cs             0x73 115
            ss             0x7b 123
            ds             0x7b 123
            es             0x7b 123
            fs             0x0  0
            gs             0x33 51
            
            (gdb) info frame           # 查看当前栈帧的详细信息
            Stack level 0, frame at 0xbffff0a0:
             eip = 0x80483eb in sum (frame.c:13); saved eip 0x80483e5
             called by frame at 0xbffff0d0
             source language c.
             Arglist at 0xbffff098, args: n=0
             Locals at 0xbffff098, Previous frame's sp is 0xbffff0a0    # 上一个栈指针地址
             Saved registers:          # 将下面值保存到寄存器中
              ebp at 0xbffff098, eip at 0xbffff09c
              
            (gdb) x /1wx 0xbffff098    # 查看ebp地址中的值
            0xbffff098: 0xbffff0c8
            
            (gdb) next
            14  }
            
            (gdb) next
            13      return ret;
            
            (gdb) info args
            n = 1
            
            (gdb) info registers        # 查看栈帧编号为1的寄存器值
            eax            0x1  1
            ecx            0x241be83d   605808701
            edx            0x1  1
            ebx            0x287ff4 2654196
            esp            0xbffff0a0   0xbffff0a0
            ebp            0xbffff0c8   0xbffff0c8
            esi            0x0  0
            edi            0x0  0
            eip            0x80483eb    0x80483eb <sum+39>
            eflags         0x200202 [ IF ID ]
            cs             0x73 115
            ss             0x7b 123
            ds             0x7b 123
            es             0x7b 123
            fs             0x0  0
            gs             0x33 51
            
            (gdb) info locals
            ret = 1      # 计算结果
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
            • 10
            • 11
            • 12
            • 13
            • 14
            • 15
            • 16
            • 17
            • 18
            • 19
            • 20
            • 21
            • 22
            • 23
            • 24
            • 25
            • 26
            • 27
            • 28
            • 29
            • 30
            • 31
            • 32
            • 33
            • 34
            • 35
            • 36
            • 37
            • 38
            • 39
            • 40
            • 41
            • 42
            • 43
            • 44
            • 45
            • 46
            • 47
            • 48
            • 49
            • 50
            • 51
            • 52
            • 53
            • 54
            • 55
            • 56
            • 57
            • 58
            • 59
            • 60
            • 61
            • 62
            • 63
            • 64
            • 65
            • 66
            • 67
            • 68
            • 69
            • 70
            • 71
            • 72
            • 73
            • 74
            • 75
            • 76
            • 77
            • 78
            • 79
            • 80
            • 81
            • 82
            • 83
    4. 调试执行异常崩溃的程序:

      • 当然,如果直接使用gdb **.exe的方式调试程序,就不用这种定位崩溃方式了!

      • 在Linux操作系统中,当程序执行发生异常崩溃时,系统可以将发生崩溃时的内存数据、调用堆栈情况等信息自动记录下载,并存储到一个文件中,该文件通常称为core文件,Linux系统所具备的这种功能又称为核心转储(core dump)。幸运的是,GDB对core文件的分析和调试提供有非常强大的功能支持,当程序发生异常崩溃时,通过GDB调试产生的core文件,往往可以更快速的解决问题。

      • 如何设置core dump文件在系统的生成存放目录:Linux-Coredump分析基础 ubuntu如何生成core文件 core文件去哪了

      • 写个代码验证一下:

        #include 
        
        int main() {
            char *a = NULL;
            *a = 2;  // 这行一看就是有BUG
            
            return 0;
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
      • 编译:

        # -g:一定不能少,否则后面用gdb调试时候不能直观地显示出BUG所在位置
        $ g++ -g -o test core.cpp 
        $ ./test
        Segmentation fault (core dumped)      <-- 发生段错误,并生成了 core 文件
        
        • 1
        • 2
        • 3
        • 4
      • 可以根据生成时间查找core dump文件:

        # 这里假设core文件被存放在/home/homework/coresave路径下
        ls /home/homework/coresave -hl | grep test
        -rw-rw-rw- 1 root      root      400K Mar 13 15:08 core.test.27725.1615619332
        -rw-rw-rw- 1 root      root      400K Mar 13 15:26 core.test.7791.1615620408
        -rw-rw-rw- 1 root      root      540K Mar 11 10:29 core.test.1868.1615429740
        -rw-rw-rw- 1 root      root      400K Mar 13 15:07 core.test.26880.1615619264
        -rw-rw-rw- 1 root      root      404K Mar  3 19:42 core.test.28802.1614771771
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      • gdb进行调试:

        # test: 要调试的可执行文件的名称
        $ gdb test /home/homework/coresave/core.test1.7791.1615620408
        
        Reading symbols from /home/zhudi/project/linux/blog/gdb/test...done.
        
        warning: core file may not match specified executable file.
        [New LWP 7791]
        Core was generated by `./test'.
        Program terminated with signal 11, Segmentation fault.
        #0  0x00000000004005bd in main () at core.cpp:5
        5	    *a = 2;
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
      • 由此可见,程序崩溃了在第五行,定位到了出现问题的代码位置。

    二、pdb调试😸

    • 注意,因为 Python 是一种解释型语言,可以通过 pdb shell 执行命令。 ipdb 是一种增强型的 pdb ,它使用IPython 作为 REPL并开启了 tab 补全、语法高亮、更好的回溯和更好的内省,同时还保留了pdb 模块相同的接口。

    • 调试python代码,可以使用ipdb调试工具

      python -m ipdb bubble.py
      
      • 1
      • 进入调试过程后,基本操作指令和gdb大同小异:

        # 显示当前行附近的11行或继续执行之前的源码显示
        $ l(ist)
        
        # 打断点
        $ b 6  # 在第六行
        
        # 一步一步执行,遇到函数就进入
        $ step
        
        # 继续执行直到当前函数的下一条语句或者 return 语句
        $ next
        
        # 继续运行,直到执行结束/出现报错/遇到断点
        $ c
        
        # 代码因某种原因停下后,打印数据;可以配合step使用
        $ p 变量名
        $ p locals()  # 打印当前时刻所有变量
        
        # 继续执行直到当前函数返回
        $ return
        
        # 停止调试
        $ q
        
        # 重启调试
        $ restart
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
    • 对于更底层的编程语言,您可能需要了解一下 gdb ( 以及它的改进版 pwndbg) 和 lldb

    • 它们都对类 C 语言的调试进行了优化,它允许您探索任意进程及其机器状态:寄存器、堆栈、程序计数器等。

  • 相关阅读:
    c++问题
    js:动态导入script脚本文件
    软件开发人员 Kubernetes 入门指南|Part 2
    11、MySql优化
    下载caj viewer查看caj论文
    【Python中图像相似性度量方法全面总结】
    从零开始C语言精讲篇7:数据的存储
    LabVIEW和NIUSRP硬件加快了认知无线电开发
    【Python】jupyter lab虚拟环境选择错误
    Prometheus 监测 RocketMQ 最佳实践
  • 原文地址:https://blog.csdn.net/qq_42091428/article/details/133775144