Linux----使用GDB调试多进程和多线程程序

来源:互联网 发布:中文域名转码 源码 编辑:程序博客网 时间:2024/05/16 19:51

一、GDB的介绍

        1、简介:

        GDB(GNU Debugger)是GCC的调试工具,其功能强大,主要帮你完成以下4个方面的功能:
        (1).启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
        (2).可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
        (3).当程序被停住时,可以检查此时你的程序中所发生的事。
        (4).动态的改变你程序的执行环境。

        2、gdb(Linux调试器)使用

       (1).产生调试信息:要进行代码的调试,就需要有调试信息,要产生调试信息,就需要在源代码生成时添加-g选项;
       (2).调试的开始和退出
           开始调试:gdb file(file表示要进行调试的源文件)
          退出调试:ctrl+d或quit

       (3).调试的一些基本操作:

      (4).附录;

      在gdb中,经常用到的恢复程序运行和单步调试的命令有:
           continue 继续运行程序直到下一个断点(类似于VS里的F5)
           next 逐过程步进,不会进入子函数(类似VS里的F10)
           setp 逐语句步进,会进入子函数(类似VS里的F11)
           until 运行至当前语句块结束
           finish 运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)

二、GDB调试多进程

        gdb进行多进程调试主要有两种种方法:

        分别是:follow-fork-mode 方法和attach 子进程方法。

        attach子进程方法:灵活强大,但需要添加额外代码,适合于各种复杂情况,特别是守护进程;
        follow-fork-mode方法:方便易用,对系统内核和GDB版本有限制,适合于较为简单的多进程系。

       下面重点介绍attach 子进程方法。

       1、follow-fork-mode 方法:

            在程序fork之前输入

(gdb)set follow-fork-mode [parent|child]

            follow-fork-mode的用法为:

 (gdb) set follow-fork-mode [parent|child]

           parent: fork之后继续调试父进程,子进程不受影响。
           child: fork之后调试子进程,父进程不受影响。
           因此如果需要调试子进程,在启动gdb后:

 (gdb) set follow-fork-mode child

          并在子进程代码设置断点。
          detach-on-fork参数,指示GDB在fork之后是否断开(detach)某个进程的调试,或者都交由GDB控制:
(gdb) set follow-fork-mode child

          on: 断开调试follow-fork-mode指定的进程。
          off: gdb将控制父进程和子进程。follow-fork-mode指定的进程将被调试,另一个进程置于暂停(suspended)状态

      2、attach 子进程方法:

            attach是GDB中中有附着到正在运行的进程中的功能实现,通过attach(pid),就可直接该进程进行调试。 在介绍这种调试方法时,我们需要先对守护进程做一个简单的学习,因为attach方法对于守护进程的调试有很大的作用。

          守护进程:

          定义:守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。
          简介:守护进程是一个在后台运行并且不受任何终端控制的进程,如果你了解过信号方面的知识,你就会明白信号在捕捉的时候为什么会单独留下一个‘9号’进程不能被捕获,两者其实都是从保护系统的角度所设计出来的。

         守护进程创建步骤:

          (1). 创建子进程,父进程退出。
          (2).在子进程中创建新对话。
          (3).改变当前目录为根目录。
          (4).重设文件权限掩码。
          (5).关闭文件描述符。
          (6).守护进程退出处理。

         演示代码:

简单测试(在这段代码中我们很明显会发现父进程中调用的Div函数中有一个除0错误):

#include<stdio.h>  #include<pthread.h>  #include<stdlib.h>    int Div(int num1, int num2)  {          pit_t id=getpid();          printf("my pid is %d\n",id);          int result, diff;          diff = num1 - num2;          result = num1 / diff;          return result;  }    int main()  {          pid_t   pid;            pid = fork();          if (pid <0)         {                  printf("fork err\n");                  exit(-1);          }       else if (pid == 0)        {//child                  int val1 = 5;                  int val2 = 1;                  int ret = 0;                  int i=5;                  while(i--)               {//parent                          ret = Div(val1, val2);                          val2++;                          val1--;                          printf("cur result is %d\n",ret);                  }            }       else         {//child                  sleep(4);                  wait(-1);                  exit(0);          }  }

运行结果:


先让程序运行到后台:执行gdb &

调试结果:


三、GDB调试多线程

       gdb提供的多线程调试工具:

       新线程创建自动提醒 thread thread-id实现不同线程之间的切换 info threads查询存在的线程 thread apply [thread-id-list] [all] args在一系列线程上执行命令 线程中设置指定的断点 set print thread-events控制打印线程启动或结束是的信息set scheduler-locking off|on|step在使用step或是continue进行调试的时候,其他可能也会并行的执行,如何才能够只让被调试的线程执行呢?该命令工具可以达到这个效果。

      off:不锁定任何线程,也就是所有的线程都执行,这是默认值。
      on:只有当前被调试的线程能够执行。
      step:阻止其他线程在当前线程单步调试时,抢占当前线程。只有当next、continue、util以及finish的时候,其他线程才会获得重新运行的机会。

示例代码:

#include <stdio.h>#include <pthread.h> void* threadPrintHello(void* arg){    while(1)    {        sleep(5);        printf("Hello");    }} void* threadPrintWorld(void* arg){    while(1)    {        sleep(5);        printf("World");    }} int main( int argc , char* argv[]){    pthread_t pid_hello , pid_world;     int ret = 0;     ret = pthread_create(&pid_hello , NULL , threadPrintHello , NULL);     if( ret != 0 )    {        printf("Create threadHello error");        return -1;    }     ret = pthread_create(&pid_world , NULL , threadPrintWorld , NULL);     if( ret != 0 )    {        printf("Create threadWorld error");        return -1;    }     while(1)    {        sleep(10);        printf("In main thread");    }     pthread_join(pid_hello , NULL);    pthread_join(pid_world , NULL);     return 0;}

   gdb hello 进入调试:

需要调试的地方打下断点,run运行到断点处。如:在 break 10 处打断点,并且 (gdb) r 得:

       产生新线程提醒:(1)LWP 3445;(2)LWP 3446  。

查询已经存在的线程:执行(gdb)info threads

      主要包括gdb分配的线程id号(例如1,2,3),操作系统分配的线程id(例如20568),线程的名字以及线程相关的调用栈信息。

切换线程

         thread threadno可以切换到指定的线程,threadno就是上面gdb分配的线程id号。如threadno = 2:


锁定一个线程

         默认情况下gdb不锁定任何线程。当我们切换到线程2的时候,使用next执行的时候,发现打印的结果中包含有其他线程的打印信息。可以使用set scheduler-locking on来锁定只有当前的线程能够执行。可以发现锁定线程之后,使用next执行变不会有其它线程的打印结果。

执行命令

         使用thread apply all bt 来让一个或是多个线程执行指定的命令。例如让所有的线程打印调用栈信息。


后面的命令就不一一实现了,大家照着命令提示做就行了,很简单的!!!!!!