调试实战 | 调试一个由内存分配失败导致的崩溃

缘起

前一阵子,朋友开发的 QT 程序遇到了一个崩溃问题,抓了 dump 。我跟着一起分析的。最后发现是由于内存分配失败导致的。

一起来练习一下排查思路吧。

说明: 本文很早就写好了草稿,一直没整理成文,Finally~

万能的 !analyze -v

拿到转储文件,第一件事就是点击 !analyze -v 超链接,让 windbg 帮忙自动分析。

analyze-v

从输出结果中的 Qt5Core!qBadAlloc+0x1a 可以猜测是内存分配失败导致的问题。

分配了多大内存?

加载好符号文件,输入 k 命令查看调用栈。

view-callstack

显示的调用栈,需要使用 !analyze -v 分析结果中的 .cxr 0x59cba8 切换上下文,然后再执行 k 命令即可查看到有意义的调用栈了。

view-meaningful-callstack

查看栈帧 03 对应的代码,可以发现是在通过 file->readAll() 读取整个文件的内容。

view-frame-03

根据代码逻辑及函数局部变量的值,可以确定要读取的文件大小大概是 288.6MB

空闲空间够大吗?

相比于之前分析空闲内存空间是否足够的办法(感兴趣的小伙伴儿可以点击 这里 查看),我们可以使用更高级的命令进行查找。命令如下:

!address -f:Free -c:".if ( %3 >= 0n302630400) {.echo %1 %2 %3}"

执行后可以发现,输出结果是空,如果减小 0n3026304000n102630400,可以发现有两条记录。说明命令是有效的,而且进程中已经没有一块空闲地址空间可以满足本次分配请求。

search-free-region-larger-than-0n302630400

更进一步

如果使用 !address -summary 查看地址空间情况,可以发现空闲空间大小是 1.025GB,占整个内存空间的 51.24%。说明当前程序是一个 32 位的程序。

address-summary

使用 !dh client 查看 client 模块的 PE 文件头信息,可以发现确实是 32 位程序,而且没有开启 Large Address Aware 标志。

view-pe-header-with-dh

解决

  1. 不要一次性读取全部文件到内存中

    这样修改后,可以处理非常大的文件。但是需要修改代码逻辑,相对复杂。

  2. 修改程序为 64

    非常简单,只需要重新编译即可。

  3. 设置 Large Address Aware 标志

    用户内存空间可以达到 3GB,可以在一定程度上避免内存分配失败的问题。set-large-address-aware-in-vs

总结

本次排查相对顺利,解决也比较简单,虽然简单,还是有一些收获的。

  • 可以在 windbg 中使用 !address -f:Free -c:".if ( %3 >= mem_size) {.echo %1 %2 %3}" 快速查找大于 mem_size 的空闲空间。

  • 可以在 windbg 中使用 !dh 命令查看 PE 文件头信息。可以快速确定一些关键信息,比如模块是 32 位的还是 64 位的,是否开启 Large Address Aware 标志等。

  • 如果可以尽量使用 64 位程序,如果必须使用 32 位程序,至少也要开启 Large Address Aware 标志。

    可以通过工程属性页中的 链接器 -> 系统 -> 启用大地址 进行开启。

BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
0%