gpt4 book ai didi

Windows 上的 C++ 堆栈遍历

转载 作者:行者123 更新时间:2023-12-01 14:46:03 26 4
gpt4 key购买 nike

我正在使用一种非常 .NET 风格的方法为 C++ 构建一个内存管理器。在这样做时,我需要知道哪些对象被认为是可达的;如果一个可达对象有相关对象的句柄,则对象被认为是可达的。所以这就提出了一个问题,哪些对象是我们搜索的根?答案是这些“eve”对象在堆栈上,无论是托管对象句柄的形式,还是本身具有托管对象句柄的范围本地对象实例的形式。

我已经阅读了一些关于此的文章,并在 MSDN 上查看了有关 Win32 API 中 StackWalk 方法的实现细节。

与往常一样,非常感谢任何帮助。并且请不要建议不要制作内存管理器,或建议替代方案,例如智能指针。我完全明白我在做什么。谢谢!

最佳答案

您的要求似乎类似于我目前正在处理的一个小项目,但我的目标不是制作内存管理器,我的目标是检测 dmalloc(以及 Debug模式的长时间运行的应用程序,它在其中正在运行)能够定期停止执行并扫描内存以查找没有引用的堆分配。有点像“愚蠢的”垃圾收集器,但不是以释放内存为目标;相反,目的是记录泄漏的分配以供以后分析(以及在分配时捕获的堆栈跟踪,我已将其添加到 dmalloc)。请注意,作为通用内存管理器的垃圾收集器,这将是一个非常低效的过程,并且需要“很长时间”才能运行(我还没有完成,但如果每次运行它我都不会感到惊讶停止正常程序执行超过 10 秒),但出于我自己的目的,我不太关心性能,因为我每隔几个月只启用一次,以测试我公司产品中的新内存泄漏。

无论如何,我假设您的内存管理器将是您的应用程序中堆内存的唯一来源?并且您系统中的线程在完全共享的内存环境中运行,其中没有线程拥有任何其他线程无法看到的内存,包括堆栈空间和线程本地存储空间?如果是这样...

我相信只有四类内存,您可以在其中找到指向堆分配的指针:

  • 在每个线程的调用栈上
  • 堆分配本身
  • 在静态分配的可写内存中(.bss & .data/.sdata,但是
    不是 .rdata/.rodata)
  • 在每个线程的线程本地存储空间中

  • 您已经知道堆栈上可能会出现指向堆分配的指针。指向分配的指针也可以(可以改为)存储在堆对象本身中,甚至不存储在堆栈中。您的问题表明您可能希望将堆栈用作垃圾收集器搜索的“根”;我认为这意味着您希望能够跟踪堆栈上的指针向外到其他分配,通过内存从一个对象搜索到另一个对象,直到您遍历内存中的所有对象并找到指向所有分配的所有指针。 “根”指针也可能存在于静态分配的对象中,它们可以直接引用,甚至堆栈上没有指向此类对象的指针,因此您不能假设所有分配都可以从您在堆栈。此外,不幸的是,对于 C++,除非您能够知道每个分配的结构(如果没有编译器的帮助,您将无法知道),您将不得不假设任何位置都可能是一个指针。因此,您必须扫描这四类内存中的每一种,寻找指向所有现有分配的潜在指针,如果您在内存中找到与分配地址匹配的值,则用“可能仍在使用”标志来标记每个类别,无论它是否实际上是一个指针。当您扫描内存时,在每个字节位置(或在每个字节位置被 sizeof(void*) 整除,如果您知道您的平台不能在未对齐的地址上有指针),您将不得不搜索您的分配列表查看该值是否在您的分配列表中。

    由于您确信自己知道自己在做什么,因此您的内存管理器可能正在平衡树结构(可能是红黑树或安德森树)中跟踪这些分配,从而为您提供 O(log n) 插入和查找这些分配,但导航这些树的比例常数将真正扼杀垃圾收集器的性能。在进行垃圾收集扫描之前,您需要按顺序(即使用中序遍历升序或降序)将树的分配指针复制到平面连续缓冲区(即“数组”)中。我建议数组 void*每个分配的地址和一个单独的位数组(不是 bool 数组),每个分配有一个位,初始化为全零,如果您找到对它的潜在引用,则分配的相应位设置为 1。当您扫描垃圾收集时,这仍然会给您 O(log n) 查找(使用二分搜索),但是您的查找具有更易于管理的比例常数;此外,这种更紧凑的数据结构往往比平衡树具有更好的缓存命中性能。

    现在我将讨论您必须扫描的三类内存中的每一种:
  • 每个线程的调用栈

  • 为此,您必须能够查询线程管理器以获取每个线程堆栈的顶部和底部。如果您只能获取每个线程的当前堆栈指针,那么您可以使用“回溯”API 来获取该堆栈上的函数返回地址列表。从那以后,您可以向回扫描每个堆栈的基址(您不知道),按顺序勾选每个返回地址,直到到达最后一个返回地址,然后在那里找到堆栈基址(或足够接近) .对于“当前线程”,请确保不包含任何与您的内存管理器相关的堆栈帧;即备份一些堆栈帧并忽略与垃圾收集器相关的那些,否则您可能会在垃圾收集器的局部变量中找到泄漏分配的地址并将它们误认为
  • 堆分配本身

  • 堆对象可以相互引用,并且您可能有一个泄漏对象网络,这些对象都相互引用,但作为一个组,它们被泄漏了。您不想看到它们相互指向的指针并将它们视为“使用中”,因此您必须小心处理这些……并且最后。完成所有其他类别后,您可以折叠/拆分 void* 的平面阵列。分配地址,单独列出“考虑使用中”分配和“尚未验证”分配。扫描“考虑使用中”的分配,寻找仍然在“尚未验证”列表中的分配的潜在指针。当您找到任何内容时,将它们从“尚未验证”列表移到“考虑使用中”列表的末尾,以便您最终也可以扫描它们。
  • 在静态分配的可写内存中(.bss & .data/.sdata,但不是
    .rdata/.rodata)

  • 为此,您需要从链接器获取符号到每个部分的开始和结束(或长度)。如果此类符号尚不存在,或者您无法从平台 API 获取该信息,则您需要获取链接器命令脚本(链接器脚本)并对其进行修改以添加和初始化全局符号到起始地址和结束每个部分的地址(或长度)。 .bss 部分包含未初始化的全局、文件范围和类静态数据成员。 .data/.sdata 部分包含非常量预初始化的全局、文件范围和类静态数据成员。您无需担心 .rdata/.rodata 部分,因为您的程序不会将堆分配地址写入静态常量数据。
  • 在每个线程的线程本地存储空间中

  • 为此,您必须能够向线程管理器查询每个线程的线程本地存储空间,否则每个线程的启动部分必须将其线程本地存储添加到线程列表中-应用程序的本地空间,并在线程退出时将其删除。

    如果您仍然在船上并想要这样做,那么现在您可能已经意识到这是一个比您最初想象的更大的项目。让我知道它是怎么回事!

    关于Windows 上的 C++ 堆栈遍历,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13848412/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com