巧用 gcc 和 glibc 调试内存问题

工程中,我们常常会遇到内存访问越界导致的问题。一般而言,如果被踩到的内存没有被释放或者用作他途(比如恰好记录着 heap 的数据结构),是不会发生问题的。但是一旦发生 core dump,定位起来就非常麻烦。

先来看这样一段危险代码:

这里虽然发生了严重内存访问越界,但是运行时并没有什么异常:

但如果被踩到的内存后续被释放的话,就会发生问题。

我们将先前申请的内存释放掉,然后编译运行:

可以看到,在 free 的时候发生了问题。这里可以使用 gdb 找到发生内存访问越界的地方。但是如果遇到大型程序的话,类似方式往往很难定位到确切的位置。

这里提供两种方式,分别使用 C 运行库 glibc 和编译器 gcc 内置的功能,来帮忙调试类似的问题。

一、MALLOC_CHECK_

GNU 的 C 标准库(glibc)支持动态内存调试,需要通过环境变量 MALLOC_CHECK_ 来设定其行为,其取值有四个等级:

  • MALLOC_CHECK_=0 关闭所有检查
  • MALLOC_CHECK_=1 当有错误被探测到时,在标准错误输出(stderr)上打印错误信息
  • MALLOC_CHECK_=2 当有错误被探测到时,不显示错误信息,直接调用 abort 进行中断
  • MALLOC_CHECK_=3 当有错误被探测到是,同时执行 1 和 2

默认情况下,该环境变量是没有设定的,需要我们手工去设定:

再次运行上述程序,glibc 就会调用 abort 中断程序,并给出丰富的提示信息:

二、AdressSanitizer

AddressSanitizer 是 Google 出品的一款开源的内存检测工具,可以用来检测内存损坏错误,例如缓冲区溢出或访问悬空指针等一系列问题。它首先被 clang 支持,继而又被 gcc 支持,gcc 4.8 及以后的版本直接可用。

再次运行该程序,可以看到 AddressSanitizer 提示 heap-buffer-overflow 并给出了相关内存地址的申请和读写操作的 backtrace,可以快速定位问题。

当然,这么强大的工具,也是有缺陷的。AddressSanitizer 编译的程序的堆、栈占用比原生程序的大,因此对于某些嵌入式设备可能并不太适用。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据