gdb介绍
?
GDB 的全称是GNU Debuger,是linux 底下的一种免費的debug程序,没有界面,当然Linux也有带界面的比如cgdb、kdbg、ddd和insight debugger,在使用gdb调试的时候我们需要在编译程序的时候生成调试信息,比如:
gcc -Wall -g3 -o test test.c
gcc生成调试信息的一些选项:
-g 和 -ggdb 也是分级别的
-
-g2:这是默认的级别,此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息。 -
-g3:包含级别2中的所有调试信息,以及源代码中定义的宏,以及C++中的内联函数(inline)。 -
-g1:级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段。
一般我比较习惯使用-g3。
?
启动gdb
?
调试可执行文件:
$gdb <program>
program也就是你的执行文件,一般在当前目录下。
调试core文件(core是程序非法执行后core dump后产生的文件):
$gdb <program> <core dump file>
$gdb program core.11127
调试服务程序:
$gdb <program> <PID>
$gdb hello 11127
如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。
?
gdb命令
?
启动gdb后,进入到交互模式,通过以下命令完成对程序的调试;注意高频使用的命令一般都会有缩写,熟练使用这些缩写命令能提高调试的效率;
运行相关命令
-
run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令,run后可以带一些命令,比如我们要调试brks程序,我们第一步是gdb brks,进入gdb后,我们输入run log.conf去运行程序。 -
continue (简写c ):继续执行,到下一个断点处(或运行结束) -
next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。 -
step (简写s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的。 -
until:当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。 -
until+行号: 运行至某行,不仅仅用来跳出循环。 -
finish: 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。 -
call 函数(参数):调用程序中可见的函数,并传递“参数”,如:call gdb_test(55) -
quit:简记为 q ,退出gdb
设置断点
-
break n (简写b n):在第n行处设置断点 (可以带上代码路径和行号: b OAGUPDATE.cpp:578) -
b fn1 if a>b:条件断点设置 -
break func(break缩写为b):在函数func()的入口处设置断点,如:break cb_button -
delete 断点号n:删除第n个断点 -
disable 断点号n:暂停第n个断点 -
enable 断点号n:开启第n个断点 -
clear 行号n:清除第n行的断点 -
info b (info breakpoints) :显示当前程序的断点设置情况 -
delete breakpoints:清除所有断点:
查看源代码
-
list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。 -
list 行号:将显示当前文件以“行号”为中心的前后10行代码,如:list 12 -
list 函数名:将显示“函数名”所在函数的源代码,如:list main -
list :不带参数,将接着上一次 list 命令的,输出下边的内容。
打印表达式
-
print 表达式:简记为 p ,其中“表达式”可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么“表达式”可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。 -
print a:将显示整数 a 的值 -
print ++a:将把 a 中的值加1,并显示出来 -
print name:将显示字符串 name 的值 -
print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数 -
print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数 -
display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值。如: display a -
watch 表达式:设置一个监视点,一旦被监视的“表达式”的值改变,gdb将强行终止正在被调试的程序。如: watch a -
whatis :查询变量或函数 -
info function: 查询函数 -
扩展info locals: 显示当前堆栈页的所有变量
查询运行信息
分割窗口
?
coredump是什么
?
??程序异常退出时,会产生一个core文件,该文件记录了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息还有各种函数调用堆栈信息等,我们可以理解为是程序工作当前状态存储生成的一个文件,通过工具分析这个文件,我们可以定位到程序异常退出的时候对应的堆栈调用等信息,找出问题所在并进行及时解决。
??上面说当程序运行过程中异常终止或崩溃时会发生 core dump,但还没说到什么具体的情景程序会发生异常终止或崩溃,例如我们使用 kill -9 命令杀死一个进程会发生 core dump 吗?实验证明是不能的,那么什么情况会产生呢? ??Linux 中信号是一种异步事件处理的机制,每种信号对应有其默认的操作,你可以在 这里 查看 Linux 系统提供的信号以及默认处理。默认操作主要包括忽略该信号(Ingore)、暂停进程(Stop)、终止进程(Terminate)、终止并发生core dump(core)等。如果我们信号均是采用默认操作,那么,以下列出几种信号,它们在发生时会产生 core dump,这个可以通过man 7 signal: ??当然不仅限于上面的几种信号。这就是为什么我们使用 Ctrl+z 来挂起一个进程或者 Ctrl+C 结束一个进程均不会产生 core dump,因为前者会向进程发出 SIGTSTP 信号,该信号的默认操作为暂停进程(Stop Process);后者会向进程发出SIGINT 信号,该信号默认操作为终止进程(Terminate Process)。同样上面提到的 kill -9 命令会发出 SIGKILL 命令,该命令默认为终止进程。而如果我们使用 Ctrl+\ 来终止一个进程,会向进程发出 SIGQUIT 信号,默认是会产生 core dump 的。还有其它情景会产生 core dump, 如:程序调用 abort() 函数、访存错误、非法指令等等。
?
前期设置
?
-
设置core文件生成的目录,其中%e表示程序文件名,%p表示进程ID,否则会在程序的当前目录生成dore文件;
echo /data/coredump/core.%e.%p >/proc/sys/kernel/core_pattern ?
-
当前执行程序的用户对core目录有写权限且有足够的空间存储core文件; -
生成不受限制的core文件,ulimit -c unlimited,这个只会对当前的终端有效,如果想永久有效,需要修改文件:/etc/security/limits.conf,增加一行,如下红圈所示:
?
使用gdb调试coredump文件
?
产生了 core 文件,我们该如何使用该 Core 文件进行调试呢?Linux 中可以使用 GDB 来调试 core 文件,命令如下:
gdb program core
program是程序名称,core是coredump文件。OK,我们来动手吧,动手,动手,动手,当你看见一个前凸后翘的美女时,你先去了解她,制定攻略,那接下来是不是要动手实践呢?so 动手,只看书不动手永远很难了解哪些理论,即使水平再高也需要动手解决问题,才能有更深刻的认识,其实,也就是说再漂亮的女人,也需要个男人对她动粗,你说是不是? ??比如: 那都有哪些操作命令可以调试这个coredump文件呢?
-
bt或者where查看调用栈信息 如果你要查看某一层的信息,你需要切换当前的栈,一般来说,程序停止时,最顶层的栈就是当前栈,如果你要查看栈下面层的详细信息,首先要做的是切换当前栈。 -
frame <n>和 f <n>:?n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。 -
up <n>: 表示向栈的上面移动n层,可以不打n,表示向上移动一层。 -
down <n>: 表示向栈的下面移动n层,可以不打n,表示向下移动一层。
上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令:
select-frame <n> 对应于 frame 命令。
up-silently <n> 对应于 up 命令。
down-silently <n> 对应于 down 命令。
查看当前栈层的信息,你可以用以下GDB命令:
?
奔溃日志
?
生成coredump去用gdb调试还是依赖我们是否生成调试信息,如果我们的没有添加-g选项就麻烦了,此时我们可以怎么办呢? ??上文说过程序崩溃(core)了是由一些信号触发的,这些信号也许是因为用户操作,也许是我们程序有一些错误的指令(比如对空指针的赋值,除数为零)触发的。那么我们程序实际上是可以捕获这些信号的,然后去打印堆栈的信息,how to do? ??
signal是一个截取信号的函数,它的申明如下:
#include <signal.h>
?
typedef void (*sighandler_t)(int);
?
sighandler_t signal(int signum, sighandler_t handler);
signal截取信号,然后调用回调函数handler处理信号,那么其实我们就可以在handler函数里打印堆栈信息,backtrace获取堆栈的大小,并且把堆栈的信息放在array指向的内存中,然后调用backtrace_symbols把堆栈中的函数信息转换成函数名。例如:
int main(int argc, char** argv)
{
? signal(SIGSEGV, handle_segv); // SIGSEGV ? 11 Core Invalid memory reference
? signal(SIGABRT, handle_segv); // SIGABRT ? 6 ? ? ? Core Abort signal from
signal(SIGINT, handle_segv);
signal(SIGTSTP, handle_segv);
signal(SIGTERM, handle_segv);
?
? //...
return 0;
}
void handle_segv(int signum)
{
? void *array[100];
? size_t size;
? char **strings;
? size_t i;
? signal(signum, SIG_DFL); /* 还原默认的信号处理handler */
?
? size = backtrace (array, 100);
? strings = (char **)backtrace_symbols (array, size);
// 这里是打印到日志中,其实也可以打印到某个文件中
? LOG_INFO("Launcher received SIG: %d Stack trace:\n", signum);
? for (i = 0; i < size; i++)
? {
? ? ? LOG_ERROR("%d %s \n",i,strings[i]);
? }
?
? free (strings);
//g_launcher->stop();
}
从上文,我们也可以看出我们在程序崩溃前可以做一些善后处理。
最后,链接一本电子书给大家https://wizardforcel.gitbooks.io/100-gdb-tips/index.html
|