欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux gdb多进程、多线程调试

程序员文章站 2024-03-17 17:40:34
...

目录

常用命令

堆栈相关命令

更为详细的断点调试

gdb多进程调试

gdb多线程调试


前言

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 调试

Linux gdb多进程、多线程调试

 

(2)设置断点

Linux gdb多进程、多线程调试

 

(3)查看栈的使用情况

Linux gdb多进程、多线程调试

 

 

更为详细的断点调试

命令

例子

说明

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]

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

 

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) 设置断点并查看信息

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试

Linux gdb多进程、多线程调试

 

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

(1) 设置 set scheduler-locking on

Linux gdb多进程、多线程调试

 

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

Linux gdb多进程、多线程调试


其他命令

(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: 查看当前锁定线程的模式

 

参考:

https://blog.csdn.net/snow_5288/article/details/72982594

https://www.cnblogs.com/euphie/p/9781482.html