zoukankan      html  css  js  c++  java
  • gdb常用命令及gdb调试多进程/线程程序&coredump

    一、常用普通调试命令

    1.简单介绍GDB

    介绍: gdbLinux环境下的代码调试⼯具。
    使⽤:需要在源代码⽣成的时候加上 -g 选项
    开始使⽤: gdb binFile
    退出: ctrl + d quit

    2.调试过程

    (1)list命令

    • list  linenum        显⽰binFile第linenum行周围的源代码,接着上次的位置往下列,每次列10⾏。
    • list  function       显示函数名为function的函数的源程序
    • list                     显示当前行后面的源程序
    • list -                  显示当前行前面的源程序

    (2)run或r

        运行程序。
    • run args         run命令可以直接接命令行参数值,也可以在执行run之前通过 set args + 参数值实现。

    (3)break(b)

    打断点,使用方法:
    • b  linenum                           在某行打断点
    • b  +offset/-offset                  在当前行号的前面或后面offset停住
    • b  filename:linenum             在某文件的某行打断点
    • b  filename:function            在某文件某个函数入口停住
    • b  *address                         在程序的运行地址处停住
    • b                                         没有参数在下一句停住
    • b where if condition            当某个条件满足时,在某一行停住(这个很有用,比如b 10 if ret == 5)
    • breaktrace(或bt)                 查看各级函数调⽤及参数
    • delete breakpoints            删除所有断点
    • delete breakpoints n         删除序号为n的断点
    • disable breakpoints           禁⽤断点
    • enable breakpoints            启⽤断点
    对于break命令,我们要灵活使用。例如打多个断点。多线程程序中我们可以主函数中线程创建后立即打断点,执行线程函数入口打断点等。

    (4)单步命令

        普通用法就不说了。
    • step count         一次性执行count步,如果有函数会进入函数
    • next count         一次执行count,不进入函数
    • finish                 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值以及参数信息
    • until                   退出循环体(尤其是针对for循环这种,很烦的)

    (5)continue命令
    continue(或c):从当前位置开始连续⽽⾮单步执⾏程序

        当程序被停住之后,可以使用continue(c)命令,恢复程序的运行直到程序结束,或到达下一个断点。这里要注意如果没有断点程序是会直接结束的。

    (6)print(p)命令

        这个命令比较常用,用来查看我们想看的内容。比如有关数组可以看全部,也可以看从左到右某一部分:


        print命令针对变量查看的输出格式有:
    • x 按十六进制格式显示变量
    • d 按十进制格式显示变量
    • u 按十六进制格式显示无符号整型
    • o 按八进制格式显示变量
    • t 按二进制格式显示变量t 按二进制格式显示变量
    • a 按十六进制格式显示变量
    • c 按字符格式显示变量
    • f 按浮点数格式显示变量

    (7)watch命令

        这个命令比较有用。watch一般用来观察某个表达式(变量也是一种表达式)的值是否有变化,如果有变化,马上停住程序。我们有一下几种方法设置观察点:
    • watch   expr                 为表达式expr设置一个观察点,一旦表达式值有变化,马上停住程序
    • rwatch  expr                  当表达式expr被读时,停住程序
    • awatch expr                  当表达式的值被读或被写时,停住程序。
    • info      watchpoints       列出所有观察点(info指令通常可以去套 举例如下,演示观测*i的值,一旦变化停下来:

        在循环中我们也可以使用watch,配合ignore,它是除了until命令之外又一个可以让我们跳出循环的方法,不过watch+ignore更强大,可以任意跳转到第i次循环。它们的意思就是观察一个变量,可以理解为断点,ignore这个断点多少次,然后用continue就可以直接跳过了。


     

     

     

     

    (8)examine命令

        使用该命令来查看内存地址中的值。语法是:x/u addr 
        addr表示一个内存地址。“x/”后的n、f、u都是可选的参数,n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容;f 表示显示的格式,如果地址所指的是字符串,那么格式可以是s,如果地址是指令地址,那么格式可以是i;u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4字节。u参数可以被一些字符代替:b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了字节长度后,GDB会从指定的内存地址开始,读写指定字节,并把其当作一个值取出来。n、f、u这3个参数可以一起使用,例如命令“x/3uh 0x54320”表示从内存地址0x54320开始以双字节为1个单位(h)、16进制方式(u)显示3个单位(3)的内存。

     

    (9)jump命令

        jump命令不会改变程序栈的内容,一般只在同一函数内跳转。
    • jump linespec     指定下一条语句的运行点,linespec可以是linenum,filename+linenum,+offset这几种形式
    • jump address      跳到代码行的地址

    (10)signal命令

    使用signal 信号名(如SIGINT)这种方式把信号发送给程序,如果程序注册了signal_handler函数,还可以进行相应的处理,帮助调试程序。

    (11)set命令

    • set args       设置命令行参数
    • set env environmentVarname=value 设置环境变量。如:set env USER=benben

    (12)call命令

    • call function     强制调用某函数
        强制调用某函数,它会显示函数返回值(如果函数返回值不是void)。print命令也可以完成该功能。

    (13)disassemble命令

        反汇编命令,查看执行时源代码的机器码。
     
     
     
     
     
     
     
    (14)其他命令
    • infoi) locals:  查看当前栈帧局部变量的值
    • info break :      查看断点信息
    • info(i) breakpoints: 查看当前设置了哪些断点
    • finish:               执⾏到当前函数返回,然后挺下来等待命令
    • set var:             修改变量的值
    • display 变量名: 跟踪查看⼀个变量,每次停下来都显⽰它的值
    • undisplay:        取消对先前设置的那些变量的跟踪
    • until X⾏号:      跳⾄X 直接回
    • n next:         单条执⾏
    • p 变量:             打印变量值。

    (14)另外一些常用调试命令

    until 跳出循环。在执行完循环体内的最后一条语句之后执行 until, 就会执行完循环体到后面的一个语句停下。

    finish 跳出当前函数,执行完当前的函数。

    tui是一个命令行的界面,能同时把代码显示出来。

    二、使用gdb调试core文件

    1.认识core文件

    linux下程序异常中止退出或崩溃的时,内核将程序在运行期间的内存状态等相关信息转存到磁盘,这个转存的文件叫core dump文件。文件中记录程序当时的内存调用、堆栈引用、进程和线程调用等信息,以帮助开发或维护人员了解异常发生当时的环境参数和信息。
    段错误(segfault)也称为核心转储;指程序试图操作不允许访问或试图访问的不合法内存的情况。可能导致段错误的原因主要有:
    1、试图解引用空指针(你不允许访问内存地址0)
    2、试图解引用不在你内存中的其他指针
    3、一个C++ vtable虚表指针被破坏并指向错误的地方,这导致程序试图去执行一些不可执行的内存。
    4、其他情况,比如未对齐的内存访问也可能会出现段错误。

    2.配置生成core文件

    编译时需要加 -g 选项使程序生成调试信息: gcc -g core_test.c -o core_test

    ①使用 ulimit -c 查看core开关,如果为0表示关闭,不会生成core文件;
    ②使用 ulimit -c [filesize] 设置core文件大小,当最小设置为4之后才会生成core文件;
    ③使用 ulimit -c unlimited 设置core文件大小为不限制,这是常用的做法;
    ④如果需要开机就执行,则需要将这句命令写到 /etc/profile 等文件。
    core文件命名和保存路径:

    ①core文件有默认的名称和路径;
    自己命名和指定保存路径,可以通过 /proc/sys/kernel/core_pattern 设置 core 文件名和保存路径,方法如下:
        echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
        命名的参数列表:
        %p - insert pid into filename 添加pid
        %u - insert current uid into filename 添加当前uid
        %g - insert current gid into filename 添加当前gid
        %s - insert signal that caused the coredump into the filename 添加导致产生core的信号
        %t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间
        %h - insert hostname where the coredump happened into filename 添加主机名
        %e - insert coredumping executable name into filename 添加命令名。

    3.调试core文件

    1)方法1: gdb [exec file] [core file] 然后执行bt看堆栈信息(或者where命令)

    (2)方法1:gdb -c [core file],然后 file [exec file],最后再使用 bt 查看错误位置

    https://blog.csdn.net/sunxiaopengsun/article/details/72974548

    三、使用gdb调试多进程多线程程序

    1.设置

    默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序。只需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)即可。两者结合起来构成了GDB的调试模式。
          follow-fork-mode  detach-on-fork    说明
    parent                   on                     只调试主进程(GDB默认)
    child                     on                     只调试子进程
    parent                   off                    同时调试两个进程,gdb跟主进程,子进程block在fork位置
    child                     off                    同时调试两个进程,gdb跟子进程,主进程block在fork位置
       设置方法:set follow-fork-mode [parent|child]   set detach-on-fork [on|off]
       查询正在调试的进程:info inferiors
       切换调试的进程: inferior <infer number>
       添加新的调试进程: add-inferior [-copies n] [-exec executable] ,可以用file executable来分配给inferior可执行文件。
       其他:remove-inferiors infno, detach inferior
    查看gdb默认的参数设置:

    2.演示代码

    下面这段代码的主要流程就是在main函数中fork创建一个子进程,然后在父进程中又创建一个线程,接着就使用gdb进行调试(block子进程)。

     1 #include <stdio.h>
     2 #include <pthread.h>
     3 #include <sys/types.h>
     4 #include <unistd.h>
     5 
     6 int main(int argc, const char **argv)
     7 {
     8     int pid;
     9     pid = fork();
    10     if (pid != 0)    //add the first breakpoint.
    11         Parent();
    12     else
    13         Child();
    14     return 0;
    15 }
    16 
    17 //Parent process handle.
    18 void Parent()
    19 {
    20     pid_t pid = getpid();
    21     char cParent[] = "Parent";
    22     char cThread[] = "Thread";
    23     pthread_t pt;
    24 
    25     printf("[%s]: [%d] [%s]
    ", cParent, pid, "step1");
    26 
    27     if (pthread_create(&pt, NULL, (void *)*ParentDo, cThread))
    28     {
    29         printf("[%s]: Can not create a thread.
    ", cParent);
    30     }
    31 
    32     ParentDo(cParent);
    33     sleep(1);
    34 }
    35 //Parent process handle after generate a thread.
    36 void * ParentDo(char *argv)
    37 {
    38     pid_t pid = getpid();
    39     pthread_t tid = pthread_self();   //Get the thread-id selfly.
    40     char tprefix[] = "thread";
    41 
    42     printf("[%s]: [%d] [%s] [%lu] [%s]
    ", argv, pid, tprefix, tid, "step2");  //add the second breakpoint.
    43     printf("[%s]: [%d] [%s] [%lu] [%s]
    ", argv, pid, tprefix, tid, "step3");
    44 
    45     return NULL;
    46 }
    47 //Child process handle.
    48 void Child()
    49 {
    50     pid_t pid = getpid();
    51     char prefix[] = "Child";
    52     printf("[%s]: [%d] [%s]
    ", prefix, pid, "step1");
    53     return;
    54 }

    如果直接运行程序,那么输出的结果如下:

    3.gdb调试

    3.1设置调试模式和Catchpoint

    设置调试父子进程,gdb跟主进程,子进程block在fork位置。

    这时可以另开一个终端,使用如下命令查看当前CentOS系统所有进程的状态:发现父进程PID为10062,通过fork产生的子进程为10065:

     同时,可以使用命令cat /proc/10062/status查看当前进程的详细信息:进程PID为10060,它的父进程(即GDB进程)为10062,同时这也是追踪进程ID,线程数Threads为1(共享使用该信号描述符的线程数,在POSIX多线程序应用程序中,线程组中的所有线程使用同一个信号描述符)。

    3.2 设置第一个断点

    在程序的第46行设置断点,并运行到断点处:

    这时再次使用命令pstree -pul查看当前系统进程的状态:发现此时仍然只有父进程13162和子进程13159。

    3.3 执行到第一个断点此时如果切换到子进程13162

    重新切换到父进程13159

    3.4 设置第二个断点并调试

    在第50行设置断点继续调试主进程(使父进程产生线程),其中父进程和线程到底是谁先执行是由内核调度控制的。

    这时使用命令pstree -pul查看当前系统进程的状态:存在父进程13159和子进程13162以及父进程创建的一个线程14208(线程用大括号{}表示)。

    同时,使用命令cat /proc/13159/status查看当前进程的详细信息:进程PID为13159,它的父进程(即GDB进程)为13154,同时这也是追踪进程ID,线程数Threads为2(当前父进程13159+线程14208)。

    3.5 查看第二个断点处的调试信息

    3.6手动切换到线程
    3.7 开始执行第二个断点处的代码

    这时使用命令查看当前系统进程的状态:存在父进程13159和子进程13162,其中线程14208已经结束了。

    本文部分参考:https://typecodes.com/cseries/multilprocessthreadgdb.html

  • 相关阅读:
    springcloud系列五 feign远程调用服务
    ribbon负载均衡
    使用RestTemplate时报错java.lang.IllegalStateException: No instances available for 127.0.0.1
    HBase介绍
    HBase单机模式安装
    HDFS介绍及简单操作
    zookeeper三种模式安装详解(centos 7+zookeeper-3.4.9)
    zookeeper介绍
    hadoop搭建伪分布式集群(centos7+hadoop-3.1.0/2.7.7)
    linux配置本地yum源
  • 原文地址:https://www.cnblogs.com/33debug/p/7043437.html
Copyright © 2011-2022 走看看