如何在Delphi中找到导致AV的悬空接口

12

我有一个复杂的应用程序,刚刚引入了一些更改,添加了几个新的类和接口,并删除了一些其他类。功能上都可以正常工作,但是在一个类的“销毁”过程之后,我会遇到一个访问冲突:

"Access violation at address 0040B984 in module 'xxxx.exe'. Read of address 80808088".

我知道这是类的“Finalize”代码中出现的问题。如果我进入反汇编(Delphi 2010),我可以看到AV的出现点。但是我找不到哪个变量在触发这个问题。在进行这么深入的操作时,是否有一种程序可以帮我找到被引用的实例的线索?

谢谢 Brian


2
我建议使用free而不是destroy。 - Bharat
7个回答

14

这个错误看起来像是你正在使用 FastMM 进行内存管理。
这个错误表示你正在引用一个已被 FastMM 清除为 DebugFillDWord 值的指针。

这意味着你正在使用一个引用已经被释放的对象的接口。
同时也意味着你没有启用 CatchUseOfFreedInterfaces

为了更改这些设置并进行调试,你需要下载 FastMM(版本 4.94)。

下载完成后:

gabr 已经提到的,在 FastMM4Options.inc 中确保启用了 FullDebugModeCatchUseOfFreedInterfaces(这将禁用 CheckUseOfFreedBlocksOnShutdown,但现在你不关心后者)。
你可能还想启用 RawStackTraces;这取决于你当前的堆栈跟踪是否足够好。

当你完成这些设置后,通过调试器运行 FastMM 并在 FastMM4 单元中的此方法上设置断点:

procedure TFreedObject.InterfaceError;

我稍微修改了我的FastMM4单元,以获得更多的上下文信息;我可以与您分享(我已经将其发送给了FastMM4团队,但尚未包含在官方源代码中)。
我写了一篇相当深入的关于使用FastMM进行调试的博客文章,这可能会对您有所帮助。
如果需要进一步解释,请在这里留言 :-)
祝好运,如果需要进一步指导,请告诉我们。
--jeroen 编辑:20100701 - 强调了Brian评论中提到的要点。

谢谢,我很感激你提到的上下文信息模式。我已经在使用FastMM 494了,但我会按照你博客中的步骤去做。Bri - Brian Frost
@brian:通过我的博客给我留言(或发送电子邮件;几乎任何以pluimers.com结尾的都可以,特别是在@符号之前使用我的名字)。 - Jeroen Wiert Pluimers
1
@所有人:这个答案的关键点是:“当你完成这些设置后,通过调试器使用FastMM运行你的应用程序,并在FastMM4单元内的此方法上设置断点:procedure TFreedObject.InterfaceError”。某种确定有关结果TFreedObject(即其类类型)更多信息的方法将非常有用。 - Brian Frost
@BrianFrost 这些更改已经一段时间前被纳入了FastMM4;请查找FullDebugModeCallBacks,示例在https://bitbucket.org/jeroenp/besharp.net/src/tip/Native/Delphi/Library/FastMM/FastMM4BootstrapUnit.pas以及差异https://github.com/pleriche/FastMM4/commit/3cf0c2addedc6a2f22b9f159abdf42e22e1d1e76 - Jeroen Wiert Pluimers

13
在大多数情况下,可以通过使用FastMM和使用条件定义FullDebugModeCatchUseOfFreedInterfaces来捕获此类错误。只需确保将FastMM4放在dpr的“uses”列表中的第一位即可。

1
好建议Gabr,我正在使用FastMM,但尚未启用“CatchUseOfFreedInterfaces”。现在这给了我一份整洁的步骤堆栈,但我仍然无法弄清楚是谁持有非法引用。我正在进行自己的引用计数(-1),因为我正在释放自己的类(如TComponent所做的那样),所以我需要知道非法接口指针从哪里“获得”。 - Brian Frost
几周前我遇到了类似的问题,这是由于混合使用引用计数和非引用计数接口导致的。我不确定为什么会出现这个问题,但无论如何,它确实存在。 - Vegar

