一、GDB简介

 GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。GDB能够跟踪程序的执行,也能够恢复程序崩溃前的状态。

GDB常规功能如下:

A、自定义程序的启动方式(指定影响程序运行的参数)

B、设置条件断点(在条件满足时暂停程序的运行)

C、回溯检查导致程序异常结束的原因(Core Dump)

D、动态改变程序的执行流(定位问题的辅助方式)

二、GDB功能

1、core dump

核心转储文件(Core Dump)是进程内存的拷贝,ulimit -c显示核心转储文件大小的最大值,0表示禁止核心转储。ulimit -c unlimited设置核心转储文件大小无限制。

ulimit -c unlimited只对当前终端环境有效,重新登陆会被重置。永久有效需要修改ulimit配置文件/etc/security/limits.conf。

#<domain>   <type>  <item>         <value>

*               soft      core            unlimited

generate-core-file命令或gcore命令可以为调试进程产生core dump文件。
gdb program core_file

加载可执行程序和core dump文件

2、GDB启动

(1)直接启动

gdb  

进入GDB调试环境

gdb program  

调试模式启动program

gdb program core 

执行program程序和core文件

gdb -tui

启动gdb,并且分屏显示源代码

gdb --args program -f config.ini

启动gdb,并指定参数

(2)动态连接

gdb program pid 

调试正在运行的程序,进程号为pid,GDB会自动进行attach

(3)运行程序

run

运行载入的可执行程序

run args  arg1 arg2

带参数运行可执行程序

(4)GDB调试示例

gdb

启动GDB

file program

载入目标可执行文件

set args arg1 arg2

设置命令行参数

run

执行目标程序

attach pid

链接到目标进程,链接成功后目标进程将停止执行

continue

恢复执行目标进程

stop

停止运行

3、信息显示

show version

显示GDB版本

gdb -q

不显示提示信息启动GDB

set confirm off

退出时不显示提示信息

set pagination off

输出信息多时不会暂停输出

info sharedlibrary regex

显示共享连接库信息

set charset GBK

设置字符编码,GDB默认使用utf-8编码。

shell command

在GDB调试环境执行shell命令

4、源码查看

dir /path/to/your/sources

添加一个源码目录到调试环境

list  line          

显示程序第linenum行周围的程序

list  function    

显示函数名为function的函数的源程序

list             

显示当前行后面的源程序

list -    

显示当前行前面的源程序

set listsize n

设置list可以查看源码行数,默认为10行

5、环境变量

show convenience

查看当前的所有环境变量

set env env_name=value

设置环境变量

set var var_name=value

设置变量,var_name必须是调试环境中的变量名称

set args arg1 arg2

设置可执行程序的命令行参数

print var_name=value

修改变量值

6、函数

info functions

查看程序中函数符号

info args
frame N

查看当前函数参数的值

info locals

查看当前局部变量的值

info variables

查看程序中的变量符号

info registers

查看函数寄存器信息

info frame

查看当前函数调用的栈帧信息

frame n

切换到栈编号为N的函数栈帧

backtrace

查看函数调用的顺序,函数调用栈信息

set step-mode on

进入不带调试信息的函数

return expression或者finish
退出正在调试的函数

disassemble function

查看函数反汇编代码

set debug entry-values 1

打印尾调用堆栈帧信息

call function

强制调用某函数,会显示函数返回值(如果函数返回值不是void)。

start function

执行函数,并停在函数开始位置

7、Catch Point

tcatch

让catchpoint只触发一次

catch fork

为fork调用设置catchpoint

catch vfork

为vfork调用设置catchpoint

catch exec

为exec调用设置catchpoint

catch syscall name or num

为系统调用设置catchpoint

catch syscall ptrace set $rax=0

通过ptrace调用设置catchpoint破解anti-debugging的程序

8、打印

set print elements number-of-elements

设置打印数组元数的最大数量

set print elements 0

set print elements unlimited

设置打印数组元素不限制

set print pretty on

优化打印格式,每行打印一个结构体成员

set print object on

按照派生类打印对象

set print address [on/off]

设置参数地址打印模式,GDB具有支持是否显示参数地址信息的功能,用户可以设置其为打开或者关闭

show print address

查看当前参数地址打印模式

set print array [on/off] 

