GNU开发工具——GDB快速入门

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GNU开发工具——GDB快速入门相关的知识,希望对你有一定的参考价值。

GNU开发工具——GDB快速入门

一、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
查看当前函数参数的值
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 &lt;win_name&gt; [+ | -]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

在文件行号上打断点

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 &lt;break_list&gt; 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
设置观察点
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 &lt;return&gt; 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
", 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 - 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命令。

以上是关于GNU开发工具——GDB快速入门的主要内容,如果未能解决你的问题,请参考以下文章

代码调试篇:gdb调试快速入门指南

gdb入门

CMake快速入门

GNU开发工具——CMake快速入门

使用GNU/gdb调试Linux C/C++可执行程序查看出错源代码、设置断点

gdb调试技巧gdb入门