前言:

linux环境下几乎只有gdb可用,windows环境下windbg也是以gdb作为底部承载,因此gdb的重要作用可见一斑,但gdb作为基础套件,其不具备界面展示,因此使用起来不是很方便,这里对其做简要的使用说明。

 

前提条件:

使用gdb进行调试必须满足以下至少一个条件:

1)在编译时使用了-g选项,那么得到的二进制文件便是包含调试信息的,那么此二进制文件是可调试的;

2) 编译时没使用-g选项增加调试信息,但有与此二进制文件配套的debuginfo包,那么可以将此debuginfo包与gdb一起运行,此时相当于把调试信息打入二进制文件中。

注:debuginfo也是在编译时生成的,通常是通过-g选项生成带有调试信息的二进制文件,然后再使用objcopy将调试信息从二进制文件中分离出来,后面会介绍objcopy的使用

 

DebugInfo的来龙去脉:

  • debuginfo是指gdb所使用的debug信息,比如使用gcc -g选项,编译的输出文件中就会包含debuginfo
  • 一般情况下,都是把debuginfo和可执行文件分开,因为放在一起,会导致可执行文件过大
  • 如何获得可执行文件和debuginfo呢?使用如下命令可以
objcopy --only-keep-debug ./a.out a.out.debug    

#这里拷贝出来,此时可执行文件中还是有debuginfo的,多出来一个.debug文件,原来的a.out大小不变
  • 如果把a.out瘦身呢?使用如下命令可以
objcopy --strip-debug ./a.out(也可以使用strip --strip-debug ./a.out)

#此时,在使用gdb来调试a.out的时候,已经不行了,报错no debugging symbols found
  • 该怎么使用debuginfo呢?使用如下命令即可
objcopy --add-gnu-debuglink=a.out.debug ./a.out

#此时,在使用gdb来调试a.out,发现可以了
  • 为什么加了有debuginfo ,调试的时候有些地方仍然没有debug信息

 -g 选项默认不会包括一些宏定义的信息。可以在编译时使用-g3

 

GDB使用细节:

启动并调试:            gdb program
带core文件启动并调试:   gdb program core  (执行后,停在哪,系统就down在哪)
调试一个运行中的进程:   gdb program 1234  或 gdb - 1234  (1234是进程号)
过滤gdb产品信息运行gdb:    gdb --silent
在gdb环境中运行shell命令:  shell ls
在gdb环境中运行make命令:   make all
转移gdb输出到文件中:set logging on      set logging file filename   (filename是文件名)

#1  - 命令补全,和shell一样,用TAB补全,同样有双击TAB罗列可选项的功能

#2 - 基本命令
run 启动程序
start 主函数处设置断点,启动程序,每次过来执行都会停住,相当于一个长期断点
starti 主函数处设置断点,启动程序,仅在第一个过来执行时会挺住,相当于一次性断点
set args 设置运行参数,进入交互界面后,set args arg1 arg2,依次设置参数1——arg1 和 参数2——arg2
show args 查看已设置的参数
continue 一直执行,知道下一个断点,若无断点,则一直执行到程序结束
bt 输出当前调用栈
n step over
s step into

break 设置断点
break lineno 当前文件的指定行处
break filename:lineno 特定文件的指定行处
break functionname 当前文件的指定函数入口
break filename:functionname 特定文件的指定函数入口
break *address 某个虚拟地址
break 下一条指令处
break ... if xxx 满足xxx条件,停在...处(...为上面提到的lineno、functionname等等)
比如:break printf if i>100
info 查看相关信息
info break 查看断点信息
info threads 查看线程信息
info r 查看寄存器信息

condition 为断点加条件(类似于break ... if xxx,更细化)
假设已经设置了断点3
condition 3 a>10 当a>10的时候,在3号断点停住
condition 3 清除加在3号断点上的条件(比如上面的a>10)

ignore 3 100 进入断点3的前100次将被忽略,从101次开始断点才有效


#3 - 多进程调试
默认情况下,gdb跟踪父进程
可以在任何时候设置跟踪子进程:set follow-fork-mode childe

那么exec系列函数是否能够使用gdb跟踪呢?

#4 - 多线程调试
info threads 查看所有线程(其中带*号的为当前线程)
thread 2 切换到2号线程
where 输出线程调用栈(对应bt)
break ... thread 2 为2号线程打上断点(...为之前提到的lineno、functionname等等)

 

进阶用法:

#1  输出字符串
p (char *)字符串初始地址

#2 输出完整字符串
gdb的字符串输出有长度限制,最多只能输出多少个,可使用show print elements查看
当字符串商都超过这个限制时,可以通过set print elements N,来把这个限制扩展到N个字符

#3 gdb value <optimized out>的解决方式
调整优化深度,默认优化深度为-O2,调整为-O0即可

===================================================GDB 进阶=====================================================

#1 http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html

#2 打印表达式
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: 显示当前堆栈页的所有变量

#3 查询运行信息
where/bt :当前运行的堆栈列表;
bt backtrace 显示当前调用堆栈
up/down 改变堆栈显示的深度
set args 参数:指定运行时的参数
show args:查看设置好的参数
info program: 来查看程序的是否在运行,进程号,被暂停的原因。

#4 分割窗口
layout:用于分割窗口,可以一边查看代码,一边测试:
layout src:显示源代码窗口
layout asm:显示反汇编窗口
layout regs:显示源代码/反汇编和CPU寄存器窗口
layout split:显示源代码和反汇编窗口
Ctrl + L:刷新窗口

#5 更强大的工具 -- cgdb
cgdb可以看作gdb的界面增强版,用来替代gdb的 gdb -tui。cgdb主要功能是在调试时进行代码的同步显示,这无疑增加了调试的方便性,
提高了调试效率。界面类似vi,符合unix/linux下开发人员习惯;如果熟悉gdb和vi,几乎可以立即使用cgdb。