GDB内存调试初探一

  • 背景

嵌入式应用开发过程中,经常出现应用崩溃的缺陷,这些问题绝大多数与内存相关。根本的原因是,C/C++编程语言本身是内存、线程不安全的;当然还有次要的原因:开发队伍能力不足,应用软件结构不合理,偏重于多线程软件开发而轻视多进程软件方案的优势等。这一切让我想起了“劣币驱逐良币”的悲剧:因为大多数嵌入式应用开发者只会C/C++编程,因此不得不沿用老掉牙的编程语言和开发模式。当然,这也与嵌入式开发团队所在的环境相关:一个不尊重软件开发理论、忽视软件设计重要性的单位,一本正经地制造缺陷的时同,偶尔生成勉强可用的软件——必然是搬起石头砸自己的脚。

此类应用崩溃问题相当棘手,要解决这些问题需要多锻炼,多调试,多总结。本文及后续文章将以GNU Libc语言库的内存分配模块为gdb调试对象,分享笔者的一些调试过程;希望对读者有一定的帮助,也希望牛人大佬多多批评指证。

 

  • 调试环境说明

我们使用前一篇博客中提到的嵌入式调试开发工具,压缩包名称为LinuxARM.tar.xz;安装方法及下载链接详见前文。这里需要说明的是,该工具使用了linaro.org提供的交叉编译器,编译器中的glibc动态库文件都是带有调试信息的,安装调试开发工具后,可以在lib/debug文件目录中找到:

只要修改可执行文件的动态链接器为/lib/ld-linux-armhf.so.d,并且依赖的C语言库文件名修改为libc.so.d,即可以加载带有调试信息的C语言动态库(其实是部分glibc库),使用gdb调试起来就更方便了。

 

  • 下载GNU Libc 源码

Linaro官方提供了上面提到的交叉编译器中的glibc源码(与GNU 官方的glibc源码存在差异),可以通过以下指令下载得到:

git clone --depth 8 https://git.linaro.org/toolchain/glibc.git -b release/2.25/master

下图为笔者的下载的glibc源码的最后一个提交:

下载该源码并不是为了编译glibc,而是为了分析glibc的内存分配模块功能的实现,同时方便gdb调试。

 

  • 制作简单调试应用

笔者编写了一个简单的C语言应用,在main函数执行之后调用了fgets(…)函数。很庆幸此时C语言库中的内存分配模块没有被初始化,这样调试可以简单一些。读者可以自行编写简单的应用,在main函数体中调用malloc(…)之类的内存分配函数即可。交叉编译后,将可执行文件(如下图可执行文件名为memory)复制到手机或嵌入式设备上,并修改其依赖的动态链接器及libc动态库:

修改之后使用ldd查看可执行文件依赖的动态库,均为两个重要的debug版本的动态库。

 

  • 调试Glibc的内存分配模块初始化钩子

通过查看glibc源码,得知初始化glibc内存分配模块的函数名称为ptmalloc_init;于是给此函数加上断点后运行简单应用:

结果不知为何断点加到malloc_hook_ini(…)函数中了。这是一个“美丽”的错误,具体原因待查:总之应用执行到malloc_hook_init(…)函数中便触发断点并停了下来。索性就来调试此函数吧:

__libc_malloc(…)函数在分配内存之前,会检查__malloc_hook函数指针,此时因为内存分配模块未初始化,该函数指针指向了malloc_hook_ini(…)函数,它调用了ptmalloc_init(…)函数,完成内存分配模块初始化的操作。

掌握gdb调试应用软件的核心点是,能够以汇编指令的视角,结合C/C++源码,理解并能够预测应用软件的执行过程。接下来反汇编malloc_hook_ini(…)函数主体,结合上图的代码,理解汇编的大致功能:

在函数malloc_hook_ini中,有一条赋值语句:

__malloc_hook = NULL;

结合对此函数的反汇编,我们预测标红色箭头的r1寄存器即为__malloc_hook静态变量的内存地址,在写入NULL之前,__malloc_hook静态变量保存的即为malloc_hook_ini函数的地址。首先,使用info address __malloc_hook查看静态变理的地址为0xb6fd3c10;之后在str指令处加上断点。当断点触发后,查看r1寄存器,与info address …给出的静态变量地址是相同的,这就验证了我们的“预测”;其二,地址0xb6fd3c10保存的函数指针确实也指向malloc_hook_ini函数的入口地址(见上图两处标黄箭头),偏移量为0x1(thumb指令集的特点):这也再次验证了我们的“预测”。

 

  • 总结

最后值得强调的是,使用gdb调试C/C++语言编写的应用,应当以汇编指令的角度,结合源代码理解并预测应用运行的流程。这就要求嵌入式开发者具有汇编的知识,也需要了解C/C++的调用规则。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章