【问题标题】:How do I go about diagnosing memory corruption errors occurring in a COM-DLL after porting it from Delphi 2007 to Delphi 2009?在将 COM-DLL 从 Delphi 2007 移植到 Delphi 2009 后,如何诊断 COM-DLL 中发生的内存损坏错误?
【发布时间】:2010-09-29 19:38:59
【问题描述】:

我刚刚将我们的几个自制 Outlook COM 插件从 Delphi 2007 移植到 Delphi 2009,现在遇到了一些非常奇怪的错误(在你问之前:这些似乎都与字符串处理没有任何明显的关系) ,例如,当一个人试图第二次调用它们时挂起 Outlook 的模态对话框(第一次似乎一切都很好),但只有当它们从一个特定的事件处理程序调用而不是在其他地方做同样的事情时才会挂起。当我将错误跟踪到特定的代码行并注释掉该行或用不同的代码替换它以达到相同的效果时(例如,通过将否则将通过函数直接调用的代码复制到调用站点),将出现错误离开——通常只是在以后再次出现一些(同样不显眼的)陈述。

在 Delphi 调试器中运行它时,我可以看到冻结之前经常出现 GetMem.inc 中的访问冲突。至少所有这些问题都是 100% 可重现的......

不用说我们在 Delphi 2007 中编译这些插件时没有遇到这些问题。

现在,我很茫然。我知道我很幸运,但即使我认为自己是一个相当有经验的程序员(尽管主要是在小众领域),我以前从来没有真正需要处理过这类错误。正如这个问题的标题所说,我什至不知道从哪里开始。我可以随心所欲地单步执行代码,但是没完没了的汇编语句对我来说毫无意义,而且我也不擅长有效地使用 CPU 视图。

此外,我什至不确定这是否是我自己的代码开始的问题(在这种情况下,我实际上倾向于怀疑它)。我们正在大量使用许多第三方库(例如 JCL、ADX、Redemption)。尤其是 ADX 仍将其 Delphi 2009 支持标记为“测试版”。

我也尝试过使用 FastMM 的 FullDebugMode 并且确实以这种方式在 ADX 中发现了一些错误(例如,在被释放后被修改的块)但是所有这些也发生在我使用 Delphi 2007 编译时,所以它不会但似乎势在必行,这些最终是观察到的回归的原因。

那么,我该如何处理呢? - 或者更好:我在哪里可以找到一些学习如何处理这个问题的好资源?例如关于使用 CPU 视图或有效解释和执行 FastMM 报告的教程?这些是正确的工具吗?我还应该去哪里看?

附录:
在这种情况下,我应该怀疑哪些类型的代码?什么样的代码甚至有可能在内存中造成如此严重的破坏?我能想到的唯一地方是我的代码执行任何远程接近显式内存操作的地方是在准备 WinAPI 调用时保留一些缓冲区空间。另外请记住,我在 Delphi 2007 和 Delphi 2009 版本之间的所有代码都是相同的,而 Delphi 2007 版本没有出现此类问题。

更新:
有可能促使我发布这个问题的问题现在已经解决了。请参阅下面我自己的答案。

