调试实战 | 记一次有教益的 vs2022 内存分配失败崩溃分析(续)

前言

前一阵子遇到了 vs2022 卡死的问题,在上一篇文章中重点分析了崩溃的原因 —— 当 vs2022 尝试分配 923MB 的内存时,物理内存+页文件大小不足以满足这次分配请求,于是抛出异常。

本篇文章将重点挖掘一下 vs2022 在崩溃之前已经分配的内容。

说明: 本文很早就写了草稿,一直没时间整理发布,Finally~

还是先从调用栈入手,找到关键参数,然后查看参数内容。

查找 vector 对象地址

栈帧 0b 对应的函数是 std::vector<T>::_Emplace_reallocate(),栈帧 0c 会调用这个函数。根据调用约定可知,调用类成员函数时,rcx 会指向类对象,在这里 rcx 会指向 std::vector<std::shared_ptr<std::stringstream>> 类型的实例。可以通过查看栈帧 0c 的反汇编代码确定 rcx 的来源。

211ae8ef5635dad978a289635c862e71.png
view-vector-instance-from-stackframe-0c

从图中可知,rcx 的值来自 rbp-0x70。那 rbp 的值是多少呢?使用 uf 查看 vcpkg!code_store::a_store::a_thread_impl::append_code_item_name() 函数的反汇编代码。

f84fa994be5908b6e080f577eae7938a.png
view-rbp-in-frame-0c

由上图可知,先把 rsp-0x920 赋值给  rbp,然后 rsp 会减小 0xa20。所以可以通过 rsp+0xa20-0x920 计算出对应的 rbp 的值,再减去 0x70 即可得到 rcx 的值。由此可知 vector 对象的地址是 0x000000b1 6547e5d0

6320c20bb9595394374244f7b4f982a7.png
view-rbp-rcx-in-frame-0c

查看 vector 内容

查阅 vs 提供的 STL 源码可知,vector 对象起始偏移 0 的位置存储了第一个元素的地址,起始偏移 864位程序)的位置存储了最后一个元素后面的地址。可以查看 vector 中前 20 个元素。

c8cb3c0230548b6573724f487ff39b28.png
view-vector-front-20-data

由调用栈可知,vector 中的元素类型是 shared_ptr<stringstream>。根据源码可知,shared_ptr<T> 类型的大小是 16 字节,偏移 0 的位置存储了对象的地址,偏移 8 的位置存储了引用计数对象的地址。

template <class _Ty>
class shared_ptr : public _Ptr_base<_Ty> { // class for reference counted resource management
    ...
};

template <class _Ty>
class _Ptr_base { // base class for shared_ptr and weak_ptr
private:
    element_type* _Ptr;
    _Ref_count_base* _Rep;
    ...
};

vector 中有多少个元素

大家应该都知道,vector 中的元素是顺序存储的,知道了起始地址及结束地址,也知道每个元素的大小,可以很容易计算出 vector 中的元素数量。

windbg 中输入 ? (000001c2434b7170-000001c21ccdd060) / 0n16 可以得到元素个数 40360465

根据上次分析的结果可知,分配的元素数量是60540697。通过查看 vs 提供的源码可知,容器扩容时会按 1.5 倍进行扩容。

来验证以一下是否符合这个规律。在 windbg 中输入 ? 0n40360465 + 0n40360465 / 2 可以得到结果 60540697

70bcc7d1e1c5eb945cdca443b3cb0722.png
view-vector-size

可见,当时 vs 在调用类似 push_back() 之类的方法向容器中增加元素,但是容器正好满了,触发了扩容操作。由此也可以验证之前的分析是正确的。

验证引用计数对象数据

拿第一个元素进行验证,实际对象的地址是 000001be 580056f0,引用计数对象的地址是 000001be 580056e0。先验证引用计数对象是否正确。

_Ref_count_base 结构如下图所示:

149ecf39ee99f6bd1cc0b2fb233ccddb.png
view-_Ref_count_base_detail

说明: devenv 加载的模块所对应的调试符号已经去除了 Type 信息,没办法通过 dt 显示类型信息。上图是我用 windbg 调试新建的测试工程时的截图。

从下图可知,引用计数相关数据是完美匹配的。

367f1d298f7d4e5907f7ed499b217039.png
verify-reference-count-object

一般 shared_ptr<T> 的引用计数和实际的数据是没有关系的,比如下面的代码:

int* p = new int();
std::shared_ptr<int> sp(p);
62dcce10ce63a1f18d63a2773480e902.png
view_normal_shared_ptr

sp._Ptr 的值是 0x017b9450sp._Rep 的地址是 0x017b9640,两者之间没有明显关系。

但是,如果你观察的比较仔细,可以发现一个非常有趣的现象 ——  vector 中的每个元素(智能指针)的引用计数对象的地址 +0x10 正好等于实际对象的地址。