设置数组打印模式,如果打开数组显示,数组中每个元素都占用一行;如果关闭,每个元素都以逗号分开,默认关闭。

set print array-indexes on

设置打印时打印数组的索引下标

print vec

打印STL容器中的内容

print array[index]@num

打印数组中任意连续的元素值

print *array@num

打印数组中开头的前num各数组元素

backtrace full

info locals

打印函数局部变量的值

info proc mappings

info files

打印进程的内存信息

print 'file.c'::var_name

打印静态变量的值

print /Nuf var_name

打印变量

examine(x) /Nuf expression

检查存储区域的数据

N:要打印的单元数量

u:单元大小,b单字节,h双字节,w四字节,g八字节

f:数据打印格式,x十六进制,d有符号十进制,u无符号十进制,o八进制,t二进制,a地址,c字符,f浮点数

9、汇编

set disassembly-flavor intel disassemble main

设置汇编指令格式

break *main

在函数的第一条汇编指令打断点

set disassemble-next-line on

set disassemble-next-line auto

set disassemble-next-line off

自动反汇编后面要执行的代码

disas /m main

将源程序和汇编指令映射起来

display /i $pc

显示将要执行的汇编指令

info registers i all-registers i registers eax

打印寄存器的值

disassemble /r main

显示程序原始机器码, 反汇编命令,查看执行时源代码的机器码。

10、信号

info signals

查看信号

handle signal stop/nostop

信号发生时是否暂停程序

handle signal print/noprint

信号发生时是否打印信号信息

handle signal pass(noignore)/nopass(ignore)

 信号发生时是否把信息丢给程序处理

signal signal_name

给程序发送信号

11、GUI支持

gdb -tui program

进入图形化调试界面

ctrl+x+a

TUI(terminal user interface)终端用户界面切换

layout asm

显示汇编代码窗口

layout regs

显示寄存器窗口

winheight <win_name> [+ | -]count

调整窗口大小

三、GDB断点调试

1、断点简介

软件断点:由非法指令异常实现(软件实现)

硬件断点:由硬件特性实现(数量有限)

数据断点:由硬件特性实现(数量有限)

2、软件断点

通过函数名设置断点

break func_name [if var = value]

tbreak func_name [if var = value]

break设置的断点是永久断点,tbreak设置的断点是临时断点,运行一次后将失效。

通过文件名行号设置断点

break file_name:line_num [if var = value]

tbreak file_name:line_num [if var = value]

info breakpoints

查看所有断点

delete 1 2 n

删除断点

enable 1 2 n

开启断点

enable breakpoints

开启所有断点

disable 1 2 n

关闭断点

disable breakpoints

关闭所有断点

print name

变量查看

set var name=value

变量设置

next n

连续执行n行代码,默认为1行

return expression

强制当前函数返回

finish

运行至当前函数返回

until lineNum

执行至目标行

jump line

调转执行

break *address  

在程序地址上打断点,如break *0x400522

break *0x400440

在程序入口处打断点,程序入口地址可以通过readelf -h program或在GDB调试环境中通过info files获取,Entry point: 0x400440。

break line

break file:line

info breakpoints

在文件行号上打断点

save breakpoints file-breakpoints-to-save

source file-breakpoints-to-save

保存已经设置的断点

tbreak line

设置临时断点

break line if cond b 11 if i==10

设置条件断点

ignore bnum count

忽略断点

break  +offset/-offset    

在当前行号的前面或后面offset停住

break  filename:linenum         在某文件的某行打断点

break  filename:function         在某文件某个函数入口停住

break  *address                      在程序的运行地址处停住

break where if condition         当某个条件满足时,在某一行停住
ignore <break_list> count

表示break_list所指定的断点号将被忽略count次。

step count

一次性执行count步,如果有函数会进入函数

next count

 一次执行count,不进入函数

finish

 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值以及参数信息

until  

退出循环体

continue命令

当程序被停住后,可以使用continue(c)命令,恢复程序的运行直到程序结束,或到达下一个断点。

在条件断点中可以调用标准库函数

break main.cpp:255 if strcmp(strA.c_str(), strB.c_str()) == 0

break main.cpp:255 if strA.compare(strB) != 0

jump命令不会改变程序栈内容,一般只在同一函数内跳转。

jump linespec     指定下一条语句的运行点,linespec可以是linenum,filename+linenum,+offset几种形式

jump address      跳到代码行的地址

3、硬件断点

当代码位于只读存储器(flash)时,只能通过硬件断点调试。

硬件断点需要硬件支持,数量有限。

GDB通过hbreak命令支持硬件断点。

show can-use-hw-watchpoints

显示当前GDB支持硬件断点的数量

hbreak使用同break相同。

4、数据断点

GDB支持数据断点的设置。

watch用于观察某个表达式的值是否有变化,如果有变化,马上停住程序。

watch命令用于监视变量是否被改变(本质为硬件断点)。

watch  expression

为表达式expression设置一个观察点,一旦表达式值有变化,马上停住程序

rwatch  expression

当表达式expression被读时,停住程序

awatch expression

当表达式的值被读或被写时,停住程序。

info watchpoints

 列出所有观察点

wacth *(type*)adress disable/enable/delete

设置观察点

info threads
watch expression thread threadnum

设置观察点只针对特定线程生效

四、多进程调试

1多进程调试模式

(1)follow-fork-mode选项

Linux 2.5.60版本内核开始,GDB对使用fork/vfork创建子进程的程序提供了follow-fork-mode选项来支持多进程调试。

follow-fork-mode调试选项参数用于指示fork子进程后GDB对父进程还是子进程进行调试。

set follow-fork-mode [parent|child]  

parent: fork后继续调试父进程,子进程不受影响,默认值。

child: fork后调试子进程,父进程不受影响。

如果不设置follow-fork-mode选项,GDB默认调试父进程。

(2)detach-on-fork选项

detach-on-fork调试选项参数用于指示GDB在fork后是否detach某个进程的调试还是都交由GDB控制。

set detach-on-fork [on|off]

on:断开调试follow-fork-mode指定的进程。

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

(3)进程调试模式实践

默认设置下,在调试多进程程序时GDB只会跟踪调试主进程。GDB(>V7.0)支持多进程分别以及同时调试,需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)选项。

follow-fork-mode  detach-on-fork   说明
parent               on               只调试主进程(GDB默认)
child                 on               只调试子进程
parent               off               同时调试两个进程,GDB跟踪主进程,子进程block在fork位置
child                 off               同时调试两个进程,GDB跟踪子进程,主进程block在fork位置

2、GDB进程调试命令

info inferiors

查询正在调试的进程

inferior pid

切换进程

3、子进程调试

(1)运行program,然后使用ps -ef | grep program搜索到子进程pid。

(2)启动GDB,在将子进程attach到GDB调试器上。

attach child-pid

被attach的进程会阻塞,进入T模式(ps 命令看到STATE为T),如果调试完毕使用 detach 命令就释放进程。

(3)恢复运行子进程

直接run即可    

(4)detach子进程

五、多线程调试

1、GDB多线程调试模式

GDB调试一般有两种模式:all-stop模式和no-stop模式。

(1)all-stop模式

在all-stop模式下,GDB调试程序时一旦程序因为任何原因而停止,所有的线程都会停止,而不仅仅是当前线程。通常,GDB不能step所有的线程,因为线程调度是GDB无法控制的。因此,当GDB停止可执行程序时会自动切换到触发断点的线程。

(2)no-stop模式

在no-stop模式下,GDB调试程序时一旦程序因为任何原因而停止,只有当前线程会被停止,而其它线程会继续运行。step,next等单步调试命令只对当前线程起作用。

no-stop模式设置必须在程序运行前进行设置。

如果需要打开no-stop模式,可以向~/.gdbinit添加配置文件:

#Enable the async interface

set target-async 1

#If using the CLI, pagination breaks non-stop

set pagination off

#Finall, turn it on

set non-stop on

GDB支持的命令有两种类型:同步和异步。同步的在输出提示符之前会等待程序report一些线程已经终止的信息,异步则是直接返回。

set pagination off不要出现 Type <return> to continue 的提示信息 。最后一步是打开。

(3)线程锁定模式

线程锁定模式必须在程序运行中进行设置,GDB在执行step/continue命令时,所有线程都会执行。通过设置线程锁定模式可以实现线程隔离控制。

set scheduler-locking [off/on/step]

off:不锁定任何线程,即所有线程同时执行命令;

on:只有当前被调试线程才会执行命令;

step:当执行step操作时,只有当前线程会被执行;执行continue时,所有线程会被执行。

2、GDB多线程调试命令

info threads

查看当前进程中所有线程,GDB会给每个运行中的线程分配一个id号,id号从1开始,前面带*的是当前正在调试的线程。                         

thread id

切换到线程号为id的线程,id为info threads表格中第一列的值,如果id为空,则打印当前所在的线程号。

break [,location] [,thread ]

为某个位置设置断点,多线程环境下Location对所有线程都适用。

thread apply [,ids...] [command]:

让一个线程id序列全部应用command包含的GDB命令。

thread apply all command

让所有线程执行GDB命令                                    

break filename:linenum thread all 

在所有线程相应行设置断点,注意如果主线程不会执行到该行,并且启动all-stop模式,主线程执行n或s会切换过去

show scheduler-locking 

显示当前模式

3、Linux多线程调试命令

ps aux | grep [,name]

查看名为name的进程详细信息,通常用于获取进程号。

pstree -p [,id]

列出主线程与子线程的关系

ps stack [,threadId]

查看线程栈

4、多线程调试实例

生产者消费者模型源码:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
#include <list>

pthread_mutex_t g_mutex;
std::list<int> g_pool;

void *consumer(void *param)
{
    while (1)
    {
        pthread_mutex_lock(&g_mutex);
        int result = g_pool.front();

        g_pool.pop_front();

        printf("consume %d\n", result);

        pthread_mutex_unlock(&g_mutex);
        sleep(1);
    }
    return NULL;
}

void *producer(void *param)
{
    static int n = 1;
    while (1)
    {
        pthread_mutex_lock(&g_mutex);
        g_pool.push_back(n++);
        printf("produce %d\n", n - 1);
        pthread_mutex_unlock(&g_mutex);
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t tid_c, tid_p;
    void *retval;
    g_pool.push_back(0);

    pthread_mutex_init(&g_mutex, NULL);
    pthread_create(&tid_p, NULL, producer, NULL);
    pthread_create(&tid_c, NULL, consumer, NULL);

    pthread_join(tid_p, &retval);
    pthread_join(tid_c, &retval);

    return 0;
}

编译:

g++ -g test.c -o test -pthread

调试:

gdb test

启动GDB调试环境,并载入test

watch g_pool

监视g_pool变量

display g_pool

断点时打印g_pool

run

运行程序

info threads

查看线程信息

thread 2

切换到线程2

continue

继续运行,查看线程2的g_pool的值变化情况

六、高级功能

1、独立调试信息加载

GDB调试时可以从单独的符号文件中加载调试信息。

gcc -g -o test main.c

编译带调试信息可执行文件

objcopy --only-keep-debug test test.debug

从可执行文件拷贝调试信息到test.debug

strip --strip-debug --strip-unneeded test

移除test中的调试信息

gdb -s test.debug -e test

指定调试符号信息文件和可执行文件,启动GDB

exec-file test

在GDB环境载入可执行文件

symbol-file test.debug

在GDB环境载入调试符号信息文件

objcopy --add-gnu-debuglink test.debug test

将分离的调试信息test.debug链接回可执行文件test中

addr2line -e test 0x401c23

addr2line读取调试信息

2、dump数据

dump binary value file_name variable_name

将内存数据拷贝到文件里

dump binary memory file_name begin_addr end_addr

改变内存数据

3、输出重定向

set logging on

将输出保存到默认gdb.txt文件中

set logging file log_file

设置输出文件

4、自定义命令

在GDB调试环境定义一个命令hello:

define hello
print "welcome"
print "hello $arg0"
end

调用hello命令:

hello world

输出结果:

$1 = "welcome"

$2 = "hello world"

 5、定义命令钩子

钩子用于在执行某个命令前或命令后,先执行某个或某些命令。

假如想在print命令前显示一段 “----------”,则: 

define hook-print   

echo ----------/n   

end   

hook-后接的必须是命令全称。 
如果想在命令执行完,再执行某个或某些命令,则: 

define hookpost-print   

echo ----------/n   

end   

6、宏查看

默认情况下,在GDB中是不能查看宏的值及定义的,可以通过如下方法查看:

(1)编译源代码时,加上“-g3 -gdwarf-2”选项,必须为“-g3”。

(2)查看宏的值使用命令print macro_name。

(3)查看宏定义,使用macro expand macro_name命令。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