我是团队中的一员,他们将Delphi 2007用于更大的应用程序,我们怀疑堆损坏,因为有时会出现奇怪的错误,没有其他解释。 我相信编译器的Rangechecking选项仅适用于数组。我想要一个工具,当存储器地址没有被应用程序分配时,它会发出异常或日志。
此致
编辑:错误类型为:
错误:模块“BoatLogisticsAMCAttracsServer.exe”中地址00404E78处的访问冲突。读取地址FFFFFFDD
EDIT2 :感谢所有建议。不幸的是,我认为解决方案比这更深。我们使用补丁版本的Bold for Delphi,因为我们拥有源代码。可能在Bold框架中引入了一些错误。是的,我们有一个带有由JCL处理的callstack的日志以及跟踪消息。因此具有异常的callstack可以像这样锁定:
20091210 16:02:29 (2356) [EXCEPTION] Raised EBold: Failed to derive ServerSession.mayDropSession: Boolean
OCL expression: not active and not idle and timeout and (ApplicationKernel.allinstances->first.CurrentSession <> self)
Error: Access violation at address 00404E78 in module 'BoatLogisticsAMCAttracsServer.exe'. Read of address FFFFFFDD. At Location BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016)
Inner Exception Raised EBold: Failed to derive ServerSession.mayDropSession: Boolean
OCL expression: not active and not idle and timeout and (ApplicationKernel.allinstances->first.CurrentSession <> self)
Error: Access violation at address 00404E78 in module 'BoatLogisticsAMCAttracsServer.exe'. Read of address FFFFFFDD. At Location BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016)
Inner Exception Call Stack:
[00] System.TObject.InheritsFrom (sys\system.pas:9237)
Call Stack:
[00] BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016)
[01] BoldSystem.TBoldMember.DeriveMember (BoldSystem.pas:3846)
[02] BoldSystem.TBoldMemberDeriver.DoDeriveAndSubscribe (BoldSystem.pas:7491)
[03] BoldDeriver.TBoldAbstractDeriver.DeriveAndSubscribe (BoldDeriver.pas:180)
[04] BoldDeriver.TBoldAbstractDeriver.SetDeriverState (BoldDeriver.pas:262)
[05] BoldDeriver.TBoldAbstractDeriver.Derive (BoldDeriver.pas:117)
[06] BoldDeriver.TBoldAbstractDeriver.EnsureCurrent (BoldDeriver.pas:196)
[07] BoldSystem.TBoldMember.EnsureContentsCurrent (BoldSystem.pas:4245)
[08] BoldSystem.TBoldAttribute.EnsureNotNull (BoldSystem.pas:4813)
[09] BoldAttributes.TBABoolean.GetAsBoolean (BoldAttributes.pas:3069)
[10] BusinessClasses.TLogonSession._GetMayDropSession (code\BusinessClasses.pas:31854)
[11] DMAttracsTimers.TAttracsTimerDataModule.RemoveDanglingLogonSessions (code\DMAttracsTimers.pas:237)
[12] DMAttracsTimers.TAttracsTimerDataModule.UpdateServerTimeOnTimerTrig (code\DMAttracsTimers.pas:482)
[13] DMAttracsTimers.TAttracsTimerDataModule.TimerKernelWork (code\DMAttracsTimers.pas:551)
[14] DMAttracsTimers.TAttracsTimerDataModule.AttracsTimerTimer (code\DMAttracsTimers.pas:600)
[15] ExtCtrls.TTimer.Timer (ExtCtrls.pas:2281)
[16] Classes.StdWndProc (common\Classes.pas:11583)
内部异常部分是重新启动异常时的callstack。
EDIT3:现在的理论是虚拟内存表(VMT)以某种方式被破坏了。当发生这种情况时,没有迹象表明它。只有在调用方法时才会引发异常(始终,地址为FFFFFFDD,-35十进制)但是为时已晚。您不知道错误的真正原因。任何关于如何捕捉这样的bug的提示都非常感谢!!!我们尝试过使用SafeMM,但问题是即使使用3 GB标志,内存消耗也太高。所以现在我尝试给SO社区一个赏金:)
EDIT4:一个提示是根据日志,在此之前经常(或者甚至总是)有另一个例外。它可以是例如数据库中的乐观锁定。我们试图通过武力提出例外,但在测试环境中它只是工作正常。
EDIT5:故事继续......我现在搜索了过去30天的日志。结果:
所以当前的理论是枚举(Bold中有很多)覆盖指针。我有5个点击,上面有不同的地址。这可能意味着enum保存5个值,其中第二个值最常用。如果存在异常,则应对数据库进行回滚,并且应销毁Boldobjects。也许有可能不是所有东西都被破坏而且枚举仍然可以写入地址位置。如果这是真的,也许可以通过regexpr搜索具有5个值的枚举的代码?
EDIT6:总而言之,目前还没有解决问题的方法。我意识到我可能会用callstack误导你一点。是的,有一个计时器,但有其他没有计时器的callstack。对不起。但有两个共同因素。
这使我确信VilleK能够最好地描述问题。 我也确信问题出现在Bold框架中。 但 BIG 的问题是,这样的问题怎么解决? 像VilleK这样的断言是不够的,因为损坏已经发生并且那个时候信号堆消失了。因此,要描述我对可能导致错误的原因的看法:
这三个事件可以在代码中一起使用,但它们也可能在以后使用。我认为对于最后一次方法调用都是如此。
EDIT7:我们与Bold Jan Norden的作者密切合作,他最近在Bold框架中找到了OCL评估员的错误。当这个被修复时,这些异常减少了很多,但它们偶尔也会出现。但这几乎已经解决了,这是一个很大的缓解。
答案 0 :(得分:6)
如果
,你写下你想要的例外情况对应用程序未分配的内存地址进行写入
如果您的意思是要在应用程序的分配地址范围内检查无效的内存写入,那么您只能做很多事情。您应该使用FastMM4,并在应用程序的调试模式下将其用于最详细和偏执的设置。这将捕获大量无效写入,访问已释放的内存等,但它无法捕获所有内容。考虑一个指向另一个可写内存位置的悬空指针(如大字符串或浮点值数组的中间位置) - 写入它会成功,它会丢弃其他数据,但是内存管理器无法捕获这样的内容访问。
答案 1 :(得分:5)
我没有解决方案,但有一些关于该特定错误消息的线索。
System.TObject.InheritsFrom从Self-pointer(该类)中减去常量vmtParent,以获取指向父类地址的指针。
在Delphi 2007中定义了vmtParent:
vmtParent = -36;
因此,在这种情况下,错误$ FFFFFFDD(-35)听起来类指针为1。
以下是重现它的测试用例:
procedure TForm1.FormCreate(Sender: TObject);
var
I : integer;
O : tobject;
begin
I := 1;
O := @I;
O.InheritsFrom(TObject);
end;
我在Delphi 2010中尝试过并获取“读取地址FFFFFFD1”,因为vmtParent在Delphi版本之间是不同的。
问题是,这种情况发生在Bold框架内部,因此您可能无法在应用程序代码中防范它。
您可以在DMAttracsTimers代码中使用的对象上尝试此操作(我假设您的应用程序代码):
Assert(Integer(Obj.ClassType)<>1,'Corrupt vmt');
答案 2 :(得分:3)
听起来你有对象实例数据的内存损坏。
VMT本身没有被破坏,FWIW:VMT(通常)存储在可执行文件中,映射到它的页面是只读的。相反,正如VilleK所说,看起来你的情况下实例数据的第一个字段被一个值为1的32位整数覆盖。这很容易验证:检查方法调用失败的对象的实例数据,并验证第一个双字是00000001。
如果确实是实例数据中的VMT指针已被破坏,那么我就是如何找到破坏它的代码:
确保有一种自动方式来重现不需要用户输入的问题。由于Windows可能选择布局内存,因此只能在一台机器上重现此问题,而不会在复制之间重新启动。
重现问题并记下内存已损坏的实例数据的地址。
重新运行并检查第二次复制:确保第二次运行中已损坏的实例数据的地址与第一次运行时的地址相同。
现在,进入第三次运行,在前两次运行指示的内存部分放置一个4字节的数据断点。重点是打破对此内存的每次修改。至少有一个中断应该是填充VMT指针的TObject.InitInstance调用;可能还有其他与实例构造相关的内容,例如内存分配器;在最坏的情况下,相关的实例数据可能是以前实例中的回收内存。要减少所需的步进量,请使数据断点记录调用堆栈,但不要实际中断。通过在虚拟调用失败后检查调用堆栈,您应该能够找到错误的写入。
答案 3 :(得分:2)
请注意,通常在定期检查的堆分配之前和之后(在每次heapmgr访问时都会有障碍?)。
这有两个后果:
以下是其他一些需要考虑的事项:
如果这一切都没有产生任何结果,请尝试简化应用程序直到它消失。然后调查最近的评论/ ifdefed部分。
答案 4 :(得分:1)
我要做的第一件事就是将MadExcept添加到您的应用程序中,并获得一个打印出确切调用树的堆栈回溯,这将让您了解这里发生了什么。您需要查看调用树,而不是随机异常和二进制/十六进制内存地址,其中包含堆栈中所有参数和局部变量的值。
如果我怀疑在我的应用程序关键的结构中存在内存损坏,我会经常编写额外的代码来跟踪这个bug。
例如,在内存结构(类或记录类型)中可以安排在开头有一个Magic1:Word,在内存中每个记录的末尾有一个Magic2:Word。完整性检查功能可以通过查看每条记录查看Magic1和Magic2是否从构造函数中设置的更改来检查这些结构的完整性。析构函数会将Magic1和Magic2更改为其他值,例如$ FFFF。
我还会考虑将trace-logging添加到我的应用程序中。在delphi应用程序中跟踪日志记录通常从我声明一个TraceForm表单开始,其中有一个TMemo,而TraceForm.Trace(msg:String)函数则以“Memo1.Lines.Add(msg)”开头。随着我的应用程序的成熟,跟踪日志记录工具是我观察运行应用程序的行为和行为的整体模式的方式。然后,当发生“随机”崩溃或内存损坏且“无解释”时,我会有一个跟踪日志返回并查看导致此特定情况的原因。
有时它不是内存损坏而是简单的基本错误(我忘了检查是否分配了X,然后我去取消引用它:X.DoSomething(...)假定X已分配,但它不是。< / p>
答案 5 :(得分:1)
我注意到计时器在堆栈跟踪中
我看到了很多奇怪的错误,其中的原因是在我免费提供表单后触发了计时器事件
原因是可以在消息队列中放置一个计时器事件,并对其他组件的销毁进行处理。
解决该问题的一种方法是禁用计时器作为表单销毁中的第一个条目。禁用时间调用Application.processMessages后,在销毁组件之前处理任何计时器事件
另一种方法是检查表格是否在时间的推移中破坏。 (cs在组件状态中销毁)。
答案 6 :(得分:0)
您可以发布此程序的源代码吗?
BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016)
所以我们可以看到第4016行发生了什么。
还有这个功能的CPU视图?
(只需在此过程的第4016行设置断点并运行。如果点击断点,则复制+粘贴CPU视图内容)。
因此我们可以看到哪条CPU指令位于地址00404E78。
答案 7 :(得分:0)
重入代码会出现问题吗?
尝试在TTimer事件处理程序代码周围添加一些保护代码:
procedure TAttracsTimerDataModule.AttracsTimerTimer(ASender: TObject);
begin
if FInTimer then
begin
// Let us know there is a problem or log it to a file, or something.
// Even throw an exception
OutputDebugString('Timer called re-entrantly!');
Exit; //======>
end;
FInTimer := True;
try
// method contents
finally
FInTimer := False;
end;
end;
N - [
答案 8 :(得分:0)
我认为还有另一种可能性:触发计时器以检查是否存在“悬空登录会话”。然后,在TLogonSession对象上进行调用以检查它是否可以被删除(_GetMayDropSession),对吧?但是如果对象已被破坏怎么办?可能是由于线程安全问题或只是一个.Free调用而不是FreeAndNil调用(因此变量仍然是&lt;&gt; nil)等等。同时,创建其他对象以便重用内存。如果您稍后尝试访问该变量,则可能/将会出现随机错误...
一个例子:
procedure TForm11.Button1Click(Sender: TObject);
var
c: TComponent;
i: Integer;
p: pointer;
begin
//create
c := TComponent.Create(nil);
//get size and memory
i := c.InstanceSize;
p := Pointer(c);
//destroy component
c.Free;
//this call will succeed, object is gone, but memory still "valid"
c.InheritsFrom(TObject);
//overwrite memory
FillChar(p, i, 1);
//CRASH!
c.InheritsFrom(TObject);
end;
模块'Project10.exe'中地址004619D9的访问冲突。阅读地址01010101。
答案 9 :(得分:0)
问题是“_GetMayDropSession”是否引用了一个已释放的会话变量?
我之前已经看到过这种错误,在TMS中,对象被释放并在onchange等中被引用(仅在某些情况下它会产生错误,很难/不可能重现,现在由TMS修复:-))。另外,使用RemObjects会话,我得到了类似的东西(由于我自己的编程错误)。
我会尝试在会话类中添加一个虚拟变量并检查它的值: