Linux下GDB调试程序

1. 什么是GDB

GDB(全称:GNU Debugger)是GNU工程师为GNU操作系统开发的调试器。它可以用于调试C、C++、Objective-C、Pascal、Ada等语言编写的程序。

2. GDB的使用条件

在程序编译的时候,添加响应的调试信息,才能使程序使用GDB进行调试,以CMake为例,示范添加调试信息的方法:

SET(CMAKE_BUILD_TYPE "Debug")    # 使得生成的程序包含调试信息

SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")

SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

设置的具体含义可参考《CMAKE学习笔记》

注:

  • 通常在为调试而编译程序时,必须关掉编译器的优化现象(-0n),并打开调试选项 -g,另外,’-Wall‘在尽量不影响程序行为的情况下打开,提示所有的warning,优化选项的具体含义可参考 《CMAKE学习笔记》
  • -g选项的作用是在可执行文件中加入调试需要的信息GDB中查看的时源文件的代码)

3. GDB启动方法

3.1 GDB启动方法

gdb xxx     // xxx表示需要调试的程序名

3.2 GDB常用命令

GDB常用命令
命令 简写 功能
file 载入需要调试的可执行文件
kill 终止正在调试的程序
list l

列出部分源代码,可列出的是正在执行的位置附近的源代码。

1. 输入list后,每次列出大概10行左右的代码,重复enter可不断

    列出后续的源代码

2. list linenum,可列出特定行linenum附近的源代码

3. list filename:linenum  列出特定文件的特定行的源代码

next n 执行一行源代码,但是不进入函数内部
step 执行一行源代码,且可以进入函数内部
run r 开始执行当前被调试的程序,遇到第一个断点的时候停下来,如果没有断点,会直接往下运行。
continue c 继续运行程序到下一个断点的位置
start 运行程序并停在主函数开始的地方(即使没有断点)
break b

设置断点

1. b linenum  在当前文件的linenum行设置断点

2. b filename:linename 在特定文件的特定行设置断点

3. b func  在函数func处设置断点

4. b filename:func  在特定文件func函数处设置断点

5. b linenum if i==x  在某行设置条件断点

info b 查看算有断点
watch 监视一个变量的值,在调试过程中,变量的值发生变化的时候程序会停在变量值发生变化的位置,watch的优点是不需要提前预知到变量的值在哪里会发生变化而去打断点,在变量被watch之后,在那个地方变量的值发生了改变,程序就会停在这个地方(相当于到了断点)
rwatch 只要程序中出现读取目标变量的值,则程序就会停在读取的位置处
awatch 只要程序中出现读取目标变量的值或者修改目标变量的值,则程序就会停在读取或者修改值的位置处
print p 查看一个变量的值
display 与print类似,也是用于在调试过程中查看变量或者表达式的值,但使用display不仅在执行该命令的同时会看到目标变量的值,后续每次程序停止执行时(停在断点处),GDB 调试器都会将目标变量的值打印出来。
whatis  显示变量或者函数的类型
ptype 显示结构的定义,如结构体类型的具体定义
make 不退出gdb,重新生成可执行文件
shell 再不退出GDB的情况下,可执行Linux shell命令
info b 打印输出所有设置的断点
info watchpoints 打印输出所有的观察点
info files 显示被调试文件的详细信息
info args 查看传入当前函数的参数值
info func 显示所有的函数名称
info prog 显示被调试程序的执行状态
info locals 打印函数内所有的变量值
info inferiors

显示当前调试程序的所有进程 (父进程和子进程)

inferior n 切换到进程n(多进程程序调试)
info frame 查看所有的栈帧信息
frame n 查看栈信息, n为栈帧的编号
up n

在当前栈帧编号(假设为t)的基础上,将编号t+n的栈帧作为新的栈帧,n的默认值为1

(可理解为在栈中进行上下移动)

down n

在当前栈帧编号(假设为t)的基础上,将编号t-n的栈帧作为新的栈帧,n的默认值为1

(可理解为在栈中进行上下移动)

backtrace bt

查看栈信息:

backtrace n :打印最里层的n的栈帧的信息

backtrace -n: 打印最外层的n个栈帧的信息

backtrace -full 打印栈帧信息的同时打印局部变量的值

where 显示当前程序运行到哪一个文件的哪一行
enable n 使能断点n
disabke n 禁用断点n
del n

删除断点n

del m n t 删除多个断点

finish 终止当前函数并返回到函数调用点
set variable

设置变量的值,当程序运行到某个地方停住,如果想改变这个位置前某一个变量的值,则可以使用set variable来实现修改:

set variable data=1

set variable buffer="testcon"

call name(args) 调用并执行函数name,传递的参数是args
return val 停止当前函数,并将值val返回给函数调用者
quit q 退出GDB

关于watch命令的补充:

watch命令实现变量监视机制的方式有两种

  • 为变量设置硬件观察点  Hardware watchpoint
  • 为变量设置软件观察点  Software  watchpoint

