如何调试(可能的)RTL问题?

时间:2011-05-13 22:10:41

标签: delphi debugging

我问这个是因为我没有好主意......希望别人有新观点。

我有一个用户在Windows 7 64位系统上运行我们的32位Delphi应用程序(使用BDS 2006编译)。我们的软件“工作正常”直到几周前。现在突然不是:它在初始化(实例化对象)时抛出了访问冲突。

我们让他重新安装了我们所有的软件 - 从头开始​​。相同的AV错误。我们禁用了他的反病毒软件;同样的错误。

由于某种原因,我们的堆栈跟踪代码(madExcept)无法为错误行提供堆栈跟踪,因此我们发送了几个错误日志记录版本供用户安装和运行,隔离产生错误的行...

事实证明,这是一个实例,它是一个简单的TStringList后代(没有重写的Create构造函数等等 - 基本上Create只是实例化一个TStringList,它有一些与后代类关联的自定义方法。)

我很想再向用户发送另一个测试.EXE;只是一个普通的TStringList实例,看看会发生什么。但是在这一点上,我觉得我在风车上挣扎,如果我发送了太多“尝试的东西”,那么冒着让用户忍耐的风险。

有关更好地调试此用户问题的方法的任何新想法? (我不喜欢挽救用户的问题......那些被忽视的人突然变成了其他5个用户突然“找到”的流行病。)

EDIT,正如Lasse所要求的那样:

procedure T_fmMain.AfterConstruction;
begin
  inherited;
      //Logging shows that we return from the Inherited call above,
      //then AV in the following line...
  FActionList := TAActionList.Create;
  ...other code here...
end;

这是正在创建的对象的定义......

type
  TAActionList = class(TStringList)
  private
    FShadowList: TStringList;              //UPPERCASE shadow list
    FIsDataLoaded : boolean;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    procedure DataLoaded;
    function Add(const S: string): Integer; override;
    procedure Delete(Index : integer); override;
    function IndexOf(const S : string) : Integer; override;
  end;

implementation

procedure TAActionList.AfterConstruction;
begin
  Sorted := False;              //until we're done loading
  FShadowList := TStringList.Create;
end;

5 个答案:

答案 0 :(得分:5)

我讨厌这些问题,但我认为你应该关注最近在对象试图构建之前发生的事情。

您描述的症状听起来像典型的堆损坏,所以也许你有类似的......

  • 正在写入外边界的数组? (转动边界检查,如果你关闭它)
  • 代码尝试访问已删除的对象?

自上面的答案以来,您已发布了代码段。这确实引发了我可以看到的几个可能的问题。

a:AfterConstruction与修改后的构造函数: 正如其他人所提到的,以这种方式使用AfterConstruction充其量不是惯用语。我认为这不是真正的“错误”,但这可能是一种气味。这些方法在Dr. Bob's site here.

上有一个很好的介绍

b:重写方法添加,删除,IndexOf 我猜这些方法以某种方式使用FshadowList项。在创建FShadowList之前,是否可以远程调用这些方法(因此使用FShadowList)?这似乎是可能的,因为您正在使用上面的AfterConstruction方法,此时虚拟方法应该“工作”。希望通过设置一些断点并查看它们被命中的顺序,可以很容易地通过调试器进行检查。

答案 1 :(得分:2)

我们的软件“工作正常”直到几周前......突然变成了其他5个用户突然“发现”的流行病。):

听起来您需要进行一些取证分析,而不是调试:您需要发现该用户环境中发生的变化才能触发错误。如果你有其他用户使用相同的部署没有问题(听起来就像你的情况那样),那就更是如此了。发送用户'要尝试的东西'是非常快速侵蚀用户信心的最好方法之一! (如果用户站点有IT支持,请让他们参与,而不是用户)。

首先,请探索以下选项:

*)如果可能的话,我会检查Windows事件日志,查看问题发生时该机器上可能发生的事件。

*)在用户方面是否有某种IT支持人员可以与该用户环境中的可能变更/问题进行讨论?

*)在错误浮出水面的时候,是否存在与该用户有某种支持问题/事件,并且/或者导致某种数据或文件损坏特定于它们?

(至于代码本身,我同意@Warran P关于解耦等)

答案 2 :(得分:2)

您应该从不覆盖程序中的AfterConstructionBeforeDestruction方法。它们并不意味着您正在使用它们,而是针对低级VCL黑客攻击(如引用添加,自定义内存处理等)。

您应该覆盖Create constructorDestroy destructor,并将初始化代码放在此处,例如:

constructor TAActionList.Create;
begin
  inherited; 
  // Sorted := False; // not necessary IMHO
  FShadowList := TStringList.Create;
end;

看看VCL代码和所有严肃发布的Delphi代码,你会发现从未使用AfterConstructionBeforeDestruction方法。我想这是问题的根本原因,因此必须修改您的代码。在未来的Delphi版本中可能会更糟。

答案 3 :(得分:2)

显然,对TAActionList在施工时所做的事情没有任何疑问。即使考虑祖先构造函数和设置Sorted := False可能产生的副作用,也表明应该没有问题。我对T_fmMain内发生的事情更感兴趣。

基本上正在发生导致FActionList := TAActionList.Create;失败的事情,即使TAActionList.Create的实现没有任何问题(可能是表单可能意外地被破坏)。

我建议您尝试更改T_fmMain.AfterConstruction,如下所示:

procedure T_fmMain.AfterConstruction;
begin
  //This is safe because the object created has no form dependencies 
  //that might otherwise need to be initialised first.
  FActionList := TAActionList.Create;

  //Now, if the ancestor's AfterConstruction is causing the problem, 
  //the above line will work fine, and...
  inherited AfterConstruction;

  //... your error will have shifted to one of these lines here.
  //other code here
end;

如果表单使用的组件的环境问题导致它在AfterConstruction期间破坏表单,那么将TAActionList.Create实例分配给FActionList实际导致AV。另一种测试方法是首先将对象创建为局部变量,然后将其分配给类字段:FActionList := LActionList

环境问题可能很微妙。例如。我们使用报告组件,我们发现需要安装打印机驱动程序,否则会阻止我们的应用程序启动。

您可以通过在表单的析构函数中设置全局变量来确认销毁理论。您也可以从析构函数中输出堆栈跟踪,以确认导致表单销毁的确切顺序。

答案 4 :(得分:0)

当MadExcept不够时(我很少说,这是很少见):

  1. 请尝试使用Jedi JCL的JCLDEBUG。如果您更改了MadExcept for JCLDEBUG,并且没有任何UI交互,则直接将堆栈跟踪写入磁盘,您可能会获得堆栈跟踪。

  2. 运行调试查看器,如MS / SysInternals debugview,并跟踪输出事件,例如发生问题的对象的自我指针。我怀疑INVALID实例指针不知何故在那里结束。

  3. 将事物分离并重构,并编写单元测试,直到找到真正令人讨厌的东西为止。 (有人建议堆损坏。我经常发现堆损坏与不安全丑陋的未经测试的代码,以及深层绑定的UI +模型级联故障密切相关。)