We have a Delphi app that has been running for a few years and now suddenly we get weird access violations. We used Eurekalog to trace where it comes from and this is even more weird. They are so far all on the Free call of an object, but inside a try except block. One of them is even in 2 try except blocks and still when the Access Violation occurs it jumps totally out of the program and ignores the try excepts, nothing excepts EurekaLog catches it at the end. Really confused on why this is suddenly happening (both instances are old code that has not been touched for years and also other codes changes is not related to it).
A example of the code is
try
if Assigned(ClientCommunication) then begin
if ClientCommunication.isConnected then begin
if ClientCommunication.closeServerConnection then begin
try
ClientCommunication.Free;
ClientCommunication := nil;
Except
on e:Exception do begin
ClientCommunication := nil; //suppress weird AV error.. get read for new object
end
end;
Now the last try except was added later to try and suppress the AV as we just want the object cleared to be restarted if required, but this is mostly called when closing the app. But it still just jumps out of that and I can’t catch it at all.
It works on our developers PCs and not at the client.
If this line: ClientCommunication.Free; is causing an exception.
There are a few things you can do here.
FreeAndNil
Replace calls to AObject.free with calls to freeandnil(AObject).
If you just do the free, the old pointer will still have a non-nil reference and Assigned(AObject) will not be able to tell a freed and active object apart.
Beware of clones
Another mistake you may make is that you've cloned the object like so:
Object1:= TObject1.Create;
//.... lots of code
Object2:= Object1;
//.... lots of code
FreeAndNil(Object2); <<-- freeing the clone-reference is a mistake
//.... lots of code
Object1.Free; <<-- exception: it's already freed
FastMM4 options
Download the latest FastMM4 from http://sourceforge.net/projects/fastmm/
It has a few extra bells and whistles from the one included with Delphi.(*)
Among those whistles is an extra diagnostic mode that makes your program dead slow, but also catches out a lot of those heap corruption bugs you seem to be suffering from.
Open up defines.inc and change this:
{$ifdef DEBUG}
{.$define EnableMemoryLeakReporting}
{.$define FullDebugMode}
{.$define RawStackTraces}
{$endif DEBUG}
Into this
{$ifdef DEBUG}
{$define EnableMemoryLeakReporting}
{$define FullDebugMode}
{$define RawStackTraces}
{$define CatchUseOfFreedInterfaces} <<-- very useful
{$define LogMemoryLeakDetailToFile}
{$define LogErrorsToFile}
{$define CheckHeapForCorruption} <<-- :-)
{$endif}
There's a nice article here: http://wiert.me/2009/07/29/delphi-fastmm-using-fastmm4-for-debugging-your-memory-allocations-part-1-introduction/
If you don't feel like mucking about with the inc file, there a little utility that will do it for you at: http://jedqc.blogspot.com/2007/07/new-fastmm4-options-interface.html
(*) I think the Delphi one has most of the debug stuff as well, but not 100% sure. Regardless can't hurt to get the latest (greatest) version.
Or buffer overflow
If your ClientCommunication object has some internal structure free will do extra work.
Imagine the following code:
TUnrelatedObject = class
buffer: array[0..99] of integer;
procedure DoWork;
end;
TUnrelatedObject.DoWork;
var
i: integer;
begin
for i:= 0 to 100 do buffer[i]:= maxint; <<-- buffer overrun
end;
Imagine that ClientCommunication is right next to UnrelatedObject in the heap.
The call to DoWork will overwrite data of ClientCommunication as well.
This may or may not trigger an access violation in DoWork. If it doesn't then the error will be very hard to trace, because it will show up in a different and totally unrelated places.
Enable range checking {$R+} in your application.
Warning
Remember to not ship the debug version to your clients.
With all the debugging your program will be dead slow.
Related
I've created a console application and set ReportMemoryLeaksOnShutdown := True.
I've created a TStringList but did not free it.
When the program finishes executing, I see the memory leak for a brief second but then the console closes.
I've tried adding a ReadLn; to the end, but it only shows a blank console window when I do that, which makes sense.
I need to find a way to pause executing after the memory leak report, but before complete program shutdown.
I'm using Delphi 10 Seattle.
program Project1;
{$APPTYPE CONSOLE}
uses
System.Classes,
System.SysUtils;
var
s : TStringList;
begin
try
ReportMemoryLeaksOnShutdown := True;
s := TStringList.Create;
//ReadLn doesn't work here, which makes sense.
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
//I need to be able to pause the program somewhere after the end statement here.
end.
The easiest is to simply run the application in a previously opened command window.
If you insist on seeing the memory leak report while running in the IDE, do as follows:
Locate the ShowMessage procedure in GetMem.inc (line 4856 in Delphi 10 Seattle)
Place a breakpoint on the end; of that procedure.
Alternatively, as Sertac Akyuz commented, put a break point on the end. of the system unit.
You can also redirect the memory leak report to a file. Download the full version of FastMM from
https://sourceforge.net/projects/fastmm/
or better, thanks to Arioch 'The, from here:
https://github.com/pleriche/FastMM4
and set the needed options in FastMM4Options.inc
var
SaveExitProcessProc: procedure;
s: TStringList;
procedure MyExitProcessProc;
begin
ExitProcessProc := SaveExitProcessProc;
{$I-}
ReadLn;
{$I+}
end;
begin
SaveExitProcessProc := ExitProcessProc;
ExitProcessProc := MyExitProcessProc;
ReportMemoryLeaksOnShutdown := True;
s := TStringList.Create;
end.
That is a bug in recent Delphi versions. I just checked it in that recent free Delphi 10.1 Starter and it behaves as you describe - but as it provides no RTL sources I can not check the exact reason.
In Delphi XE2 it behaves as expected: creates the task-modal dialog and waits for you to react, just like described by Sertak.
In Delphi 10.1 the leak is indeed reported to the console window, but the program is not stopped to wait for user attention. That is poor solution, for both this reason and for the possible use of console programs in scripting (CMD or PS scripts would not "understand" this message and might confuse it with legitimate output and fail execution of further stages programs.
I think you have to open regression-type bug report over Delphi 10.0 - but I do not think they would fix it until 10.2 release.
I also switched your application from Delphi-forked memory manager to the original one, and then the erroneous behavior was reverted: the program displayed the message box and waited until I dismiss it before exiting into IDE.
Currently i suggest you to use the mentioned original memory manager rather than Delphi fork of it.
program Project1;
{$APPTYPE CONSOLE}
uses
FastMM4,
System.Classes,
System.SysUtils;
...
The original memory manager resides at http://github.com/pleriche/FastMM4
You can use Git client in your Delphi or a standalone one to keep yourself updated, or you can download the code once and stop updating, up to you.
The relevant quotes of its code are:
{$ifdef LogErrorsToFile}
{Set the message footer}
LMsgPtr := AppendStringToBuffer(LeakMessageFooter, LMsgPtr, Length(LeakMessageFooter));
{Append the message to the memory errors file}
AppendEventLog(#LLeakMessage[0], UIntPtr(LMsgPtr) - UIntPtr(#LLeakMessage[1]));
{$else}
{Set the message footer}
AppendStringToBuffer(LeakMessageFooter, LMsgPtr, Length(LeakMessageFooter));
{$endif}
{$ifdef UseOutputDebugString}
OutputDebugStringA(LLeakMessage);
{$endif}
{$ifndef NoMessageBoxes}
{Show the message}
AppendStringToModuleName(LeakMessageTitle, LMessageTitleBuffer);
ShowMessageBox(LLeakMessage, LMessageTitleBuffer);
{$endif}
end;
end;
{$endif}
end;
and
{Shows a message box if the program is not showing one already.}
procedure ShowMessageBox(AText, ACaption: PAnsiChar);
begin
if (not ShowingMessageBox) and (not SuppressMessageBoxes) then
begin
ShowingMessageBox := True;
MessageBoxA(0, AText, ACaption,
MB_OK or MB_ICONERROR or MB_TASKMODAL or MB_DEFAULT_DESKTOP_ONLY);
ShowingMessageBox := False;
end;
end;
This code depends upon being run on desktop Windows, so maybe Embarcadero tried to "fix" it to make it cross-platform. However the way they did it broken it on Windows console....
Also consider using adding other forms of logging - into the file and/or into the Windows Debug Strings. They would not be so attention-catching as the modal window, but would at least help you save the information, if you would know where to look for it.
This is certainly a hack, don't use in production :)
ReportMemoryLeaksOnShutdown:= True;
IsConsole:= False;
TStringList.Create;
However, it causes the leak message (and some other messages) to be displayed in a message box (where all text can be copied by pressing Ctrl+C).
(Tested with Delphi 10.2, please report any side effects we wouldn't like)
Set a breakpoint on "end." in system.pas.
However this solution is not completely ideal, because exit procedures/unit finalizations will still execute after this "end." statement.
This can be "checked" by F7/debugging/stepping into the "end." statement, it will lead to some assembler function and once the assembler function is exited by stepping over assembler instructions with F8 it will return to a function called "FinalizeUnits" in system.pas, where this function recursively calls itself to clean up the finalize sections of units I suppose.
So as long as you don't have to pause after the cleaning up of the finalization sections of units, this solution is not so bad.
However, cleaning up of units/finalization sections follows a certain order, it's likely that your own units's finalization section will be executed, before the memory manager is shutdown in the "end." statement.
Otherwise a different solution will have to be used.
To get into system.pas add it temporarely to a uses clausule or so, and choose open file, later removed it to prevent compile errors like:
"[dcc32 Error] TestProgram.dpr(8): E2004 Identifier redeclared: 'System'"
I use Delphi's LeakCheck library https://bitbucket.org/shadow_cs/delphi-leakcheck.
I know I can disable leak reporting using a construct like this:
{$IFDEF DEBUG}
System.ReportMemoryLeaksOnShutdown := true; // this will enable LeakCheck to display a message on Windows
{$ELSE}
System.ReportMemoryLeaksOnShutdown := false;
{$ENDIF}
But I also need the library to NOT collect any data when compiled in RELEASE mode.
I can easily "hack" the LeakCheck.pas initialization/finalization sections like this:
...
{$IFDEF DEBUG} // <--- Code added by me
initialization
TLeakCheck.Initialize;
finalization
TLeakCheck.Finalize;
{$ENDIF} // <--- Code added by me
end.
Is there any better way? A conditional define I miss or a global property?
I don't use the specific library (LeakCheck) you mention, but I typically do this by only including the unit when the right configuration is defined (in this case, DEBUG). This means that in release it's not even included in the executable.
uses
...,
{$IFDEF Debug}
LeakCheck,
{$ENDIF}
...;
As is pointed out in a comment, LeakCheck has to be the first unit listed in the .dpr's uses clause, which may cause an occasional problem with the IDE; it sometimes ends up breaking due to the {$IFDEF}. I usually don't find this to be a major issue, because once it happens and you've seen what the cause is, it's pretty easy to just go back in and fix it.
If that becomes too much of an issue, there is another workaround - create a new unit that does nothing but use LeakCheck and SysUtils, and add the above {$IFDEF} in that unit. You then include the new unit first in your .dpr. As it's only task is actually to use LeakCheck, it still puts LeakCheck in first-compile order in the .dpr when needed, and does not include it at all when not.
Update: The changes to introduce VCL styles in XE2 have removed the memory leak. So I guess it was unintentional after all.
I came across a VCL memory leak today, in Themes.pas. It only occurs for DLLs. The unit finalization code is as so:
finalization
if not IsLibrary then
InternalServices.Free;
InternalServices is a singleton that is created on demand when you call the ThemeServices function. Many DLLs do not have UI and so do not ever create this singleton. However, I happen to have a COM add-in to Excel which does result in this leak manifesting.
The leak doesn't particularly bother me because this DLL is never repeatedly loaded and unloaded from the same process. And I know how I could fix the leak using the ThemeServicesClass global variable.
My question though, is to ask if anyone can explain why this code is the way it is. It seems quite deliberately coded this way. For the life of me I cannot come up with an explanation for this intentional leak.
One explanation is that the unit finalization section is executed while the OS loader lock is active. During that time, there are significant restrictions on what a DLL is allowed to do without risking deadlock.
We run into this same problem as well. Here is what we are currently doing to prevent the leak in our projects:
In the dll .dpr file add Themes to the uses section.
Add the following to clean up the leak on shutdown:
procedure DLLEntryProc(EntryCode: integer);
begin
case EntryCode of
DLL_PROCESS_DETACH:
begin
ThemeServices.Free;
end;
DLL_PROCESS_ATTACH:
begin
end;
DLL_THREAD_ATTACH:
begin
end;
DLL_THREAD_DETACH:
begin
end;
end;
end;
begin
{$IFDEF DEBUG}
ReportMemoryLeaksOnShutdown := True;
{$ENDIF}
DllProc := #DLLEntryProc;
end.
On the same idea that there must be a good reason to leave it leaking (indy leaks a couple of things on purpose as well, or at least it used to) I prefer to ignore it, like so:
{$I FastMM4Options.inc}
...
{$IFDEF EnableMemoryLeakReporting}
if IsLibrary then// ThemeServices is leaked when created in a DLL.
RegisterExpectedMemoryLeak(ThemeServices);
{$ENDIF}
Mike Lischke's TThemeServices subclasses Application.Handle, so that it can receive broadcast notifications from Windows (i.e. WM_THEMECHANGED) when theming changes.
It subclasses the Application object's window:
FWindowHandle := Application.Handle;
if FWindowHandle <> 0 then
begin
// If a window handle is given then subclass the window to get notified about theme changes.
{$ifdef COMPILER_6_UP}
FObjectInstance := Classes.MakeObjectInstance(WindowProc);
{$else}
FObjectInstance := MakeObjectInstance(WindowProc);
{$endif COMPILER_6_UP}
FDefWindowProc := Pointer(GetWindowLong(FWindowHandle, GWL_WNDPROC));
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FObjectInstance));
end;
The subclassed window procdure then does, as it's supposed to, WM_DESTROY message, remove it's subclass, and then pass the WM_DESTROY message on:
procedure TThemeServices.WindowProc(var Message: TMessage);
begin
case Message.Msg of
WM_THEMECHANGED:
begin
[...snip...]
end;
WM_DESTROY:
begin
// If we are connected to a window then we have to listen to its destruction.
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FDefWindowProc));
{$ifdef COMPILER_6_UP}
Classes.FreeObjectInstance(FObjectInstance);
{$else}
FreeObjectInstance(FObjectInstance);
{$endif COMPILER_6_UP}
FObjectInstance := nil;
end;
end;
with Message do
Result := CallWindowProc(FDefWindowProc, FWindowHandle, Msg, WParam, LParam);
end;
The TThemeServices object is a singleton, destroyed during unit finalization:
initialization
finalization
InternalThemeServices.Free;
end.
And that all works well - as long as TThemeServices is the only guy who ever subclasses the Application's handle.
i have a similar singleton library, that also wants to hook Application.Handle so i can receive broadcasts:
procedure TDesktopWindowManager.WindowProc(var Message: TMessage);
begin
case Message.Msg of
WM_DWMCOLORIZATIONCOLORCHANGED: ...
WM_DWMCOMPOSITIONCHANGED: ...
WM_DWMNCRENDERINGCHANGED: ...
WM_DESTROY:
begin
// If we are connected to a window then we have to listen to its destruction.
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FDefWindowProc));
{$ifdef COMPILER_6_UP}
Classes.FreeObjectInstance(FObjectInstance);
{$else}
FreeObjectInstance(FObjectInstance);
{$endif COMPILER_6_UP}
FObjectInstance := nil;
end;
end;
with Message do
Result := CallWindowProc(FDefWindowProc, FWindowHandle, Msg, WParam, LParam);
And my singleton is similarly removed when the unit finalizes:
initialization
...
finalization
InternalDwmServices.Free;
end.
Now we come to the problem. i can't guarantee the order in which someone might choose to access ThemeServices or DWM, each of which apply their subclass. Nor can i know the order in which Delphi will finalize units.
The subclasses are being removed in the wrong order, and there is a crash on application close.
How to fix? How can i ensure that i keep my subclassing method around long enough until the other guy is done after me is done? (i don't want to leak memory, after all)
See also
Raymond Chen: Safer Subclassing
MSDN: Using Window Procedures
Raymond Chen: When the normal window destruction messages are thrown for a loop
Update: i see Delphi 7 solves the bug by rewriting TApplication. ><
procedure TApplication.WndProc(var Message: TMessage);
...
begin
...
with Message do
case Msg of
...
WM_THEMECHANGED:
if ThemeServices.ThemesEnabled then
ThemeServices.ApplyThemeChange;
...
end;
...
end;
Grrrr
In other words: trying to subclass TApplication was a bug, that Borland fixed when they adopted Mike's TThemeManager.
That very well may mean that there is no way to remove subclasses on TApplication in reverse order. Someone put that in the form of an answer, and i'll accept it.
Change your code to call SetWindowSubclass, as the article you linked to advises. But that only works if everyone uses the same API, so patch Theme Manager to use the same technique. The API was introduced in Windows XP, so there's no danger that it's not available on the systems where it would be needed.
There should be no problem with patching Theme Manager. It's designed to support Windows XP, which Microsoft doesn't support anymore, and to support Delphi 4 through 6, which Borland doesn't support anymore. Since development has ceased on all relevant products, it is safe for you to fork the Theme Manager project without risk of falling behind due to future updates.
You're not really introducing a dependency. Rather, you're fixing a bug that's only present when both window-appearance libraries are in use at the same time. Users of your library don't need to have Theme Manager for yours to work, but if they wish to use both, they need to use Theme Manager with your patches applied. There should be little objection to that since they already have the base version, so it's not like they'd be installing an entirely new library. They'd just be applying a patch and recompiling.
Rather than subclassing the TApplication window, perhaps you can use AllocateHWnd() instead to receive the same broadcasts separately, since it is a top-level window of its own.
I think I would do the following:
Put a reference to ThemeServices in the initialization section of ThemeSrv.pas.
Put a reference to DwmServices in the initialization section of DwmSrv.pas (I'm guess the name of your unit).
Since units are finalised in reverse order from the order of initialisation, your problem will be solved.
Why don't you just use the ApplicationEvents and be done with it. No need to messing around with subclassing.
The other way is to create only one subclass and create multi-notify events and subscribe as many as you wish.
Cheers
I am troubleshooting a problem with existing code that always worked fine (it's the Terminal Server unit from the Jedi Windows Security Library).
After some investigation the problem part has been brought down to a call to WTSOpenServer:
while true do
begin
hServer := WTSOpenServer(PChar('server'));
WTSCloseServer(hServer);
hServer := 0;
end;
After a random (but small) number or runs we get a total app crash which makes it hard to debug.
Here are the things I already tried:
WTSOpenServer does not write to the pServername parameter (like CreateProcessW) (in fact I checked the disassembly and it makes a copy)
The code runs fine when passing nil as parameter (and thus work with the localmachine).
When using a remote server, localhost or even dummy as pServerName the result is always crash (On Vista and higher even an invalid servername returns a valid handle as per documentation).
Tested with both Delphi 2009 and 2010
The same code runs fine in Visual Studio (c++).
Checked the disassembly in Visual Studio and made the call the WTSOpenServer in asm from Delphi (and change the Handle type to a pointer like in C):
hModule := LoadLibrary('wtsapi32.dll');
if hModule = 0 then
Exit;
WTSOpenServer := GetProcAddress(hModule, 'WTSOpenServerW');
if WTSOpenServer = nil then
Exit;
while true do
begin
asm
push dword ptr pServerName;
call dword ptr WTSOpenServer;
mov [hServer], eax;
end;
hServer := nil;
end;
Leave out the call to WTSCloseServer
Test the code on both x64 and x86 version of Windows 7
Use External Debugger instead of Delphi one (seems to run fine in that case so my guess is that it's some kind of timing/thread/deadlock issue)
Added AddVectoredExceptionHandler then I see a EXCEPTION_ACCESS_VIOLATION but the stacks seems to be corrupted, EIP is 1 so cannot determine where it happens.
At this point I don't know how to further troubleshoot this or find an explanation.
Try run your application with FastMM in FullDebugMode. It looks more like a bug in your/3rd party-lib code - possible memory overwrite/buffer overflow (moslty like sth. GetMem too small for UnicodeString/String alike operations, and it 'works' but will sooner or later crash/AV).
I've had several similar situations when migrating big app to D2009, and in most cases it was due to assumption Char=1 byte. Sometimes very strange things happened, but always FullDebugMode helped. Exception was CreateProcessW, but it's know/documented behaviour.
With FullDebugMode if app overwrite memory, then when you free it, FastMM gives you exception where it was allocated, so easly you can track down this bug. It adds some bytes at begining and end of allocation, so will know if it was overwritten.
I'm not able to reproduce it with new/empty VCL project, you can try it your self (this loop running for about 5 min):
uses JwaWtsApi32;
procedure TForm7.FormCreate(Sender: TObject);
var
hServer: DWORD;
begin
while true do
begin
hServer := WTSOpenServer(PChar('server'));
WTSCloseServer(hServer);
hServer := 0;
end;
end;