软件观察点:watch命令监视目标变量或者表达式之后,GDB调试器会以单步执行的方式运行程序,在运行完每一行代码之后,都会区检测目标变量或者表达式的值是否发生了变化,如果改变,则程序会停止在值发生变化的位置。这种机制会降低程序的调试效率,但是调试程序的目的是为了查找到其中的bug,所以一定程度的效率降低并不是关注的重点。

硬件观察点:系统会为GDB提供少量的寄存器(Intel x86 提供4个调试寄存器),每个寄存器可以作为一个观察点,协助GDB完成变量监视,这种机制在同样实现变量监视的同时,不会影响程序的调试效率。

因为系统提供的调试寄存器数量有限,因此如果在程序中设置过多的硬件观察点,则可能会导致观察点失效,此时GDB会提示:

Hardware watchpoint num: Could not insert watchpoint

此时需要删除或者禁用一些观察点。

此外,调试寄存器的大小固定,因此不能用硬件观察点来监视占用字节数较多的变量(比如一些操作系统中,GDB只能监视4字节长度的数据,如 long 类型监视不了,可以尝试转换为 int 类型)。目前大多数系统都支持建立硬件观察点,所以GDB调试在建立观察点的时候,会优先建立硬件观察点,只有当系统不支持硬件观察点的时候,才会去建立软件观察点。使用如下命令,可强制GDB只建立软件观察点:

set can-use-hw-watchpoints 0

注:awatch 和 rwatch 命令只能设置硬件观察点,当系统不支持硬件观察点的时候,GDB会打印输出如下信息:

Expression cannot be implemented with read/access watchpoint.

关于display命令的补充:

display命令还支持将变量值通过特定的格式进行输出:

display/fmt variable
/fmt 描述
/d 以有符号、十进制的形式打印出整数。
/x 以十六进制的形式打印出整数。
/u 以无符号、十进制的形式打印出整数。
/t 以二进制的形式打印出整数。
/o 以八进制的形式打印出整数。
/f 以浮点数的形式打印变量或表达式的值。
/c 以字符形式打印变量或表达式的值。

通过display显示的变量或者表达式,都会被记录在自动显示列表中,可通过执行如下命令,查看列表中记录的所有变量或者表达式:

info dispaly

Linux下GDB调试程序_第1张图片

 Num: GDB为列表中的变量或者表达式提供的唯一编号

 Enb: 列表中的变量是处于激活状态还是禁止状态(y/n)

Expression: 列表中的变量或者表达式

可使用如下命令删除自动显示列表中的某个变量:

undisplay n
delete display n

可使用如下命令使能或者禁用自动显示列表中的某个变量:

enable display n       // 使能
disable display n      // 禁止

关于frame命令的补充

在程序中每个被调用的函数在执行的时候,都会生成与此函数相关的一些基本信息,这些基本信息包括:

  • 当前函数在程序中什么位置被调用
  • 函数被调用时传入的具体的参数值
  • 函数体中各局部变量的值

这些基础信息会存储在一块称栈帧的内存空间中,即程序运行时,每调用一个函数,就会生成一个对应的栈帧,程序调用结束的时候,栈帧会自动销毁。而这些栈帧的存储位置集中在一块特定的内存区域,称之为栈或者栈区。(在程序执行的时候都会占用一整块内存空间,且这块内存空间会被细分为多个不同的区域,例如栈区、堆区、全局数据区、常量区等,用以存储程序中不同的资源)。

因此当程序因在某个函数中存在某种错误而停止执行的时候,可以通过程序的栈帧记录的信息,查找程序异常停止的原因(C,C++程序中至少存在一个函数,即main函数,因此也会至少生成一个栈帧)。

frame命令的用法:

frame spec

通过上述命令,可以将指定的栈帧选定为当前的栈帧,spec参数可以指定为:

  • 栈帧编号,0 为当前被调用函数对应的栈帧号,最大编号的栈帧对应的函数通常就是 main() 主函数;
  • 栈帧的地址
  • 通过函数的函数名指定。如果是类似递归函数,其对应多个栈帧的话,通过此方法指定的是编号最小的那个栈帧。

栈帧的编号以及栈帧的地址,都可以通过如下命令进行查询:

info frame

Linux下GDB调试程序_第2张图片

 通过info frame可以查看到栈帧的如下信息:

  • 当前栈帧的编号
  • 当前栈帧的地址
  • 当前栈帧对应函数的存储地址,以及该函数被调用时的代码存储的地址
  • 当前函数的调用者,以及对应的栈帧地址
  • 编写此栈帧所使用的编程语言 (source language c++)
  • 函数参数的存储地址以及参数值
  • 栈帧中局部变量的存储地址
  • 栈帧中存储的寄存器变量,例如指令寄存器(64位环境中用 rip 表示,32为环境中用 eip 表示)、堆栈基指针寄存器(64位环境用 rbp 表示,32位环境用 ebp 表示)等。

-----------------------------------------------分割线------------------------------------------------

 4. GDB调试多进程程序

多进程程序调试,首先启动GDB调试,接着需要做两个设置:

