一、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
调试模式启动programgdb 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
启动GDBfile program
载入目标可执行文件set args arg1 arg2
设置命令行参数run
执行目标程序attach pid
链接到目标进程,链接成功后目标进程将停止执行continue
恢复执行目标进程stop
停止运行
3、信息显示
show version
显示GDB版本 gdb -q
不显示提示信息启动GDBset 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
为系统调用设置catchpointcatch 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
在文件行号上打断点
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
设置观察点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调试环境,并载入testwatch g_pool
监视g_pool变量display g_pool
断点时打印g_poolrun
运行程序info threads
查看线程信息thread 2
切换到线程2continue
继续运行,查看线程2的g_pool的值变化情况
六、高级功能
1、独立调试信息加载
GDB调试时可以从单独的符号文件中加载调试信息。gcc -g -o test main.c
编译带调试信息可执行文件objcopy --only-keep-debug test test.debug
从可执行文件拷贝调试信息到test.debugstrip --strip-debug --strip-unneeded test
移除test中的调试信息gdb -s test.debug -e test
指定调试符号信息文件和可执行文件,启动GDBexec-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命令。