您的位置:首页 > 运维架构 > Linux

Linux GDB core高级调试

2017-07-20 00:57 411 查看
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。
一般来说,GDB主要帮忙你完成下面四个方面的功能:
    1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。

    2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)

    3、当程序被停住时,可以检查此时你的程序中所发生的事。

    4、动态的改变你程序的执行环境。
当linux系统输出一个程序的错误类型为“Segmentation
fault”时,代表该错误为段错误,注意它说的是“段错误”,不是“段错误(核心已转储)”。因此我们想要一个核心转储文件用来调试。(核心转储文件core是进程内存的拷贝
– 这个名字来源于磁芯存储器时代 – 可用调试器分析),下面的几部可以开始GDB的高级调试之旅。

GDB core调试技巧

1. 检查core配置

在终端输入代码查看当前core文件的配置:

# ulimit -c
0
# cat /proc/sys/kernel/core_pattern
core


ulimit -c 显示核心转储文件大小的最大值,这里是零:禁止核心转储(对于本进程和它的子进程)。

/proc/…/core_pattern 仅仅被设为 “core”,表示会在当前目录下生成一个文件名为 “core
的 核心转储文件。目前这样就行了,但是我要演示如何把它设置为全局位置。

2. 配置core文件

# ulimit -c unlimited
# mkdir /var/cores
# echo "/var/cores/core.%e.%p" > /proc/sys/kernel/core_pattern


你可以进一步定制 core_pattern;例如,%h 为主机名,%t 为转储的时间。这些选项被写在 Linux 内核源码
Documentation/sysctl/kernel.txt中。

要使 core_pattern 保持不变,重启之后仍然有效,你可以通过设置 /etc/sysctl.conf 里的 “kernel.core_pattern
实现。

以上过程也可以替换如下:

1、需要在/etc/security/limits.conf中添加:

* soft core 1000000
* hard core 1000000


2、在.bash_profile里有设置

ulimit -c unlimited  //表示可以生成99999大小的core文件。0表示不生成
ulimit -c 99999


3、设置生成文件名:

echo "/tmp/core-%e-%p-%t" >/proc/sys/kernel/core_pattern


3. 使配置生效

source  /etc/security/limits.conf
source .bash_profile
source /proc/sys/kernel/core_pattern

4. 复现错误 

5. 使用GDB调试core文件

假设你的可执行程序名为test,则在命令行执行:gdb
test core.15667

# gdb test core.15667
GNU gdb Fedora (6.8-27.el5)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...

warning: Can't read pathname for load map: Input/output error.
…………………(中间还很多内容,此处省略)……………………………
Loaded symbols for /usr/lib/libgpg-error.so.0
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
[New process 15668]
#0  0x0804c760 in thread _handler () at test.cpp:707
707                             CDev* cur_dev = *it_d;
看到最后两行显示了出错的行数和信息,获得更加详细的调用堆栈,可以使用bt指令:
(gdb) bt
就会得到类似于下面的信息:
#0  0x0804c760 in thread _handler () at test.cpp:707
#1  0x006b149b in start_thread () from /lib/libpthread.so.0
#2  0x0060842e in clone () from /lib/libc.so.6
这样最后的调用堆栈即得到了错误点和错误函数,完成调试过程。

附: GDB高级调试技巧

1. 条件断点

设置一个条件断点,条件由cond指定;在gdb每次执行到此断点时,cond都被计算。当cond的值为非零时,程序在断点处停止。

用法: break [break-args] if (condition)

例如:

break main if argc > 1

break 180 if (string == NULL && i < 0)

break test.c:34 if (x & y) == 1

break myfunc if i % (j+3) != 0

break 44 if strlen(mystring) == 0
b 10 if ((int)$gdb_strcmp(a,"chinaunix") == 0)

b 10 if ((int)aa.find("dd",0) == 0)

2. condition

可在我设置的条件成立时,自动停止当前的程序,先使用break(或者watch也可以)设置断点,然后用condition来修改这个断点的停止(就是断)的条件。

用法:
condition <break_list> (conditon)

例如:

cond 3 i == 3

condition 2 ((int)strstr($r0,".plist") != 0)

3. ignore

如果我们不是想根据某一条件表达式来停止,而是想断点自动忽略前面多少次的停止,从某一次开始才停止,这时ignore就很有用了。

用法:

ignore <break_list> count

上面的命令行表示break_list所指定的断点号将被忽略count次。

例如:

ignore 1 100,表示忽略断点1的前100次停止

4. 为断点设置命令列表

设置一个断点并且在上面中断后,我们必须会查询一些变量或者做一些其他动作。如果这些动作可以一起呵成,岂不妙哉!使用命令列表(commands)就能实现这个功能。

步骤:

1.建立断点

2.使用commands命令

用法:
commands <break_list>

例如:

(gdb) commands 1

Type commands for when breakpoint 1 is hit,one per line.

End with a line saying just "end".

>silent

>print "n= %d \n",n

>continue

>end

5. 查看宏 

默认情况下,在GDB中是不能查看宏的值及定义的,但通过如下方法,则可以达到目的: 
编译源代码时,加上“-g3
-gdwarf-2”选项,请注意不是“-g”,必须为“-g3”,查看宏的值使用命令p,这和查看变量的值的方法相同,如果想查看宏的定义,使用 “-macro expand”命令即可 

6. gdbinit文件 

GDB在启动时,会在用户主目录中寻找这个文件,并执行该文件中所有命令,文件格式为: 
define command-alias
command
end
如要给“b main”取一个别名“bm”: 
define bm
b main
end
此外,还可以给这个别名加上帮助说明性文字,格式为: 
document bm
帮助说明性文字
end
如,给“bm”别名添加帮助说明性文字: 
document bm
break at main() function
end
这样,在使用“help bm”时,GDB就会打印出“break at main() function” 

7. 定义命令钩子 

钩子用来在执行某个命令前或命令后,先执行某个或某些命令。假如想在print命令前显示一段 “----------”,则: 
define hook-print
echo ----------/n
end
注意“hook-”后接的必须是命令全称,不能是缩写。 
如果想在命令执行完,再执行某个或某些命令,则: 
#define hookpost-print
echo ----------/n
end

8. 查看动态数组的取值

p *array@len
  @的左边是数组的首地址的值,也就是变量array所指向的内容,右边则是数据的长度,其保存在变量len中,其输出结果,大约是下面这个样子的:    

    (gdb) p *array@len

    $1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40} 

9. 查看内存

可以使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:

    x/<n/f/u> <addr>    

    n、f、u是可选的参数。

    n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。

    f 表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。

    u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。

    <addr>表示一个内存地址

    n/f/u三个参数可以一起使用。例如:    

    命令:x/3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示三个单位,u表示按十六进制显示。

10. gdb 内存断点设置。

rwatch, watch, awatch
分别代表读,写,读写内存断点,用的是硬件断点。

11. gdbtui 
gdb 图形化接口。方便调试

启动gdb, 按ctrl-x ctl-a 也可进入tui 模式

12. 与调试控制相关的命令

continue    继续运行程序直到下一个断点(类似于VS里的F5)

next        逐过程步进,不会进入子函数(类似VS里的F10)

step        逐语句步进,会进入子函数(类似VS里的F11)

until        运行至当前语句块结束

finish  运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)

13. gdb 调试跟踪多进程程序

gdb只能跟踪一个进程(默认是跟踪父进程),而不能同时跟踪多个进程,
可以设置gdb跟踪父进程还是子进程, 命令如下:
set follow-fork-mode parent 跟踪父进程, 默认
set follow-fork-mode child 跟踪子进程

14. 简单的函数桩

通过GDB,我们可以变相的实现简单的函数桩,且较为便捷。

基本思路就是在需要打桩的函数上设置断点,待进入函数后,根据对函数的打桩需求,可以分别采取如下措施:
(1)直接返回:用GDB的return命令强制返回即可,如果有返回值,直接跟在return后面即可,如:
return 5

(2)执行函数的部分逻辑:这需要借助GDB的执行路径篡改功能。
“jump <行号>”直接跳至对应的代码行继续执行,也可以用“jump *<地址>”跳转到机器指令地址。
Tip:在x86开发环境下,即使像VC这种不支持“执行路径篡改”功能的调试器,也可以通过直接修改寄存器PC的值达到跳转的目的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: