Linux gdb多进程、多线程调试步骤与参数配置

本文总结GDB在Linux下的调试方法,涵盖基本命令、堆栈分析、多进程与多线程调试设置,如断点、跟踪、切换进程线程,以及调试步骤与参数配置。

前言

gdb 是 linux 平台下进行程序调试的最常用的工具。简单的程序调试就是加断点,然后一步一步让程序运行,直到找到 bug 。

一般的程序调试起来比较简单,但是在多进程或多线程情况下调试起来就比较麻烦。

若 test.c 是你想要调试的程序,那么在编译时需要加 -g,即 gcc test.c -g -o test。完成编译后使用命令:gdb test。

常用命令

命令 命令缩写及例子 说明
list + n l + n 显示源码第n行前后的代码,显示范围有限。
break + n b + n 在第n行设置断点
info i 描述程序状态
run r 开始运行程序
display disp 跟踪查看某个变量的值
info display   用于显示当前所有要显示值的表达式的情况
undisplay undisplay + 编号 用于结束某个表达式值的显示
step s 执行下一条语句,如果该语句为函数调用,则进入函数执行其中的第一条语句
next n 执行下一条语句,如果该语句为函数调用,不会进入函数内部执行
print p >打印内部变量的值
continue c 继续运行,直到遇到下一个断点
start st 开始执行程序,在main函数的第一条语句前面停下来
kill k 终止正在调试的程序
quit q 退出gdb
set args set args arg1 arg2 设置运行参数
show args show args 查看运行参数
finish finish 一直运行到函数返回并打印函数返回时的堆栈地址和返回值及参数值等信息

堆栈相关命令

命令 例子 说明
backtrace bt 查看堆栈信息
frame f 1 查看栈帧
info reg info reg/ i r 查看寄存器使用情况
info stack info stack 查看堆栈使用情况
up/down up/down 跳到上一层/下一层函数

这里以一个简单的程序为例,进行调试。

#include <bits/stdc++.h>
using namespace std;
#define M 5

int fact(int n)             //线性递归
{
    if (n < 0)
        return 0;
    else if(n == 0 || n == 1)
        return 1;
    else
        return n * fact(n - 1);
}
 
int facttail(int n, int a)   //尾递归
{
    if (n < 0)
        return 0;
    else if (n == 0)
        return 1;
    else if (n == 1)
        return a;
    else
        return facttail(n - 1, n * a);
}

int facttail1(int n, int a)  
{
    while(n > 0)
    {
        a = n * a;
        n--;
	}
	return a;
}
 
int main()
{
    //printf("%p", facttail);
    int a = fact(M);
    int b = facttail(M, 1);
    cout << "A:" << a <<endl;
    cout << "B:" << b <<endl;
}

(1)开始 gdb 调试

(2)设置断点

(3)查看栈的使用情况

更为详细的断点调试

命令

例子

说明
break + 设置断点的行号

break n

在n行处设置断点
tbreak + 行号或函数名 tbreak n/func 设置临时断点,到达后被自动删除
break + filename + 行号 break main.c:10 用于在指定文件对应行设置断点
break + <0x...> break 0x3400a 用于在内存某一位置处暂停
break + 行号 + if + 条件

break 10 if i==3

用于设置条件断点,在循环中使用非常方便
info breakpoints/watchpoints [n] >info break

n表示断点编号,查看断点/观察点的情况

clear + 要清除的断点行号 clear 10 用于清除对应行的断点,要给出断点的行号,清除时GDB会给出提示
delete + 要清除的断点编号 delete 3 用于清除断点和自动显示的表达式的命令,要给出断点的编号,清除时GDB不会给出任何提示
disable/enable + 断点编号 disable 3 让所设断点暂时失效/使能,如果要让多个编号处的断点失效/使能,可将编号之间用空格隔开
awatch/watch + 变量 awatch/watch i 设置一个观察点,当变量被读出或写入时程序被暂停

rwatch + 变量

rwatch i 设置一个观察点,当变量被读出时,程序被暂停

catch

  设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是C++的异常

tcatch

  只设置一次捕捉点,当程序停住以后,应点被自动删除

gdb多进程调试

命令 例子 说明
set follow-fork-mode [parent|child] set follow-fork-mode parent or child 设置调试器的模式,mode参数可以是:
parent :fork之后调试原进程,子进程不受影响,这是缺省的方式
child :fork之后调试新的进程,父进程不受影响。
show follow-fork-mode show follow-fork-mode 显示当前调试器的模式
set detach-on-fork [on|off] set detach-on-fork on or off 设置gdb在fork之后是否detach进程中的其中一个,或者继续保留控制这两个进程 。
on:子进程(或者父进程,依赖于follow-fork-mode的值)会detach然后独立运行,这是缺省的mode
off:两个进程都受gdb控制,一个进程(子进程或父进程,依赖于follow-fork-mode)被调试,另外一个进程被挂起
info inferiors info inferiors 显示所有进程
inferiors processid inferiors 2 切换进程
detach inferiors processid detach inferiors processid detach 一个由指定的进程,然后从fork 列表里删除。这个进程会被允许继续独立运行。
kill inferiors  processid kill inferiors  processid 杀死一个由指定的进程,然后从fork 列表里删除。
catch fork >catch fork 让程序在fork,vfork或者exec调用的时候中断

调试多进程时,需要设置 detach-on-fork 的值,默认值为 on,设置为 off 的含义:一个进程被调试,另外一个进程被挂起,这样就可以交替的调试进程。follow-fork-mode 的默认值为 parent,即默认调试父进程。调试代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	int num = 0;
	pid_t pid = fork();
	if(pid == 0) //子进程
	{
		while(1)
		{
			num++;
			printf("child:pid:[%d] num:[%d]\n", getpid(), num);
			sleep(2);
		}
	}
	else
	{
		while(1){
			num = num + 2;
			printf("parent:pid:[%d] num:[%d]\n", getpid(), num);
			sleep(2);
		}
	}
	return 0;
}

(1) 查看系统默认的 follow-fork-mode 和 detach-on-fork  设置follow-fork-mode 和 detach-on-fork

show follow-fork-mode
show detach-on-fork
set follow-fork-mode [parent|child]   
set detach-on-fork [on|off]

(2) 设置断点并查看断点信息

(3) 运行程序并使用 info inferiors 命令 (显示GDB调试的所有进程,其中带有*的进程是正在调试的进程)

(4) 使用 inferior + [编号] 切换进程,对子进程进行调试

(5) 继续运行程序,观察子进程的输出

(6) 中断子进程运行后,开始逐步调试

(7) 再次切换回父进程完成调试

gdb多线程调试

命令 例子 说明
info threads info threads 查询线程信息
thread + 线程号 thread 2 切换线程
thread apply [threadno] [all] + 命令 thread apply [threadno] [all] bt 线程根据相应的命令完成操作
set print thread-events set print thread-events 控制线程开始和结束时的打印信息
show print thread-events show print thread-events 显示线程打印信息的开关状态

调试代码:

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
 
/*全局变量*/
int sum = 0;
/*互斥量 */
pthread_mutex_t mutex;
/*声明线程运行服务程序*/
void* pthread_function1 (void*);
void* pthread_function2 (void*);
 
int main (void)
{
    /*线程的标识符*/
    pthread_t pt_1 = 0;
    pthread_t pt_2 = 0;
    int ret = 0;
    /*互斥初始化*/
    pthread_mutex_init (&mutex, NULL);
    /*分别创建线程1、2*/
    ret = pthread_create( &pt_1,                  //线程标识符指针
                           NULL,                  //默认属性
                           pthread_function1,     //运行函数
                           NULL);                 //无参数
    if (ret != 0)
    {
        perror ("pthread_1_create");
    }
	
    ret = pthread_create( &pt_2,                  //线程标识符指针
                          NULL,                   //默认属性
                          pthread_function2,      //运行函数
                          NULL);                  //无参数
    if (ret != 0)
    {
        perror ("pthread_2_create");
    }
    
    /*等待线程1、2的结束*/
    pthread_join (pt_1, NULL);
    pthread_join (pt_2, NULL);
 
    printf ("main programme exit!\n");
    return 0;
}
 
/*线程1的服务程序*/
void* pthread_function1 (void*a)
{
    int i = 0;
    printf ("This is pthread_1!\n");
    for( i=0; i<3; i++ )
    {
        pthread_mutex_lock(&mutex); /*获取互斥锁*/
        /*临界资源*/
        sum++;
        printf ("Thread_1 add one to num:%d\n",sum);
        pthread_mutex_unlock(&mutex); /*释放互斥锁*/
        /*注意,这里以防线程的抢占,以造成一个线程在另一个线程sleep时多次访问互斥资源,所以sleep要在得到互斥锁后调用*/
        sleep (1);
    }
    pthread_exit ( NULL );
}
 
/*线程2的服务程序*/
void* pthread_function2 (void*a)
{
    int i = 0;
    printf ("This is pthread_2!\n");
    for( i=0; i<5; i++ )
    {
        pthread_mutex_lock(&mutex); /*获取互斥锁*/
        /*临界资源*/
        sum++;
        printf ("Thread_2 add one to num:%d\n",sum);
        pthread_mutex_unlock(&mutex); /*释放互斥锁*/
        /*注意,这里以防线程的抢占,以造成一个线程在另一个线程sleep时多次访问互斥资源,所以sleep要在得到互斥锁后调用*/
        sleep (1);
    }
    pthread_exit ( NULL );
}

(1) 设置断点并查看信息

(2) 运行程序,这里可以不设第一个断点

(3) 两个线程交替运行,观察不同线程的输出结果

(4) 使用 info threads 查看线程信息,使用 thread + [编号] 切换线程

(5) 使用 thread apply + [编号] + 命令 

锁定其他线程,只让当前线程运行

(1) 设置 set scheduler-locking on

(2)  观察线程 1 运行的情况并完成调试

其他命令

(1) thread apply ID1 ID2 IDN command: 让线程编号是ID1,ID2…等等的线程都执行command命令。

(2) thread apply all command:所有线程都执行command命令。

(3) set scheduler-locking off|on|step: 在调试某一个线程时,其他线程是否执行。在使用step或continue命令调试当前被调试线程的时候,其他线程也是同时执行的,如果我们只想要被调试的线程执行,而其他线程停止等待,那就要锁定要调试的线程,只让他运行。

  • off:不锁定任何线程,默认值。
  • on:锁定其他线程,只有当前线程执行。
  • step:在step(单步)时,只有被调试线程运行。

(4) set non-stop on/off:当调试一个线程时,其他线程是否运行。

(5) set pagination on/off: 在使用backtrace时,在分页时是否停止。

(6) set target-async on/ff: 同步和异步。同步,gdb在输出提示符之前等待程序报告一些线程已经终止的信息。而异步的则是直接返回。

(7) show scheduler-locking: 查看当前锁定线程的模式

总结

以上Linux gdb多进程、多线程调试过程为个人经验,希望能给大家一个参考。

声明:本文来自互联网或用户投稿,该文观点仅代表作者本人,不代表本站立场。文章及其配图仅供学习和交流之用,版权归原作者所有,如有内容侵权或者其他违规问题,请联系本站处理。

相关文章