The customer did not actually ask any question. He just stated his app crashes. It was unclear if the customer is seeking help fixing a bug in his application, asking why there was no bug reported previosly, or he thinks it is some sort of "false-positive" bug report.
Well, any access violation exception is a definitive bug in the application, which can not be false-positive. It is the issue that needs to be fixed.
As for why there was no bug report before: many memory-related bugs are sensitive to how memory is allocated, so some changes in code may hide or reveal a memory bug. EurekaLog 7 does have memory checks - and these are enabled by default, unlike previous EurekaLog major versions. If you did not run your application with memory checks before - your application may have memory bugs hidden. So if you add EurekaLog 7 to your application, a previosly hidden bug may be revealed. We have many examples shown in stories from our customers.
Now, about troubleshooting the issue: we don't actually provide such a service. Yes, we offer support for our own product (EurekaLog), but debugging bugs in your application is outside the scope of our support. Currently we don't offer such consulting service. However, if it is not much trouble - we would gladly assist you unofficially.
So, we started by looking at the call stack from customer's bug report. Scrolling down the call stack - we can see his application is shutting down and cleaning up components:
...
Classes.TComponent.DestroyComponents
Forms.DoneApplication
SysUtils.DoExitProc
System._Halt0
Unit1.Form1.btnCloseClick 3854[1]
...
(all names from customer's code here and below were replaced with generic names)
First thing to notice is that functions from RTL do not have line numbers. Typically this happens because you did not configure your project for debugging. Specifically, you need to enable the "Use Debug DCUs" option. However, the customer uses Delphi 5, which does not have line number information for debug DCUs, so there is nothing he can do.
Next, walking the stack up, we can see some
TContosoViewPanel
class is being deleted as part of this process:...
Forms.TScrollingWinControl.Destroy
Contoso.TContosoViewPanel.Destroy 291 [16]
Controls.TWinControl.Destroy
...
The
TContosoViewPanel
class deletes some other class/component as part of its destruction:...
Controls.TControl.Destroy
System.TObject.Free
Controls.TWinControl.Destroy
...
Unfortunately, the call stack misses line numbers, but since the
Free
method is called from the TWinControl.Destroy
destructor - we are talking about deleting owned components. In other words, the TContosoViewPanel
class owns some component, and it deletes this component as part of its own destruction.When a component is deleted, it sends a notification about it being deleted:
...
Forms.TCustomForm.Notification
Classes.TComponent.RemoveComponent
Classes.TComponent.Destroy // owned component
...
This notification is being received by the (same?)
TContosoViewPanel
class:...
Classes.TList.Get // crashes here
Contoso.TContosoViewPanel.Notification 1732 [25]
Classes.TComponent.Notification
...
Summary: it is a pretty clear picture indicating a bug in the
TContosoViewPanel
class. Looks like the TContosoViewPanel
class owns some other component, but it does not expect this component to be deleted at the Contoso.TContosoViewPanel.Notification
1732 [25] line - which can happen during shutdown.The
TList.Get
method is really simple:function TList.Get(Index: Integer): Pointer; begin if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); Result := FList^[Index]; end;Let's take a look at the "Assembler" tab from the bug report to see if we can squeeze some extra information out of bug report:
00459C30 53 PUSH EBX ; start of the TList.Get 00459C31 56 PUSH ESI 00459C32 8BF2 MOV ESI, EDX 00459C34 8BD8 MOV EBX, EAX ; the EAX register is Self, it is stored into the EBX register ... ; Result := FList^[Index]; 00459C4E 8B4304 MOV EAX, [EBX+4] ; the FList field is read into the EAX register 00459C51 8B04B0 MOV EAX, [EAX+ESI*4] ; -- EXCEPTIONSo, the code from the
TList.Get
reads its FList
field (a pointer to an array) - which succeeds. And then it tries to dereference it - which fails.Let's also take a look at the exception's message: "Access violation at address 00459C51 in module 'Project1.exe'. Read of address 042C7D58". Match the exception message with disassembler output: obviosly the 042C7D58 address from the message is the result of [EAX + ESI * 4] from the disassember. We can learn what these registers are from the bug report's "CPU" tab:
EAX: 00D5B2AC
ESI: 00D5B2AB
The
EAX
is supposed to be an address of the dynamically allocated memory (array). The ESI
is supposed to be an index in the array. As you can see, the ESI
is clearly incorrect (it should be something like 0, 1, 2, etc.). Additionally, these two values seems to be aweful close to each other, which may indicate both EAX
and ESI
are incorrect and are part of something else.We can take a look at the "Modules" tab: the Project1.exe is loaded at $00400000 and have a size of 14749696 or $00E11000. $00400000 + $00E11000 = $01211000 - which is less than $00D5B2AC. It means that $00D5B2AC and $00D5B2AB are code addresses. In other words, $00D5B2AC can't be address of dynamic memory, because it is a pointer to code in the app.
Сorollary: both
EAX
(pointer to an array) and ESI
(index of the array) are incorrect and are, in fact, pointers to code. Possibly - functions.That's about all info that you can extract from the bug report. What's next? Well, assuming you have the same application that was used to produce the bug report:
- You can run your program;
- Pause it;
- Use View / Debug Windows / CPU;
- Right click on the code (disassembler view);
- Select "Goto address...";
- Enter $00D5B2AC (or $00D5B2AB);
- See where this will take you.
While we don't have the customer's source code, we can make a guess: both
TList
and its index are read by the Contoso.TContosoViewPanel.Notification
from some object. And that object is already deleted at this moment. The customer has access to its source code, so he can easily check where these values are coming from.When an object is deleted, EurekaLog fills that object with some values. Some of these values are pointers to the
DeadObjectVirtualMethodCall
function from the EMemLeaks
unit. This function will be called when someone will try to call a virtual method on the deleted object. Since $00D5B2AC and $00D5B2AB values are code addresses - I would speculate one of them is a pointer to the DeadObjectVirtualMethodCall
function, which further confirms the Contoso.TContosoViewPanel.Notification
accesses an already deleted object.Another way to tackle this is to place breakpoints on the mentioned lines 291 and 1732 of the Contoso.pas file. And walk step-by-step watching for the mentioned bug.
The customer replied back informing us he was able to find and fix the bug in his application armed with this information.