【问题讨论】:

    标签: delphi debugging memory-management delphi-2009


    【解决方案1】:

    获得解决方案的最佳工具可能是内存断点。

    调试内存损坏是一件痛苦的事,所以首先要让你的生活尽可能简单:找到一套精确的、保证可重复的、每次都有效的步骤。如有必要,模拟 Outlook 主机,这样您就不需要依赖 Outlook 时间问题或解决空间布局问题等。

    您必须获得一组可靠且可重现的步骤,这些步骤会在可预测地址处导致 AV 或其他错误。

    然后您要做的是重新启动进程,为引用该地址的任何内容创建一个内存断点集,并熟悉该内存块的生命周期。最小化和合理化您的复制步骤在这里会有所帮助。添加其他断点可能会有所帮助,并且仅在应用程序后期启用内存断点;或者使用 D2009 断点的日志功能来记录内存值/调用堆栈等,而不是实际闯入被调试对象。

    【讨论】:

    • 谢谢,这听起来很有希望。至少在一种情况下,我确实在可预测的地址上有一个可预测的 AV(可预测的总是相同的)。我应该将断点设置在 AV 发生的地址(即 GetMem.inc 中的第 1654 行)还是尝试写入的地址? (待定)
    • (ctd) 如果我将其设置为后者,则在断点触发之前引发 AV...
    • BP指的是无法访问的地址
    • 再次感谢!我想我现在找到了真正的问题。请参阅我自己对这个问题的回答。
    【解决方案2】:

    不完全是对更普遍的问题的答案,但很可能是对提示它的特定问题的解决方案:

    我现在有 95% 的把握确定了问题所在! :)

    这就是我所做的:

    • 我在编译器中启用了 RangeChecking 和 OverflowChecking
    • 我追踪并修复了所有导致 ERangeErrorEIntOverflow 异常的问题
      (各有一个)
    • 我在启用 FastMM 和 FullDebugMode 的情况下再次运行程序
    • 我终于能够在所有情况下确定问题的原因是对 JCL 函数 GetWindowCaption 的调用

    似乎GetWindowCaption 显然尚未检查 Unicode 兼容性:它使用从 API 函数 GetWindowTextLength(返回字符数)返回的值作为 ReallocMem 的输入(预计字节数)为GetWindowText 分配缓冲区(在Delphi 2009 中返回WideChars 缓冲区)。繁荣!该函数为缓冲区分配的内存太少,但GetWindowText 只是简单地覆盖了以下内存,从而破坏了块页脚。

    我现在已在 JCL 错误跟踪器中将其归档为 item #4648

    我从中得出的结论是:始终确保修复所有报告的错误!包括(看似)非关键错误,如范围和溢出错误。如果不出意外,它将使调试更加可预测。

    【讨论】:

    • 我几乎不会将范围或溢出错误称为“非关键”。除非您有意进行环绕式数学运算,否则几乎每次溢出都会默默地破坏您的数据。永远不要永远关闭范围或边界检查,除非你绝对需要速度并且你确定它已经安全了。
    • 我同意:始终修复范围和溢出错误(保持检查状态)并将所有警告视为错误(D2009 的一项重要功能)。然后在允许的情况下显式忽略/允许范围和溢出或其他警告条件。
    【解决方案3】:

    您在 D2007 中发现了双重释放错误,即使它在此版本中看起来确实可以正常工作,这意味着您需要修复这些错误,因为您很幸运 D2007 版本不需要回收内存与 D2009 版本一样激进,并且由于内存中的“影子持久性”,错误不会出现。
    我会使用 FastMM fulldebugmode 来查找错误代码并尽可能修复它,然后按照 Barry 的建议解决内存使用问题。
    有关如何使用集成调试器的功能,以及如何从非中断断点记录信息,您可能需要查看此 CodeRage 3 会话:Delphi Debugging for Dummies

    【讨论】:

    • 问题是 FastMM 报告的错误不会出现在我自己的代码中,而是出现在 ADX 库中(虽然我确实有源代码)。 AFAICT 它不是双重释放,而是对已释放块的修改。在某些时候,我还遇到了“损坏的块页脚”。
    • 修复它! ...或者不要使用。任何在正式释放后依赖内存持久性的代码都是不好的。 FastMM 非常擅长指出这一点。如果它是双重释放或使用释放对象的属性,则大致相同......
    • 再次感谢!我想我现在找到了真正的问题。修复此问题后,ADX 中的错误也消失了。请参阅我自己对这个问题的回答。
    【解决方案4】:

    我会关注系统内置的完整页面堆支持的方向。

    查看this post 了解如何配置它。如果您的内存使用量不太大,这是最容易找到问题的方法。

    当内存消耗较重时,它会变得很棘手 - 但就像我说的,首先尝试 full peagheap。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-05
      • 2012-07-06
      • 1970-01-01
      • 2011-06-17
      • 2010-12-15
      • 1970-01-01
      • 2010-10-09
      • 2011-04-07
      相关资源
      最近更新 更多