在 Delphi 中检测 VMT 或堆损坏的正确工具是什么?

发布于 2024-08-14 08:26:54 字数 4441 浏览 7 评论 0原文

我是使用 Delphi 2007 开发大型应用程序的团队的成员,我们怀疑堆损坏,因为有时会出现没有其他解释的奇怪错误。 我相信编译器的 Rangechecking 选项仅适用于数组。我想要一个工具,当对应用程序未分配的内存地址进行写入时,该工具可以给出异常或日志。

问候

编辑:错误类型为:

错误:模块“BoatLogisticsAMCAttracsServer.exe”中地址 00404E78 处存在访问冲突。阅读地址 FFFFFFDD

EDIT2:感谢您的所有建议。不幸的是,我认为解决方案比这更深刻。我们使用 Bold for Delphi 的修补版本,因为我们拥有源代码。 Bold 框架中可能引入了一些错误。是的,我们有一个包含由 JCL 处理的调用堆栈的日志以及跟踪消息。因此,带有异常的调用堆栈可以像这样锁定:

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)

内部异常部分是重新引发异常时的调用堆栈。

EDIT3:现在的理论是虚拟内存表(VMT)在某种程度上被破坏了。当这种情况发生时,没有任何迹象表明它发生。仅当调用方法时才会引发异常(地址 FFFFFFDD 上ALWAYS,十进制 -35),但为时已晚。您不知道错误的真正原因。任何有关如何捕获此类错误的提示都非常感谢!我们尝试过使用 SafeMM,但问题是即使使用 3 GB 标志,内存消耗也太高。所以现在我尝试向 SO 社区提供赏金:)

EDIT4: 一个提示是,根据日志,在此之前经常(甚至总是)存在另一个异常。例如,它可以是数据库中的乐观锁定。我们尝试过强制引发异常,但在测试环境中它工作得很好。

EDIT5:故事继续...我现在对过去 30 天的日志进行了搜索。结果:

  • "读取地址 FFFFFFDB" 0
  • "读取地址 FFFFFFDC" 24
  • "读取地址 FFFFFFDD" 270
  • "读取地址 FFFFFFDE" 22
  • "读取地址 FFFFFFDF" 7
  • "读取地址 FFFFFFE0" 20
  • "读取地址 FFFFFFE1 " 0

所以目前的理论是枚举(粗体有很多)覆盖指针。我得到了 5 次点击,上面的地址不同。这可能意味着枚举包含 5 个值,其中第二个值最常用。如果出现异常,数据库应发生回滚,并且 Bold 对象应被销毁。也许有可能并非所有内容都被破坏,并且枚举仍然可以写入地址位置。如果这是真的,也许可以通过 regexpr 搜索代码来查找具有 5 个值的枚举?

EDIT6:总而言之,目前还没有解决问题的方法。我意识到我可能会用调用堆栈误导您。是的,其中有一个计时器,但还有其他没有计时器的调用堆栈。对此感到抱歉。但有两个共同因素。

  • 读取地址 FFFFFFxx 时出现异常。
  • 调用堆栈的顶部是 System.TObject.InheritsFrom (sys\system.pas:9237)

这让我相信 VilleK 最好地描述了问题。 我也确信问题出在 Bold 框架的某个地方。 但问题是,如何解决这样的问题? 仅拥有像 VilleK 这样的断言建议是不够​​的,因为损坏已经发生,并且调用堆栈此时已经消失。因此,描述一下我对可能导致错误的原因的看法:

  1. 在某个地方,一个指针被分配了一个错误的值 1,但它也可以是 0、2、3 等。
  2. 一个对象被分配给该指针。
  3. 对象基类中有方法调用。这会导致调用 TObject.InheritsForm 方法,并在地址 FFFFFFDD 上出现异常。

这 3 个事件可以一起出现在代码中,但也可能在以后使用。我认为最后一个方法调用也是如此。

EDIT7:我们与 Bold Jan Norden 的作者密切合作,他最近在 Bold 框架的 OCL 评估器中发现了一个错误。当这个问题得到解决后,此类异常会减少很多,但它们仍然偶尔会出现。但令人欣慰的是,这个问题几乎已经解决了。

I'm a member in a team that use Delphi 2007 for a larger application and we suspect heap corruption because sometimes there are strange bugs that have no other explanation.
I believe that the Rangechecking option for the compiler is only for arrays. I want a tool that give an exception or log when there is a write on a memory address that is not allocated by the application.