set follow-fork-mode child
set detach-on-fork off

follow-fork-mode: 可取值为:child , parent, 用于设置GDB跟踪子进程还是父进程,在进行多进程程序调试的时候,可设置为跟踪子进程。

detach-on-fork: 可取值为off 或者 on, 表示调试当前进程的时候,其他进程是否继续运行,当设置为off的时候,调试当前进程,其他进程会被GDB挂起。当设置为on,调试当前进程的时候,其他进程会继续运行,

可以通过如下语句查看设置值:

show follow-fork-mode
show detach-on-fork

在设置上述的两个选项之后,即可开始调试多进程程序,在遇到fork()进程之后,GDB会自动切换新fork出的进程里面,原来的进程则被GDB挂起,可通过如下语句查看目前程序的所有进程:

info inferiors

Linux下GDB调试程序_第3张图片

可看到当前程序共有两个进程,可通过如下命令在不同进程之间进行切换:

inferior n

其中n表示info输出的进程的Num号,而不是进程号

使用如下命令可使进程脱离GDB调试:

detach inferiors n

5. GDB调试多线程程序

Linux环境下的线程本质上依然是进程,称之为轻量级进程(Light Weight Process, LWP),计算机是以进程作为资源分配的最小单位。而线程是操作系统调度执行的最小单位。

测试代码如下所示:

#include 
#include 
#include 
#include 

// 编写多线程测试程序
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

void* worker1(void* args)
{

    pthread_mutex_lock(&mutex);

    int* arrs = (int*)args;

     pthread_t tid = pthread_self();     // 获取线程ID

    for (size_t i = 0; i < 10; i++)
    {
        /* code */
        arrs[0]++;

        sleep(1);

        printf("Thread %d current cnt value is %d\n", tid, arrs[0]);
    }
    

    pthread_mutex_unlock(&mutex);

    return NULL;
}

void* worker2(void* args)
{
    pthread_mutex_lock(&mutex1);

    int* arrs = (int*)args;

    pthread_t tid = pthread_self();

    for (size_t i = 0; i < 10; i++)
    {
        /* code */
        arrs[1]++;

        sleep(1);

        printf("Thread %d current cnt value is %d\n", tid, arrs[1]);
    }
    
    pthread_mutex_unlock(&mutex1);

    return NULL;
}

int main(int argc, char* argv[])
{
    int array[2] = {0};

    pthread_t thread1, thread2;

    pthread_create(&thread1, NULL, worker1, array);
    pthread_create(&thread2, NULL, worker2, array);

    pthread_detach(thread1);
    pthread_detach(thread2);

    //pthread_join(thread1, NULL);
    //pthread_join(thread2, NULL);

    while (true)
    {
        // 等待两个子线程运行结束,主线程才能结束
        // 否则会由于主线程的提前推出而导致子线程执行失败
        // printf("Waiting for child thread to terminate...\n");
        pthread_mutex_lock(&mutex2);
        
        if (array[0] >= 10 && array[1] >=10)
        {
            break;
        }

        pthread_mutex_unlock(&mutex2);
    }
    
    printf("The Main thread terminate!\n");

    return 0;
}

运行结果:

Linux下GDB调试程序_第4张图片

 开始调试:

在进程多线程调试的时候,我们需要设置,让调试当前线程的时候,其他的线程能够被GDB挂起,可通过如下命令设置命令设置线程锁:

set scheduler-locking off

scheduler-locking 可取值为:

  • on   锁定线程,调试当前线程的时候,其他的线程会暂时被GDB挂起
  • off   不锁定线程,在调试当前线程的时候,其他的线程都会继续运行
  • step 当单步执行当前线程时,其它线程不会执行。但如果该模式下执行 continue、until、                finish 命令,则其它线程也会继续执行,并且如果某一线程执行过程遇到断点,则 GDB            调试器会将该线程作为当前线程。

可通过如下命令查看线程锁的设置值:

show scheduler-locking

注:set scheduler-locking要处于线程运行环境下才能生效,也就是程序已经运行并且暂停在某个断点处,否则会出现 “Target 'exec' cannot support this command.” 这样的错误;而且设置后的scheduler-locking值在整个进程内有效,不属于某个线程。

 运行至创建线程之后,可通过如下的方式查看所有的线程(主线程和子线程)

 可看到线程ID前面带有星号,表示此线程是当前正在被调试的线程,可通过thread id去切换到不同的线程进行调试。

thread n

 可在循环中设置条件断点,来调试程序:

线程2中,当循环进行到i=6的时候,会触发断点

Linux下GDB调试程序_第5张图片

 此时切换到线程3进行,按照相同的方式进行调试,此外在调试过程中,可指定某个或者所有的线程执行GDB命令

thread apply id GDB_CMD
thread apply all GDB_CMD

tread apply all detach 所有被挂起的线程进行释放,开始运行

-------------------------------------to be continued-----------------------------------------

你可能感兴趣的:(c++,C++并发编程,c++多线程,linux,运维,服务器)