6

找到问题的步骤:

  1. 像 Gabr 建议的那样在 fulldebugmode 中使用 FastMM(根据你的 808080 模式,我认为你已经这样做了)。
  2. 在 Destroy 过程中显式地将你的类中使用的所有接口设置为 nil。
  3. 在 Destroy 过程的开头设置断点。
  4. 现在逐步执行 Destroy 过程,当处理悬空接口时,你会遇到访问冲突并且你会知道是哪个接口。
  5. 如果在没有问题的情况下,你仍然遇到 AV,请按照步骤 2-5 对父类进行操作。

我也遇到过这些问题,上述方法帮助我找到了它们。我的问题是由实现接口的 TComponents 引起的。假设你有 ComponentA 和 ComponentB,ComponentB 实现了一个接口。你将 ComponentB(或其接口)分配给 ComponentA 并存储接口引用。现在 ComponentB 被销毁了,但 ComponentA 不知道这一点。当你销毁 ComponentA 时,它会将接口置为 nil,调用 _Release 方法,从而导致 AV。

解决方法是使用 TComponent.FreeNotification。当你从 ComponentB 接收到免费通知时,在 ComponentA 中将接口置为 nil。我不了解你的代码,但如果你的问题类似,你也可以使用 FreeNotifications。

编辑:添加步骤 5


Fox: 我会仔细检查我是否到处都有 nil! - Brian Frost
@Brian Frost:你知道哪个类产生了AV吗?明确地将接口设置为nil非常简单,但是当有很多接口时可能很麻烦。 - The_Fox

4

我做了类似的事情,你对象的析构函数中加入以下代码将会有帮助:

Destructor TMyObjectThatIAmDoingManualRefCounting.Destroy;
begin
  if FMyRefCount<>0 then
    messageDlg('You dork, you called Free on me when someone still had references to me');

  inherited;
end;

然后,您至少可以找出在哪里不适当地释放了对象。可能您释放对象的时间太早了。释放对象过早实际上不会引起问题,只有在释放保持接口引用的对象时才会出现错误。
另一件事是,在addref和release方法中设置断点,跟踪谁保留接口引用以及那些对象是否在之后释放它们。
还有一个常见的问题是,如果您在同一方法中获取接口并释放对象。
var
  o:TSomeObject;
begin
  o:=TSomeObject.Create;
  (o as ISomeInterface).DoSomething;
  o.free
end;

这将导致方法结束时出现AV错误,因为编译器创建了一个虚假的接口变量,在方法结束时被释放。如果要避免这种情况,您需要执行以下操作。
var
  o:TSomeObject;
  i:ISomeInterface;
begin
  o:=TSomeObject.Create;
  i:=(o as ISomeInterface); // or Supports or whatever
  i.DoSomething;
  i:=nil;
  o.free
end;

3

我曾经遇到过一个类似的bug,就是当一个接口引用被设置在已有的对象上时,当拥有该对象的接口释放时,接口引用计数器不会自动减少。这个问题可以通过在拥有者对象的析构函数中添加if Assigned(FMyInterface) then FMyInterface := nil;来解决。


明智的教训是:如果使用接口,请专门使用,并让引用计数机制为您管理所有内容。如果可能,尽量避免在 TComponent 的后代中使用接口,因为所有者/组件关系将与引用计数机制竞争,导致一个受害者:试图调试内存问题的人。 - Jeroen Wiert Pluimers

2

您在代码中需要注意的一件事是:

FInterfacedObject.GetInterface 

在相同的作用域中

FInterfacedObject := TInterfacedObjectClass.Create.

其中FInterfacedObject是一个类变量。

如果您想要,可以从内部函数调用GetInterface,但是如果您在创建FInterfacedObject的同一作用域中调用GetInterface,出于任何原因,您将使引用计数降为0并释放该对象,但它不会为nil,所以如果您这样做

if assigned(FInterfacedObject) then
    FInterfacedObject.Free;

您将会收到一个访问违规的错误提示。


2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接