Regards

EDIT: The error is of type:

Error: Access violation at address 00404E78 in module 'BoatLogisticsAMCAttracsServer.exe'. Read of address FFFFFFDD

EDIT2: Thanks for all suggestions. Unfortunately I think that the solution is deeper than that. We use a patched version of Bold for Delphi as we own the source. Probably there are some errors introduced in the Bold framwork. Yes we have a log with callstacks that are handled by JCL and also trace messages. So a callstack with the exception can lock like this:

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)

The inner exception part is the callstack at the moment an exception is reraised.

EDIT3: The theory right now is that the Virtual Memory Table (VMT) is somehow broken. When this happen there is no indication of it. Only when a method is called an exception is raised (ALWAYS on address FFFFFFDD, -35 decimal) but then it is too late. You don't know the real cause for the error. Any hint of how to catch a bug like this is really appreciated!!! We have tried with SafeMM, but the problem is that the memory consumption is too high even when the 3 GB flag is used. So now I try to give a bounty to the SO community :)

EDIT4: One hint is that according the log there is often (or even always) another exception before this. It can be for example optimistic locking in the database. We have tried to raise exceptions by force but in test environment it just works fine.

EDIT5: Story continues... I did a search on the logs for the last 30 days now. The result:

  • "Read of address FFFFFFDB" 0
  • "Read of address FFFFFFDC" 24
  • "Read of address FFFFFFDD" 270
  • "Read of address FFFFFFDE" 22
  • "Read of address FFFFFFDF" 7
  • "Read of address FFFFFFE0" 20
  • "Read of address FFFFFFE1" 0

So the current theory is that an enum (there is a lots in Bold) overwrite a pointer. I got 5 hits with different address above. It could mean that the enum holds 5 values where the second one is most used. If there is an exception a rollback should occur for the database and Boldobjects should be destroyed. Maybe there is a chance that not everything is destroyed and a enum still can write to an address location. If this is true maybe it is possible to search the code by a regexpr for an enum with 5 values ?

EDIT6: To summarize, no there is no solution to the problem yet. I realize that I may mislead you a bit with the callstack. Yes there are a timer in that but there are other callstacks without a timer. Sorry for that. But there are 2 common factors.

  • An exception with Read of address FFFFFFxx.
  • Top of callstack is System.TObject.InheritsFrom (sys\system.pas:9237)

This convince me that VilleK best describe the problem.
I'm also convinced that the problem is somewhere in the Bold framework.
But the BIG question is, how can problems like this be solved ?
It is not enough to have an Assert like VilleK suggest as the damage has already happened and the callstack is gone at that moment. So to describe my view of what may cause the error:

  1. Somewhere a pointer is assigned a bad value 1, but it can be also 0, 2, 3 etc.
  2. An object is assigned to that pointer.
  3. There is method call in the objects baseclass. This cause method TObject.InheritsForm to be called and an exception appear on address FFFFFFDD.

Those 3 events can be together in the code but they may also be used much later. I think this is true for the last method call.

EDIT7: We work closely with the the author of Bold Jan Norden and he recently found a bug in the OCL-evaluator in Bold framework. When this was fixed these kinds of exceptions decreased a lot but they still occasionally come. But it is a big relief that this is almost solved.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

烟雨扶苏 2024-08-21 08:26:54

您写道您希望有一个例外,如果

有一个写入未由应用程序分配的内存地址

,但无论如何都会发生,硬件< /a> 和 操作系统 确保这一点。

如果您的意思是要检查应用程序分配的地址范围内的无效内存写入,那么您能做的就只有这么多了。您应该使用 FastMM4,并在应用程序的调试模式下使用它及其最详细和偏执的设置。这将捕获大量无效写入、对已释放内存的访问等,但它无法捕获所有内容。考虑一个指向另一个可写内存位置的悬空指针(例如大字符串或浮点值数组的中间) - 写入它会成功,并且会丢弃其他数据,但内存管理器无法捕获此类数据使用权。

You write that you want there to be an exception if

there is a write on a memory address that is not allocated by the application

but that happens anyway, both the hardware and the OS make sure of that.

