最近程序在linux系统中出现程序的进程意外死掉的现象,查看日志发现是信号量异常致使。结合进程死机异常产生的core文件和程序版本经过GDB工具进行程序的调试,发现是内存的问题。linux
为了更好的使用GDB工具,查阅资料进行总结,也但愿可以帮助开始使用GDB的新手。c++
GDB就是一个帮助程序猿调试程序的工具,相似于windows下的IDE自带的调试工具(如断点调试、内存查看、堆栈信息显示等)。正则表达式
通常来讲 GDB 主要调试的是 C/C++的程序。要调试 C/C++的程序,首先在编译时,必需要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数能够作到这一点。shell
> cc -g hello.c -o helloexpress
> g++ -g hello.cpp -o hello
若是没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址windows
一、gdb <program>
program 也就是你的执行文件,通常在当前目录下。数组
二、gdb <program> core
用gdb同时调试一个运行程序和core 文件,core 是程序非法执行后core dump 后产生的文件。bash
三、gdb <program> <PID>
若是你的程序是一个服务程序或者已经在运行了,那么能够指定这个服务程序运行时的进程 ID。gdb 会自动attach 上去,并调试他。program 应该在PATH 环境变量中搜索获得函数
也能够这样:先用 gdb <program>关联上源代码,并进行 gdb,在 gdb 中用 attach 命令来挂接进程的 PID。并用detach 来取消挂接的进程工具
l <-------------------- l 命令至关于list,从第一行开始例出原码 <-------------------- 直接回车表示,重复上一次命令 break 16 <-------------------- 设置断点,在源程序第16行处 break func <-------------------- 设置断点,在函数func()入口处 r <--------------------- 运行程序,run 命令简写 n <--------------------- 单条语句执行,next命令简写 c <--------------------- 继续运行程序,continue 命令简写 p i <--------------------- 打印变量i的值,print命令简写 bt <--------------------- 查看函数堆栈 finish <--------------------- 退出函数 q <--------------------- 退出 gdb
shell <command string>
如:(gdb) shell $PWD
bash: /home/duxx/test_gdb: is a directory
make <make-args> 等价于“shell make <make-args>”
set args 可指定运行时参数。
show args 命令能够查看设置好的运行参数。
(gdb) set args 5
(gdb) show args
Argument list to give program being debugged when it is started is "5".
path <dir> 可设定程序的运行路径。
show paths 查看程序的运行路径。
set environment varname [=value] 设置环境变量。如:set env USER=hchen
咱们用break 命令来设置断点。正面有几点设置断点的方法:
break <function> eg:break func()
在进入指定函数时停住。C++中可使用class::function 或 function(type,type)格式来指定函数名。
break <linenum> eg:break 8
在指定行号停住。
break +offset (当前行号+offset)
break -offset (当前行号-offset)
在当前行号的前面或后面的offset行停住(是在程序运行前和运行时均可设置)。offiset 为天然数。
break filename:linenum eg:break test.cpp:18
在源文件filename 的linenum 行处停住。
break filename:function eg:break test.cpp:func()
在源文件filename 的function 函数的入口处停住。
break *address eg:break 0x0804895b
在程序运行的内存地址处停住。
break
break 命令没有参数时,表示在下一条指令处停住。
break ... if <condition>
...能够是上述的参数,condition 表示条件,在条件成立时停住。好比在循环境体中,能够设置
break if i=100,表示当i为 100 时停住程序。
查看断点时,可以使用info 命令,以下所示:(注:n 表示断点号)
info breakpoints [n]
info break [n]
eg:
1 breakpoint keep y 0x080488bd in func() at test.cpp:19
breakpoint already hit 1 time
2 breakpoint keep y 0x08048852 in test1() at test.cpp:8
3 breakpoint keep y <PENDING> +2
观察点通常来观察某个表达式(变量也是一种表达式)的值是否有变化了,若是有变化,立刻停住程序。咱们有下面的几种方法来设置观察点:
watch <expr> 为表达式(变量)expr设置一个观察点。当表达式值有变化时,立刻停住程序。
rwatch <expr> 当表达式(变量)expr被读时,停住程序。
awatch <expr> 当表达式(变量)的值被读或被写时,停住程序。
info watchpoints 列出当前所设置了的全部观察点
注意:设置观察点时,要在其观察的变量处设置断点,而后run程序,走到此断点时,才可设置观察点,continue观察设置的观察点是否有变化
clear
清除全部的已定义的中止点,测试不能所有删除,使用delete便可
clear <function>
clear <filename:function>
清除全部设置在函数上的中止点。
clear <linenum>
clear <filename:linenum>
清除全部设置在指定行上的中止点。
delete [breakpoints] [range...]
删除指定的断点,breakpoints 为断点号。若是不指定断点号,则表示删除全部的断点。range 表示断点号的范围(如:3-7)。其简写命令为d。
能够在设置断点的时候加入条件
break foo if value_a > value_b
也能够用 condition 命令来修改断点的条件。(只有break 和watch 命令支持if,catch 目前暂不支持if)
condition <bnum> <expression> eg: condition 3 i=5(不须要 if)
修改断点号为bnum的中止条件为expression
condition <bnum>
清除断点号为bnum的中止条件。
还有一个比较特殊的维护命令ignore,你能够指定程序运行时,忽略中止条件几回。
ignore <bnum> <count>
表示忽略断点号为bnum的中止条件count次。
bt 打印当前的函数调用栈的全部信息 调用顺序:从下向上依次调用
bt <n>
n 是一个正整数,表示只打印栈顶上n个层的栈信息。
-n 表一个负整数,表示只打印栈底下n个层的栈信息 eg:
(gdb) bt <------------------------栈的信息 #0 test1 () at test.cpp:8 <------------------------栈的第0层(栈顶) #1 0x080488f6 in func () at test.cpp:22 <------------------------栈的第1层 #2 0x0804895b in main (argv=1, argc=0xbffff4c4) at test.cpp:33 (gdb) bt 2 <------------------------打印栈顶2个层的栈信息 #0 test1 () at test.cpp:8 #1 0x080488f6 in func () at test.cpp:22 (More stack frames follow...)
frame <n>
f <n> n 是一个从0 开始的整数,是栈中的层编号。好比:frame 0,表示栈顶,frame 1,表示栈的第二层。
up <n> 表示向栈的上面移动 n层,能够不打n,表示向上移动一层。
down <n> 表示向栈的下面移动n 层,能够不打 n,表示向下移动一层。
info frame 会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句
info args 打印出当前函数的参数名及其值。
info locals 打印出当前函数中全部局部变量及其值。
info catch 打印出当前的函数中的异常处理信息
(gdb) info frame <------------------ 打印出当前栈的详细信息 Stack level 0, frame at 0xbffff3f0: eip = 0x8048852 in test1 (test.cpp:8); saved eip = 0x80488f6 called by frame at 0xbffff410 source language c++. Arglist at 0xbffff3e8, args: Locals at 0xbffff3e8, Previous frame's sp is 0xbffff3f0 Saved registers: ebx at 0xbffff3e4, ebp at 0xbffff3e8, eip at 0xbffff3ec (gdb) f 0 <------------------ 打印栈顶信息和代码 #0 test1 () at test.cpp:8 8 int* ppp = (int *)new int[10]; (gdb) f 2 #2 0x0804895b in main (argv=1, argc=0xbffff4c4) at test.cpp:33 33 func(); (gdb) up 1 <------------------ 当前栈的指向向上移动一层即#1的栈 #1 0x080488f6 in func () at test.cpp:22 22 test1(); (gdb) info frame <------------------ 打印当前栈的详细信息 Stack level 1, frame at 0xbffff410: eip = 0x80488f6 in func (test.cpp:22); saved eip = 0x804895b called by frame at 0xbffff440, caller of frame at 0xbffff3f0 source language c++. Arglist at 0xbffff408, args: Locals at 0xbffff408, Previous frame's sp is 0xbffff410 Saved registers: ebp at 0xbffff408, eip at 0xbffff40c
在程序编译时必定要加上-g 的参数,把源程序信息编译到执行文件中,list简写l。
list <linenum>
显示程序第linenum行的周围的源程序(通常是打印当前行的上 5 行和下 5 行)。
list <function>
显示函数名为function 的函数的源程序(若是显示函数是是上 2 行下 8 行,默认是 10 行)。
list
显示当前行后面的源程序(默认行数)。
list -
显示当前行前面的源程序(默认行数)。
也能够定制显示的范围,使用下面命令能够设置一次显示源程序的行数。
set listsize <count>
设置一次显示源代码的行数。
show listsize
查看当前listsize 的设置。
list 命令还有下面的用法:
list <first>, <last> 显示从first行到last行之间的源代码。
list , <last> 显示从当前行到 last 行之间的源代码(包含首尾行)。
list + 日后显示源代码(默认行数)。
通常来讲在list 后面能够跟如下这们的参数:
<linenum> 行号。
<+offset> 当前行号的正偏移量。
<-offset> 当前行号的负偏移量。
<filename:linenum> 哪一个文件的哪一行。
<function> 函数名。
<filename:function> 哪一个文件中的哪一个函数。
<*address> 程序运行时的语句在内存中的地址。
forward-search <regexp>
search <regexp>
向前面搜索。
reverse-search <regexp>
所有搜索。
其中,<regexp>就是正则表达式,也主一个字符串的匹配模式
查看程序的变量或地址
一、全局变量(全部文件可见的) eg: p g
二、静态全局变量(当前文件可见的)
三、局部变量(当前Scope 可见的) eg: p m
若是你的局部变量和全局变量发生冲突(也就是重名),通常状况下是局部变量会隐藏全局变量,查看格式:file::variable eg: p 'test.cpp'::m
查看某个函数的变量:function::variable eg:没有成功
数组查看 “@”
格式:*内存地址@内存长度 “@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度
int arr[]={2,23,5,6,7,8}; (gdb) p *ppp@10 $2 = {1, 2, 0, 0, 0, 0, 0, 0, 0, 0} int* ppp = (int *)new int[10]; (gdb) p *arr@6 $3 = {2, 23, 5, 6, 7, 8}
输出格式
用GDB的数据显示格式:
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
(gdb) p m $5 = 10 (gdb) p/x m $6 = 0xa (gdb) p/o m $7 = 012 (gdb) p/c m $8 = 10 '\n' (gdb) p/f m $9 = 1.40129846e-44
查看内存的值 examine
格式:x/<n/f/u> <addr> examine 命令(简写是x)来查看内存地址中的值
n、f、u是可选的参数
n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。
f 表示显示的格式,参见上面。若是地址所指的是字符串,那么格式能够是 s,若是地十是指令地址,那么格式能够是 i。
u 表示从当前地址日后请求的字节数,若是不指定的话,GDB 默认是 4 个 bytes。u 参数能够用下面的字符来代替,b 表示单字节,h 表示双字节,w 表示四字节,g 表示八字节。当咱们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其看成一个值取出来。
<addr>表示一个内存地址
(gdb) p/x m (查看变量m的值) $10 = 0xa (gdb) p/x &m (查看变量m的地址) $11 = 0xbffff3d8 (gdb) x 0xbffff3d8 (根据内存地址查看其值) 0xbffff3d8: 0x0000000a (gdb) x/4ub 0xbffff3d8 (查看此内存后4个单字节的值) 0xbffff3d8: 10 0 0 0 (gdb) x/2uh 0xbffff3d8 (查看此内存后2个双字节的值) 0xbffff3d8: 10 0
GDB环境变量设置与查看
一、GDB的环境变量和UNIX 同样,也是以$起头
二、环境变量能够定义任一的类型。包括结构体和数组
三、查看当前所设置的全部的环境变量 show convenience
四、查看单一变量 p $变量名 eg:p $foo
eg: set $foo = 12
set $pp="abcdef"
查看寄存器的值
info registers 查看寄存器的状况。(除了浮点寄存器)
info all-registers 查看全部寄存器的状况。(包括浮点寄存器)
info registers <regname ...> 查看所指定的寄存器的状况。
p $寄存器名字 看看指定的寄存器的状况 eg: p $eip
使用GDB的 print 命令便可完成:eg (gdb) print x=4【C/C++的语法,Pascal的语法:x:=4】
变量和GDB中的参数冲突,set var 命令来修改:eg set var width=47
GDB 能够修改程序的执行顺序,可让程序执行随意跳跃。
jump <linespec>
指定下一条语句的运行点。<linespce>能够是文件的行号,能够是 file:line 格式,能够是+num 这种偏移量格式
jump <address>
这里的<address>是代码行的内存地址。
注意,jump 命令不会改变当前的程序栈中的内容,因此,当你从一个函数跳到另外一个函数时,当函数运行完返回时进行弹栈操做时必然会发生错误,可能结果仍是很是奇怪的,甚至于产生程序 CoreDump。因此最好是同一个函数中进行跳转
使用 singal 命令,能够产生一个信号量给被调试的程序
语法是:signal <singal>,UNIX 的系统信号量一般从1 到 15。因此<singal>取值也在这个范围
注意:single 命令和 shell 的 kill 命令不一样,系统的 kill 命令发信号给被调试程序时,运行程序立刻会被GDB停住,而single 命令所发出一信号则是直接发给被调试程序的
四、强制函数返回
return
return <expression>
使用 return 命令取消当前函数的执行,并当即返回,若是指定了<expression>,那么该表达式的值会
被认做函数的返回值。
call <expr>
表达式中能够一是函数,以此达到强制调用函数的目的。并显示函数的返回值,若是函数返回值是
void,那么就不显示。
最后给你们推荐一本书:陈皓的《用GDB调试程序》。但愿对你们有所帮助。