[转帖]gdb进阶调试技巧

gdb,进阶,调试,技巧 · 浏览次数 : 0

小编点评

info sharedlibrary #显示共享库(gdb) info sharedlibrary From To Syms Read Shared Object Library0x00007ffff7dd5050 0x00007ffff7df4854 Yes (*) /lib64/ld-linux-x86-64.so.20x00007ffff7aceb60 0x00007ffff7b84bb2 Yes (*) /lib64/libstdc++.so.60x00007ffff76c9510 0x00007ffff77687ca Yes (*) /lib64/libm.so.60x00007ffff74a7dc0 0x00007ffff74b8a25 Yes (*) /lib64/libgcc_s.so.10x00007ffff7104900 0x00007ffff724ef0f Yes (*) /lib64/libc.so.6gdb调用函数void func() { xxx; }b funccall func() // gdb 直接调用这个函数gdb调用class的成员函数// todostrip命令 (这个和gdb关系不大)strip命令可以极大的降低可执行文件的体积,其实就是去掉符号表和调试信息等。当gdb的时候,拷贝strip下来的.debug文件到需要gdb的机器上,然后进行gdb attach。这里gdb上来以后,会自动找到对应的.debug文件。

正文

https://www.jianshu.com/p/9bdaa0644dba

 

整理一下在linux下C/C++用gdb工具debug一些提高效率的操作。基本的gdb操作就不在这里赘述了。

  • 打印各种变量
    x 命令
    在gdb中可以使用x命令,来打印内存中的值。具体的格式是x/nfu addr。 含义为以f格式打印从addr开始的n个长度单元为u的内存值。
    1. n表示打印的n个u的长度
    2. f 表示打印格式,u,d表示10进制的无符号和有符号数; x表示16进制;t表示二进制
    3. u表示单元长度,b表示一个byte,w表示4个byte
    int main()
    {
      int val = 0xa;
      
      char buff[1024];
      memset(buff, 0, sizeof(1024));
    
      char name[]="hello world";
      strcpy(buff, name);
      
      int arr[10] = {0};
      arr[0] = 0xa;
      arr[1] = 10;
      arr[2] = 20;
    
      return 0;
    }
    
    (gdb) x/16db &arr
    0x7fffffffe010:   10  0   0   0   10  0   0   0
    0x7fffffffe018:   20  0   0   0   0   0   0   0
    
    (gdb) x/16sb &buff
    0x7fffffffe050:    "hello world"
    0x7fffffffe05c:    "\177"
    0x7fffffffe05f:    ""
    0x7fffffffe060:    "<纵?\177"
    0x7fffffffe067:    ""
    0x7fffffffe068:    "X\227\177"
    0x7fffffffe06f:    ""
    0x7fffffffe070:    "\020暂?\177"
    0x7fffffffe077:    ""
    0x7fffffffe078:    ""
    0x7fffffffe079:    ""
    0x7fffffffe07a:    ""
    0x7fffffffe07b:    ""
    0x7fffffffe07c:    "\001"
    0x7fffffffe07e:    ""
    0x7fffffffe07f:    ""
    

  • 日志打印到某个文件
    set pagination off
    set logging file a.log
    set logging overwrite
    set logging on
    

  • -tui 图形界面选项
    gdb a.out -tui # gdb tui 启动
    gdb attach pid -tui # gdb tui attach


     

  • gdb定义函数
    在开发中有时会遇到需要看一个很大的数组,里面有没有被写坏的元素。如果数组中还有嵌套其他结构体,每个依次打印出来非常麻烦。这里可以使用gdbinit,定义一个函数,依次遍历数组,打印超过某个值的元素。比如:
    const int MAX_LEN = 1024;
    
    struct DEBUG_DETAIL_INFO{
      int id;
      int extra_info;
    };
    
    struct DEBUG_ARR{
      int num;
      DEBUG_DETAIL_INFO info_arr[MAX_LEN];
    };
    
    int main()
    {
      DEBUG_ARR arr;
      init_arr(100, arr);
      
      // todo
      return 0;
    }
    

gdb调用函数有两种方式:

  1. gdb启动时,会在当前目录下查找 .gdbinit 文件
  2. gdb在运行时,在命令行输入 source xx.gdb文件 来加载

gdb启动的时候,会在当前目录或home目录自动加载gdbinit脚本。在脚本中定义gdb函数。(或者gdb attach上去。)