If you mean you want to check for invalid memory writes in your application's allocated address range, then there is only so much you can do. You should use FastMM4, and use it with its most verbose and paranoid settings in debug mode of your application. This will catch a lot of invalid writes, accesses to already released memory and such, but it can't catch everything. Consider a dangling pointer that points to another writeable memory location (like the middle of a large string or array of float values) - writing to it will succeed, and it will trash other data, but there's no way for the memory manager to catch such access.

倚栏听风 2024-08-21 08:26:54

我没有解决方案,但有一些关于该特定错误消息的线索。

System.TObject.InheritsFrom 从自指针(类)中减去常量 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”,因为 Delphi 版本之间的 vmtParent 不同。

问题是这种情况发生在 Bold 框架的深处,因此您可能无法在应用程序代码中防范它。

您可以在 DMAttracsTimers 代码(我假设是您的应用程序代码)中使用的对象上尝试此操作:

Assert(Integer(Obj.ClassType)<>1,'Corrupt vmt');

I don't have a solution but there are some clues about that particular error message.

System.TObject.InheritsFrom subtracts the constant vmtParent from the Self-pointer (the class) to get pointer to the adress of the parent class.

In Delphi 2007 vmtParent is defined:

vmtParent = -36;

So the error $FFFFFFDD (-35) sounds like the class pointer is 1 in this case.

Here is a test case to reproduce it:

procedure TForm1.FormCreate(Sender: TObject);
var
  I : integer;
  O : tobject;
begin
  I := 1;
  O := @I;
  O.InheritsFrom(TObject);
end;

I've tried it in Delphi 2010 and get 'Read of address FFFFFFD1' because the vmtParent is different between Delphi versions.

The problem is that this happens deep inside the Bold framework so you may have trouble guarding against it in your application code.

You can try this on your objects that are used in the DMAttracsTimers-code (which I assume is your application code):

Assert(Integer(Obj.ClassType)<>1,'Corrupt vmt');
知你几分 2024-08-21 08:26:54

听起来对象实例数据的内存已损坏。

VMT 本身并没有被损坏,FWIW:VMT(通常)存储在可执行文件中,并且映射到它的页面是只读的。相反,正如 VilleK 所说,您的情况下实例数据的第一个字段似乎被值为 1 的 32 位整数覆盖。这很容易验证:检查方法调用失败的对象的实例数据,并验证第一个双字是否为 00000001。

如果确实是实例数据中的 VMT 指针被损坏,那么我将如何找到损坏它的代码:

  1. 确保有一种自动方法来重现不需要用户输入的问题。由于 Windows 可能如何选择内存布局,该问题可能只能在一台计算机上重现,而无需在重现之间重新启动。

  2. 重现问题并记下内存损坏的实例数据的地址。

  3. 重新运行并检查第二次复制:确保第二次运行中损坏的实例数据的地址与第一次运行的地址相同。

  4. 现在,进入第三次运行,在前两次运行指示的内存部分上放置一个 4 字节数据断点。关键是要中断对此内存的每次修改。至少一个中断应该是填充 VMT 指针的 TObject.InitInstance 调用;可能还有其他与实例构造相关的内容,例如内存分配器中;在最坏的情况下,相关的实例数据可能已经从之前的实例中回收了内存。要减少所需的单步执行量,请使数据断点记录调用堆栈,但不实际中断。通过在虚拟调用失败后检查调用堆栈,您应该能够找到错误的写入。

It sounds like you have memory corruption of object instance data.

The VMT itself isn't getting corrupted, FWIW: the VMT is (normally) stored in the executable and the pages that map to it are read-only. Rather, as VilleK says, it looks like the first field of the instance data in your case got overwritten with a 32-bit integer with value 1. This is easy enough to verify: check the instance data of the object whose method call failed, and verify that the first dword is 00000001.

If it is indeed the VMT pointer in the instance data that is being corrupted, here's how I'd find the code that corrupts it:

  1. Make sure there is an automated way to reproduce the issue that doesn't require user input. The issue may be only reproducible on a single machine without reboots between reproductions owing to how Windows may choose to lay out memory.

  2. Reproduce the issue and note the address of the instance data whose memory is corrupted.

  3. Rerun and check the second reproduction: make sure that the address of the instance data that was corrupted in the second run is the same as the address from the first run.

  4. Now, step into a third run, put a 4-byte data breakpoint on the section of memory indicated by the previous two runs. The point is to break on every modification to this memory. At least one break should be the TObject.InitInstance call which fills in the VMT pointer; there may be others related to instance construction, such as in the memory allocator; and in the worst case, the relevant instance data may have been recycled memory from previous instances. To cut down on the amount of stepping needed, make the data breakpoint log the call stack, but not actually break. By checking the call stacks after the virtual call fails, you should be able to find the bad write.

心房敞 2024-08-21 08:26:54

麦吉当然是对的。 (fastmm4 调用标志 fulldebugmode 或类似的东西)。

请注意,这通常适用于定期检查堆分配之前和之后的障碍(在每次 heapmgr 访问时?)。

这有两个后果:

  • fastmm 检测到错误的位置可能偏离发生错误的位置,
  • 可能无法检测到完全随机写入(不是现有分配的溢出)。

因此,这里还有一些其他需要考虑的事情:

  • 启用运行时检查
  • 检查所有编译器的警告。
  • 尝试使用不同的delphi版本或FPC进行编译。其他编译器/rtls/堆管理器具有不同的布局,这可能导致更容易捕获错误。

如果这一切都没有产生任何结果,请尝试简化应用程序,直到它消失。然后调查最近评论/ifdefed 的部分。

mghie is right of course. (fastmm4 calls the flag fulldebugmode or something like that).

Note that that works usually with barriers just before and after an heap allocation that are regularly checked (on every heapmgr access?).

This has two consequences:

  • the place where fastmm detects the error might deviate from the spot where it happens
  • a total random write (not overflow of existing allocation) might not be detected.

So here are some other things to think about:

  • enable runtime checking
  • review all your compiler's warnings.
  • Try to compile with a different delphi version or FPC. Other compilers/rtls/heapmanagers have different layouts, and that could lead to the error being caught easier.

If that all yields nothing, try to simplify the application till it goes away. Then investigate the most recent commented/ifdefed parts.

旧夏天 2024-08-21 08:26:54

我要做的第一件事就是将 MadExcept 添加到您的应用程序中,并获取堆栈回溯,打印出确切的调用树,这将使您了解这里发生的情况。您需要查看一个调用树,其中包含堆栈中的所有参数和局部变量的值,而不是随机异常和二进制/十六进制内存地址。

如果我怀疑应用程序关键结构中的内存损坏,我通常会编写额外的代码来跟踪此错误。

例如,可以将内存结构(类或记录类型)安排为在内存中每个记录的开头有一个 Magic1:Word,在结尾有一个 Magic2:Word。完整性检查函数可以通过查看每个记录 Magic1 和 Magic2 是否与构造函数中设置的值相比是否发生更改来检查这些结构的完整性。析构函数会将 Magic1 和 Magic2 更改为其他值,例如 $FFFF。

我还会考虑向我的应用程序添加跟踪日志记录。 delphi 应用程序中的跟踪日志记录通常从我声明一个 TraceForm 表单开始,其中包含一个 TMemo,TraceForm.Trace(msg:String) 函数以“Memo1.Lines.Add(msg)”开始。随着我的应用程序的成熟,跟踪日志记录工具是我观察运行应用程序的整体行为模式和不当行为的方式。然后,当发生“随机”崩溃或“没有解释”的内存损坏时,我可以回溯跟踪日志,看看是什么导致了这种特殊情况。

