调试实战 | 记一次有教益的 vs2022 内存分配失败崩溃分析(续)
内容提要
本文分析了vs2022卡死问题,重点挖掘了崩溃前vs2022已分配的内容。通过查找vector对象地址和查看vector内容,得出vector中元素个数为40360465,验证了之前的分析。同时发现vector中每个元素的引用计数对象地址+0x10等于实际对象地址,推测vector中存储的对象是通过std::make_shared()创建的。文章总结了procdump的使用和64位进程的虚拟内存空间。
延伸解读
从调用栈定位 vector 实例的调试技巧
文章展示了如何通过调用栈和反汇编找到 vector 对象地址:先确定栈帧 0c 中 rcx 的来源为 rbp-0x70,再根据函数反汇编计算出 rbp 的值,最终得到 vector 地址。这种方法适用于调试符号不完整或类型信息缺失的场景,但需要熟悉 x64 调用约定和栈帧布局。对于普通开发者,理解这一过程有助于在崩溃转储中定位关键数据结构。
vector 扩容机制与崩溃诱因的关联
通过计算 vector 中元素个数为 40360465,并验证其 1.5 倍扩容后为 60540697,与之前分析中尝试分配的元素数量一致。这表明崩溃发生在 vector 扩容时,因需要分配大量内存而触发。这提醒我们,在频繁插入元素的场景中,vector 的扩容可能带来显著的内存峰值,尤其是在元素为 shared_ptr 等较大对象时,需关注内存使用。
make_shared 的内存布局特征
文章发现 vector 中每个 shared_ptr 的引用计数对象地址加 0x10 等于实际对象地址,推测对象由 std::make_shared 创建。这是因为 make_shared 将引用计数对象和目标对象分配在同一块内存中,_Ref_count_obj2 的 _Storage 直接存储目标对象。这种布局减少了内存分配次数,提升性能,但也会使对象与引用计数同生命周期,可能延迟内存释放。
64 位进程内存分配的限制与调试工具
文章总结指出,64 位进程用户态虚拟内存可达 128TB,但 malloc/new 在超出堆阈值时会调用 NtAllocateVirtualMemory 并提交内存(MEM_COMMIT),受物理内存和页文件大小限制。因此,即使虚拟地址空间充足,实际分配仍可能失败。procdump 以管理员权限安装后可捕获完整转储,是事后调试的有效手段,命令为 procdump -i -ma d:\dumps\。
Q&A
vs2022崩溃的主要原因是什么?
vs2022崩溃是因为尝试分配923MB内存时,物理内存和页文件不足。
如何查找vector对象的地址和内容?
可以通过调用栈分析,找到vector对象的地址,并查看其内容。
vector中元素的数量是如何计算的?
通过起始地址和结束地址,以及每个元素的大小,可以计算出vector中的元素数量。
为什么vector中每个元素的引用计数对象地址与实际对象地址相差0x10?
这是因为vector中存储的对象是通过std::make_shared()创建的,引用计数对象地址加0x10等于实际对象地址。
procdump工具的使用方法是什么?
使用管理员权限运行procdump -i -ma d:\dumps\即可安装,-i表示安装,-ma表示执行完整转储。
64位进程的虚拟内存空间有多大?
64位进程的虚拟内存空间为256TB,用户态可用128TB。