以第一个元素为例,引用计数对象的地址是 000001be 580056e0,实际对象的地址是 000001be 580056f0,两者正好相差了 0x10

这是怎么回事呢?如果你对 stl 比较熟悉,可能已经想到了 std::make_shared()vector 中存储的对象都是通过 std::make_shared() 创建出来的。

make_shared

我摘录了 vs 中提供的源码

template <class _Ty, class... _Types>
shared_ptr<_Ty> make_shared(_Types&&... _Args) { // make a shared_ptr to non-array object
    const auto _Rx = new _Ref_count_obj2<_Ty>(_STD forward<_Types>(_Args)...);
    shared_ptr<_Ty> _Ret;
    _Ret._Set_ptr_rep_and_enable_shared(_STD addressof(_Rx->_Storage._Value), _Rx);
    return _Ret;
}

注意代码中 _Ret._Set_ptr_rep_and_enable_shared() 第一个参数的值是 _Rx->_Storage._Value 的地址。

_Rx 的类型是 _Ref_count_obj2<_Ty>*_Ref_count_obj2 继承自 _Ref_count_base。而  _Ref_count_base 的大小是 16 字节:虚表指针 8 字节,两个引用计数各占 4 字节,一共 16 字节。

大概的内存结构图如下:

a5867cd83e2d9366cc86c925192c6144.png
make_shared_relation

务必注意 _Ref_count_obj2 中的 _Storage 存储了整个目标对象,而不是指针。

总结

  • procdump 真是事后调试的好帮手。以管理员权限运行 procdump -i -ma d:\dumps\ 即可安装。-i 表示安装(如果要卸载,可以使用 -u 参数)。-ma 表示执行完整转储,d:\dumps\ 表示 .dmp 文件保存的位置。

  • 相较于 32 位进程的 4GB232 次方)虚拟内存空间而言, 64 位进程的虚拟内存空间超级大,目前是  256TB(总共 64 位,目前只用了 48 位),内核态和用户态平均分,用户态可以使用一半,也就是 128TB

  • 如果使用 malloc() 或者 new() (内部会调用 malloc())分配的内存大小超出堆阈值,那么内部会使用 NtAllocateVirtualMemory() 分配内存,而且 AllocationType 的值是 MEM_COMMIT。分配 MEM_COMMIT 类型的内存是受物理内存+分页文件大小限制的。

参考资料

  • vs 源码

  • NTSTATUS Values

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/731797.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

昇思25天学习打卡营第4天|网络构建|函数式自动微分

学AI还能赢奖品&#xff1f;每天30分钟&#xff0c;25天打通AI任督二脉 (qq.com) 网络构建 神经网络模型是由神经网络层和Tensor操作构成的&#xff0c;mindspore.nn提供了常见神经网络层的实现&#xff0c;在MindSpore中&#xff0c;Cell类是构建所有网络的基类&#xff0c;也…

借助AI营销类API,实现自动化的营销流程

借助AI营销类API&#xff0c;企业可以实现自动化的营销流程&#xff0c;提高效率和效果&#xff0c;并节省大量的时间和资源。这些API利用人工智能和机器学习的技术&#xff0c;能够自动化地执行各种营销任务和流程。首先&#xff0c;AI营销类API可以帮助企业实现自动化的市场调…

【鸿蒙】创建第⼀个鸿蒙项⽬

点击 Create Project 配置项目 开发工具界面 工程介绍

探索AI前沿:本地部署GPT-4o,打造专属智能助手!

目录 1、获取API_key 2、开始调用 3、openai连接异常 4、解决方法&#xff1a; 5、调用GPT-4o 1、获取API_key 这里就不多赘述了&#xff0c;大家可以参考下面这篇博客 怎么获取OpenAI的api-key【人工智能】https://blog.csdn.net/qq_51625007/article/details/13763274…

大数据与java哪个好找工作?这篇文章帮你做选择!

大数据与java哪个好找工作&#xff1f;这篇文章帮你做选择&#xff01; 还在为选择Java开发还是Java大数据而头疼吗&#xff1f;别担心&#xff0c;本文将从就业前景、学习方向、学习内容以及薪资待遇四个方面&#xff0c;为你揭开Java和Java大数据的神秘面纱&#xff0c;帮你做…

ZW3D二次开发_删除草图中的实体

1.目前草图中的实体不能直接通过id删除&#xff0c;而是通过entityPath实体路径&#xff0c;所以需要将id转化为实体路径。 2.以下示例代码的主要功能为获取草图中的所有实体并删除&#xff1a; int Count;int *idEnts;ZF_CALL(cvxSkInqGeom(&Count, &idEnts));//获取…

《窄门》情不知所起,而一往情深

《窄门》情不知所起&#xff0c;而一往情深 安德烈纪德&#xff08;1869-1951&#xff09;&#xff0c;法国作家。纪德一生著有小说、剧本、论文、散文、日记、书信多种&#xff0c;主要作品有小说《背德者》《窄门》《田园交响曲》《伪币制造者》等&#xff0c;戏剧《康多尔王…