有时,这不是内存损坏,而是简单的基本错误(我忘记检查 X 是否已分配,然后我取消引用它: X.DoSomething(...) 假设 X 已分配,但事实并非如此。

The first thing I would do is add MadExcept to your application and get a stack traceback that prints out the exact calling tree, which will give you some idea what is going on here. Instead of a random exception and a binary/hex memory address, you need to see a calling tree, with the values of all parameters and local variables from the stack.

If I suspect memory corruption in a structure that is key to my application, I will often write extra code to make tracking this bug possible.

For example, in memory structures (class or record types) can be arranged to have a Magic1:Word at the beginning and a Magic2:Word at the end of each record in memory. An integrity check function can check the integrity of those structures by looking to see for each record Magic1 and Magic2 have not been changed from what they were set to in the constructor. The Destructor would change Magic1 and Magic2 to other values such as $FFFF.

I also would consider adding trace-logging to my application. Trace logging in delphi applications often starts with me declaring a TraceForm form, with a TMemo on there, and the TraceForm.Trace(msg:String) function starts out as "Memo1.Lines.Add(msg)". As my application matures, the trace logging facilities are the way I watch running applications for overall patterns in their behaviour, and misbehaviour. Then, when a "random" crash or memory corruption with "no explanation" happens, I have a trace log to go back through and see what has lead to this particular case.

Sometimes it is not memory corruption but simple basic errors (I forgot to check if X is assigned, then I go dereference it: X.DoSomething(...) that assumes X is assigned, but it isn't.

泛滥成性 2024-08-21 08:26:54

我注意到堆栈跟踪中有一个计时器。
我见过很多奇怪的错误,其原因是在释放表单后触发了计时器事件。
原因是计时器事件可能会被放在消息队列上,并且不会被处理以破坏其他组件。
解决该问题的一种方法是禁用计时器作为销毁表单的第一个条目。禁用时间调用 Application.processMessages 后,因此在销毁组件之前会处理任何计时器事件。
另一种方法是检查表单是否在计时器事件中被破坏。 (组件状态中的 csDestroying)。

I Noticed that a timer is in the stack trace.
I have seen a lot of strange errors where the cause was the timer event is fired after the form i free'ed.
The reason is that a timer event cound be put on the message que, and noge get processed brfor the destruction of other components.
One way around that problem is disabling the timer as the first entry in the destroy of the form. After disabling the time call Application.processMessages, so any timer events is processed before destroying the components.
Another way is checking if the form is destroying in the timerevent. (csDestroying in componentstate).

眉目亦如画i 2024-08-21 08:26:54

能贴一下这个程序的源代码吗?

BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression
(BoldSystem.pas:4016)

所以我们可以看到第 4016 行发生了什么。

还有这个函数的 CPU 视图?
(只需在此过程的第 4016 行设置一个断点并运行。如果遇到断点,则复制+粘贴 CPU 视图内容)。
所以我们可以看到地址00404E78处是哪条CPU指令。

Can you post the sourcecode of this procedure?

BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression
(BoldSystem.pas:4016)

So we can see what's happening on line 4016.

And also the CPU view of this function?
(just set a breakpoint on line 4016 of this procedure and run. And copy+paste the CPU view contents if you hit the breakpoint).
So we can see which CPU instruction is at address 00404E78.

星軌x 2024-08-21 08:26:54

可重入代码会不会有问题?

尝试在 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@

Could there be a problem with re-entrant code?

Try putting some guard code around the TTimer event handler code:

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@

清晰传感 2024-08-21 08:26:54

我认为还有另一种可能性:触发计时器以检查是否存在“悬空登录会话”。然后,对 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。

I think there is another possibility: the timer is fired to check if there are "Dangling Logon Sessions". Then, a call is done on a TLogonSession object to check if it may be dropped (_GetMayDropSession), right? But what if the object is destroyed already? Maybe due to thread safety issues or just a .Free call and not a FreeAndNil call (so a variable is still <> nil) etc etc. In the mean time, other objects are created so the memory gets reused. If you try to acces the variable some time later, you can/will get random errors...

An example:

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;

Access violation at address 004619D9 in module 'Project10.exe'. Read of address 01010101.

半夏半凉 2024-08-21 08:26:54

问题不是“_GetMayDropSession”引用已释放的会话变量吗?

我以前见过这种错误,在 TMS 中,对象被释放并在 onchange 等中引用(仅在某些情况下它给出错误,非常困难/不可能重现,现在已由 TMS 修复:-))。另外,在 RemObjects 会话中,我得到了类似的东西(由于我自己的错误编程错误)。

我会尝试向会话类添加一个虚拟变量并检查它的值:

  • 公共变量 iMagicNumber:整数;
  • 构造函数创建:iMagicNumber := 1234567;
  • 析构函数 destroy: iMagicNumber := -1;
  • “其他程序”:assert(iMagicNumber = 1234567)

Isn't the problem that "_GetMayDropSession" is referencing a freed session variable?

I have seen this kind of errors before, in TMS where objects were freed and referenced in an onchange etc (only in some situations it gave errors, very difficult/impossible to reproduce, is fixed now by TMS :-) ). Also with RemObjects sessions I got something similar (due to bad programming bug by myself).

I would try to add a dummy variable to the session class and check for it's value:

  • public variable iMagicNumber: integer;
  • constructor create: iMagicNumber := 1234567;
  • destructor destroy: iMagicNumber := -1;
  • "other procedures": assert(iMagicNumber = 1234567)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文