GDB 的秘密(九)
在上节博客中,我们学习了链接器的知识。那么本节课我们继续学习嵌入式开发中的一些常用的操作,今天我们学习的是在 GNU 开发中,必不可少的调试利器 GDB。可能搞底层的同志或多或少的听过 GDB,但是觉得它总是那么的神秘,很模糊。那么我们今天就来看看 GDB。
先从它的概念开始介绍,什么是 GDB 呢?它是 GNU项目中的调试器(gnu debuger),它的作用是能追踪程序的执行,也能恢复程序崩溃前的状态。那么我们为什么需要 GDB 呢?在我们日常的软件开发中,难免会写出一些难以发现的 bug,那么这些 bug 又是昙花一现,马上就结束了。我们来不及反应程序就崩溃了,因此我们找不到 bug 的来源。因此我们需要在软件开发的过程中进行调试,这也便是 gdb 的由来。
下来我们来看看 GDB 的一些常规性应用,一般包括以下几方面:
1、自定义程序的启动方式(指定影响程序运行的参数);
2、设置条件断点(在条件满足时暂停程序的执行);
3、回溯检查导致程序异常结束的原因(Core Dump);
4、动态改变程序执行流(定位问题的辅助方式)。
那么 GDB 又是怎样进行启动的呢?它的启动方式可以大致分为两种:直接启动和动态连接。直接启动又分为三种:gdb; gdb test.out; gdb test.out core; 动态连接:gdb test.out pid
下来我们来看看 GDB 应用的一个示例,如下
介绍了它的启动方式后,我们再来看看用它如何进行断点调试。在介绍断点调试之前,我们首先来看看在 GNU 中的断点类型。它分为三种:软件断点、硬件断点、数据断点。软件断点是由非法指令异常实现的(也即是通过软件实现),硬件断点和数据断点则是由硬件特性实现(共同的特点是数量有限)。
接下来我们来看看软件断点的相关操作:
1、通过函数名设置断点:
如 a> break func_name [ if var = value ]
b> tbreak func_name [ if var = value ]
2、通过文件名行号设置断点:
如 a> break func_name:line_num [ if var = value ]
b> tbreak func_name:line_num [ if var = value ]
上面的 break 和 tbreak 两种方式的区别是,tbreak 设置的是临时断点,而 break 设置的则是永久断点。下面我们继续来看看断点操作的一些常用命令
介绍了软件断点的相关操作之后,我们来继续介绍硬件断点的操作及应用。那么我们是在什么样的情况下才会去使用硬件断点呢?1、当代码位于只读存储器(Flash)时,只能通过硬件断点调试;2、硬件断点需要硬件支持,数量有限;3、GDB 中通过 hbreak 命令支持硬件断点;4、hbreak 与 break 的使用方式是完全一致的。
我们先来看看数据断点:在 GDB 中支持数据断点的设置,watch 命令用于监视变量是否被改变(其本质也为硬件断点)。watch 命令的用法:watch var_name,在 GDB 中可以检查任意内存区域中的数据。命令语法:x /Nuf experssion ,其中 N 是需要打印的单元数,u 指的是每个单元的大小,f 指的是数据打印的格式。我们来看看 x 命令中参数 u 对应的单位,如下所示
接下来我们来看看 GDB 中的打印格式,如下图所示
在这块我们可以利用这个特性用来判断系统大小端,示例代码如下
如果是上面的那种情况,那么此系统就是小端;反之则是大端。接下来看看函数调用栈的查看(backtrace 和 frame)。
backtrace 是用来查看函数调用的顺序(函数调用栈的信息);frame N 则是切换到栈编号为 N 的上下文中;info frame 是用来查看当前函数站调用的栈帧信息的。栈帧信息就是我们之前在 C 语言中讲到的函数活动记录,如下
我们再来深入的看看 info 命令,如下
那么在调试中还有一些小技巧,比如在断点处自动打印:display /f expression,相应的去除打印就是:undisplay;查看程序中的符号:whatis,ptype;GDB 中的代码查看:list,set listsize N;GDB 中的 shell 操作:shell。看看断点出自动打印的示例,如下
符号查看的示例如下
通过今天对 GDB 的学习,总结如下:1、GDB 支持数据断点的设置(一种类型的硬件断点);2、watch 用于监视变量是否被改变,x 用于查看内存中的数据;3、GDB 支持函数调用栈的查看(backtrace,info frames);4、GDB 支持运行时对程序中的符号进行查看(whatis,ptype)。