x64汇编fastcall调用约定

x64汇编环境&#xff1a;只需要在x86基础上对项目属性进行设置&#xff0c;将平台设置为所有平台&#xff1b; 以及在将debug改为x64模式即可&#xff1a; 后续写完代码直接生成项目再使用本地调试器进行运行即可。 fastcall调用约定 在x64架构下&#xff0c;fastcall调用约定…

android倒计时封装(活动进入后台,倒计时依然能正常计时)

public class TimeUtils { /倒计时时长 单位&#xff1a;秒/ public static int COUNT 20*60; /当前做/ private static int CURR_COUNT 0; /预计结束的时间/ private static long TIME_END 0; /计时器/ private static Timer countdownTimer; /显示倒计时的textVi…

大数据学习-大数据介绍

意义 从海量的数据中分析出海量数据背后的价值 需要分析海量的数据&#xff0c;就需要存储、计算和分析 那就需要分布式多台计算机合适的工具来处理数据 工具 特点 大数据的核心工作&#xff1a;从海量的、高增长的、多类别的、信息密度低的数据中挖掘出高质量的结果 数据存储…

STM32通过SPI软件读写W25Q64

文章目录 1. W25Q64 2. 硬件电路 3. W25Q64框架图 4. 软件/硬件波形对比 5. 代码实现 5.1 MyI2C.c 5.2 MyI2C.h 5.3 W25Q64.c 5.4 W25Q64.h 5.5 W25Q64_Ins.h 5.6 main.c 1. W25Q64 对于SPI通信和W25Q64的详细解析可以看下面这篇文章 STM32单片机SPI通信详解-CSDN博…

开发中遇到的一个bug

遇到的报错信息是这样的&#xff1a; java: Annotation processing is not supported for module cycles. Please ensure that all modules from cycle [hm-api,hm-common,hm-service] are excluded from annotation processing 翻译过来就是存在循环引用的情况&#xff0c;导…

C++进阶之AVL树

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 C进阶​ ​​​​算法 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.前言 二.插入 三.旋转 3.1右旋 …

postman国内外竞争者及使用详解分析

一、postman简介 Postman 是一款广泛使用的 API 开发和测试工具&#xff0c;适用于开发人员和测试人员。它提供了一个直观的界面&#xff0c;用于发送 HTTP 请求、查看响应、创建和管理 API 测试用例&#xff0c;以及自动化 API 测试工作流程。以下是 Postman 的主要功能和特点…

Docker常用操作和命令

文章目录 1、卸载旧版本 2、yum安装Docker CE&#xff08;社区版&#xff09; 3、添加镜像加速器 4、docker --version 查看docker版本 5、docker info 或 docker system info 显示 Docker 系统的详细信息&#xff0c;包括容器、镜像、网络等 6、docker search 搜索镜像 …

JVM类加载器与双亲委派机制

通过上一篇Java的类加载机制相信大家已经搞明白了整个类加载从触发时机&#xff0c;接着我们就来看下类加载器&#xff0c;因为类加载机制是有加载器实现的。 类加载器的分类 启动类加载器 Bootstrap ClassLoader 是 Java 虚拟机&#xff08;JVM&#xff09;的一部分&#x…

C#调用OpenCvSharp实现图像的直方图均衡化

本文学习基于OpenCvSharp的直方图均衡化处理方式&#xff0c;并使用SkiaSharp绘制相关图形。直方图均衡化是一种图像处理方法&#xff0c;针对偏亮或偏暗的图像&#xff0c;通过调整图像的像素值来增强图像对比度&#xff0c;详细原理及介绍见参考文献1-4。   直方图均衡化第…

【中学教资科目二】02中学课程

02中学课程 第一节 课程概述1.1 课程的分类 第二节 课程组织2.1 课程内容的文本表现形式2.2 课程评价 第三节 基础教育课程改革3.1 基础教育改革的目标3.2 新课改的课程结构 第一节 课程概述 1.1 课程的分类 学校课程有多种类型&#xff0c;其中最利于学生系统掌握人类所取得的…

多维表格/业务库表格大数据量性能瓶颈

先说最终结论&#xff1a;Angular 组件创建性能损耗是当下主要的性能瓶颈 理由&#xff1a; 基于以往编辑器性能优化的经验&#xff0c;编辑器在动态渲染内容时会创建很多壳子组件&#xff08;也就是Angular 组件&#xff09;&#xff0c;排查的时候就发现如果略这些壳子组件性…

mysql--安装跳过验证修改密码安全加固

安装mysql 配置mysql的yum源 [rootVM-0-14-rockylinux ~]# tee /etc/yum.repos.d/mysql.repo << EOF > [MYSQL] > namemysql > baseurlhttps://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql-5.7-community-el7-x86_64 > gpgcheck0 > EOF yum安装mysq…