define print_if_my_arr
  printf "argc %d, arg %d \n", $argc, $arg0

  set $num = arr.num
  printf "print_func num %d \n", $num

  set $idx = 0
  while $idx < $num
      if arr.info_arr[$idx].id >= $arg0
          printf "idx %d id %d \n", $idx, arr.info_arr[$idx].id
      end
      set $idx = $idx + 1
  end
end

document print_if_my_arr
  example : print_if_my_arr 10
  desc    : print DEBUG_ARR ele if val >= 10
end

 

 

gdb实测一把
 

gdbinit文件同理也可以插入其他命令,比如各种让输出比较直观的命令等:

set print pretty on
set print object on

gdb定义的常用函数可以放到一个文件里面,需要的时候通过source命令加载。

define mybt
    set logging file output_bt.log
    set logging on
    bt
    set logging off
end

使用的时候

gdb attach xxxx
source xxx/path/xxx.gdb
mybt

  • gdb 条件断点
    # 文件名 + 行号的条件断点
    b xxx.cpp:20 if value == 100   #xx.cpp 行号20 如果value == 100 触发断点
    
    # 包含一些简单计算的断点
    (gdb) p 100
    $2 = 100
    (gdb) b test1.cpp:8 if i + $2 == 130
    

  • ignore 命令
    断点触发被忽略xx次
    ignore <break_list> count
     

  • command命令
    command <break_list>

     

    触发断点后自动执行command里面定义的命令
     

 

 

command的命令另一个妙用
 

  • gdb return
    进入某个函数执行,不想执行里面的逻辑,直接gdb return 出去。里面的逻辑就不会执行了。
 

  • gdb gcore
    gcore命令可以从当前环境导出成为一个core文件,供日后分析。


     

  • handle SIGPIPE nostop
    为了gdb不被信号断开,可以屏蔽掉各种pipe信号。

  • gdb -ex 参数
    生产环境操作的时候可以使用

    gdb -ex "bt" -batch -p pid
    

     

     

    跳出死循环
     
     

  • display 命令

     

    对于debug需要关注的变量名,可以使用display打印出来。每次gdb操作的时候,都会显示出来。示意图如下:
     

  • info命令
    info sharedlibrary  #显示共享库
    (gdb) info sharedlibrary 
    From                To                  Syms Read   Shared Object Library
    0x00007ffff7dd5050  0x00007ffff7df4854  Yes (*)     /lib64/ld-linux-x86-64.so.2
    0x00007ffff7aceb60  0x00007ffff7b84bb2  Yes (*)     /lib64/libstdc++.so.6
    0x00007ffff76c9510  0x00007ffff77687ca  Yes (*)     /lib64/libm.so.6
    0x00007ffff74a7dc0  0x00007ffff74b8a25  Yes (*)     /lib64/libgcc_s.so.1
    0x00007ffff7104900  0x00007ffff724ef0f  Yes (*)     /lib64/libc.so.6
    

  • gdb调用函数
    void func() {  xxx; }
    
    b func
    call func()  // gdb 直接调用这个函数
    
    gdb调用class的成员函数
    // todo
    

  • strip命令 (这个和gdb关系不大)
    strip命令可以极大的降低可执行文件的体积,其实就是去掉符号表和调试信息等。当gdb的时候,拷贝strip下来的.debug文件到需要gdb的机器上,然后进行gdb attach。这里gdb上来以后,会自动找到对应的.debug文件。
     cd xxxx_app_bin_path
     objcopy --only-keep-debug xxxx_app ./.debug/xxxx.debug
     strip --strip-debug --strip-unneeded xxxx_app
     objcopy --add-gnu-debuglink=./.debug/xxxx_app.debug xxxx_app
    

GDB的基本原理

gdb有两种跟踪进程的方法,第一种是gdb + a.out的方式启动进程,第二种的方式是gdb去attach一个已经启动了的进程。这种方式都是利用ptrace系统调用去跟踪被调试的进程。

ptrace共有四个参数:
long ptrace(enum __ptrace_request request,pid_t pid,void *addr,void *data);

// 两种gdb的区别在于参数不同
PTRACE_TRACEME 和 PTRACE_ATTACH

ptrace系统函数是Linux内核提供的一个用于进程跟踪的系统调用,通过它,一个进程(gdb)可以读写另外一个进程(test)的指令空间、数据空间、堆栈和寄存器的值。而且gdb进程接管了test进程的所有信号,也就是说系统向test进程发送的所有信号,都被gdb进程接收到,这样一来,test进程的执行就被gdb控制了,从而达到调试的目的。

也就是说,如果没有gdb调试,操作系统与目标进程之间是直接交互的;如果使用gdb来调试程序,那么操作系统发送给目标进程的信号就会被gdb截获,gdb根据信号的属性来决定:在继续运行目标程序时是否把当前截获的信号转交给目标程序,如此一来,目标程序就在gdb发来的信号指挥下进行相应的动作。

 

 

第一种方式是通过gdb去fork一个子进程,子进程去执行被调试的进程。然后建立父子关系。如下图所示:
 

第二种方式是gdb去attach被调试的子进程。如果想对一个已经执行的进程B进行调试,那么就要在gdb这个父进程中调用ptrace(PTRACE_ATTACH,[其他参数]),此时,gdb进程会attach(绑定)到已经执行的进程B,gdb把进程B收养成为自己的子进程,而子进程B的行为等同于它进行了一次 PTRACE_TRACEME操作。此时gdb进程会发送SIGSTO信号给子进程B,子进程B接收到SIGSTOP信号后,就会暂停执行进入TASK_STOPED状态,表示自己准备好被调试了。


 

与[转帖]gdb进阶调试技巧相似的内容:

[转帖]gdb进阶调试技巧

https://www.jianshu.com/p/9bdaa0644dba 整理一下在linux下C/C++用gdb工具debug一些提高效率的操作。基本的gdb操作就不在这里赘述了。 打印各种变量x 命令在gdb中可以使用x命令,来打印内存中的值。具体的格式是x/nfu addr。 含义为以f格

[转帖]gdb 常用命令

https://www.cnblogs.com/xvic/p/15997498.html 栈信息 不管是操作转储文件还是用GDB设置断点进行调试,都可以输入 (gdb)bt 打印栈内容进行查看。一般的宕机BUG,看下宕机的位置,然后看下源代码基本就可以解决了。但是很多情况下简单的 (gdb)bt 还

[转帖]如何快速查看进程/子线程堆栈

背景:分析现网问题时,有时需要快速查看某个进程/子线程堆栈调用,便于进一步分析问题,现提供几种不同获取进程堆栈方法。 实现方法: 1.使用gdb attach 调试进程,使用gdb相关cmd调试进程 # gdb -p pid 进入gdb后,可通过 (gdb)bt 查看主进程堆栈 (gdb)info

[转帖]GDB调试core文件

在Linux环境下C程序经常会出现A segmentation fault(段错误),如果我们的程序只有几十行,那么我们可以通过printf输出调试来找到哪个地方出现了异常,但如果是在项目中,如果我们还是通过print找查找错误,那么效率会很低。那么我们来学习一下Linux环境下通过core文件来找

[转帖]gdb调试

https://zhuanlan.zhihu.com/p/395115890?utm_id=0 写这个文档的目的,是让有一定软件开发基础的同学,可以较为快速地入门gdb的使用。gdb的命令非常多,在做了简短的介绍后,我挑选了最高频使用的命令/场景,希望能够让读者一个小时拥有即战力。 为了让读者对gd

[转帖]gdb调试常见命令详细总结(附示例操作)

一、简介 通过gdb调试我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码,程序的调试过程主要有:单步执行,跳入函数,跳出函数,设置断点,设置观察点,查看变量。 本文将主要介绍linux下的gdb调试工具常用的命令和具体的使用实

[转帖]关于gdb相关的几个工具的说明

https://phpor.net/blog/post/846 使用rpm命名查看gdb的rpm包,主要由下面几个程序:/usr/bin/gcore/usr/bin/gdb/usr/bin/gdbserver/usr/bin/gdbtui/usr/bin/gstack 其中:gcore 是生成cor

[转帖]记一次使用gdb诊断gc问题全过程

https://www.cnblogs.com/codelogs/p/17092141.html 简介# 上次解决了GC长耗时问题后,系统果然平稳了许多,这是之前的文章《GC耗时高,原因竟是服务流量小?》然而,过了一段时间,我检查GC日志时,又发现了一个GC问题,如下:从这个图中可以发现,我们GC有

[转帖] 记一次使用gdb诊断gc问题全过程

记一次使用gdb诊断gc问题全过程 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# 上次解决了GC长耗时问题后,系统果然平稳了许多,这是之前的文章《GC耗时高,原因竟是服务流量小?》然而,过了一段时间,我检查GC日志时,又发现了一个GC问题,如下:从这个图中可

[转帖]程序运行崩溃(segfault)的排查方法

这篇博文记录的非常详细:https://blog.csdn.net/zhaohaijie600/article/details/45246569 我的笔记: 写的C++程序老是运行两三天就挂了,关键是挂的时候连“segment fault”都不显示。动用了gdb、valgrind还是没办法,最后还是