views:

506

answers:

3

Hi. I had this nasty bug that disappeared in the past but now after quite some time it returned.

I have two TSam objects (derived from TPersistent) created and loaded into an TAsmJob object (derived from TObjectList).

At runtime, a form creates a TStringGrid and then the AsmJob which creates those two SAM objects (and load some data from disk in each of them). The AsmJob is also assigned to the grid. When the form is destroyed, the Grid takes care of the AsmJob by freeing it, which frees the TSam objects. Here is the problem: the first object is freed withot problems but the second one dies when its inherited method (in Destroy destructor) is called.

I use FreeAndNil in the entire program to free the objects. The TSam objects are not NIL!!!!! So, this is the first attempt to free the objects. Even the data inside the objects is consistent.

The backbone of the program looks like this:

**Create:**

Form -> StringGrid
     -> AsmJob -> Sam1, Sam2
StringGrid.AsmJob:= AsmJob;


**Free:**

Form -> StringGrid -> AsmJob -> Sam1, Sam2

I really don’t understand where I try to double-free or overwrite the object AFTER it has been released.


edit:

Some of the errors I got:

  • FastMM has detected an error during a free block scan operation. FastMM detected that a block has been modified after being freed.

  • FastMM has detected an error during a free block scan operation. The block header has been corrupted.

Detail:

The current thread ID is 0x19C, and the stack trace (return addresses) leading to this error is: 
402E77 [System][@FreeMem] 
4068DC [System][@DynArrayClear] 
405E2D [System][@FinalizeArray] 
405D31 [System][@FinalizeRecord] 
40432F [System][TObject.CleanupInstance] 
404272 [System][TObject.FreeInstance] 
404641 [System][@ClassDestroy] 
4D313E [UnitSam.pas][TSam.Destroy][297] 
4042BF [System][TObject.Free] 
4149ED [SysUtils][FreeAndNil] 
4D9C0A [UnitAsmJob.pas][UnitAsmJob][TAsmJob.Destroy][180]

I have all "debug" options enabled in the IDE, including the "Range Check". Also, the FastMM4 is set to super aggressive debug mode. Without FastMM or outside of the debugger the program runs just fine - but yet I know it doesn't mean that the bug is not there anymore. Actually it worked (probably) for more than one one year, until I have installed FastMM.


edit:

Thanks to everybody. No I am feeling I am moving a bit in the good direction.

The structure of the program is more complicated I offered only the backbone to keep the original post small. But what the heck, it already got larger :) So, those TSam objects are used to load data from disk. One file in each object. They are doing also some processing and data validation. For each of these TSam I also have a graphical object that shows on the screen (graphically) the data contained in the TSam objects. Each line in the TStringGrid also show the data in TSam, but textually.

One question I have: if I break the program in smaller pieces to find out where the error is, the error will still appear? Or it is possible to appear only in this particular configuration?


Answer to "how does the AsmJob get assigned to TStringGrid so that the TStringGrid destroys the AsmJob, can you show us?"

MyGrid = TStringGrid
  public 
    AsmJob: TAsmJob; 
  end;

then somewhere in the TForm.Create (the form that holds the Grid), I do

MyGrid.AsmJob=AsmJob;

and in the destructor of the MyGrid I do:

begin 
  FreeAndNil(AsmJob); 
  inherited 
end;
+3  A: 

A block header getting corrupted usually means something's been overwriting memory, usually by doing some sort of unsafe operation. Are you using raw pointers or assembly code in any of your tasks? Also, if you have range checking and bounds checking turned off, try turning them on and rebuilding. They can help catch a lot of this sort of problem.

Mason Wheeler
Hi Mason.No ASM, no raw pointer operation, range checking is ALWAYS on.This is exactly what I am trying to figure out the whole day today: where I could possible overwrite that damn object.
Altar
I saw a situation like this once before. Eventually tracked it down to a 3rd party library I was using that was doing some operations with raw pointers and fumbling a few of them. Might that be the case here?
Mason Wheeler
BTW it doesn't look like it's not the object that's being overwritten, but the memory block header. But just to be sure, try putting a breakpoint in the cleanup code that will allow you to examine the state of this object before the destructor runs. Look at it in the debugger and make sure its fields are valid. (If not, then you can use an address breakpoint to make finding whatever's overwriting your memory trivial.)
Mason Wheeler
+1  A: 

A couple of things and I'm asking because I can't see your code.

Given the following code:

procedure TForm1.FormCreate(Sender: TObject);
var
   wObjLst : TObjectList;
begin
   wObjLst := TObjectList.Create;
   try
      wObjlst.OwnsObjects := true;
      wObjlst.Add(TPersistent.Create);
      wObjlst.Add(TPersistent.Create);
   finally
      freeandnil(wObjlst);
   end;
end;

This works with out error.

You state that

At runtime, a form creates a TStringGrid and then the AsmJob which creates those two SAM objects (and load some data from disk in each of them). The AsmJob is also assigned to the grid. When the form is destroyed, the Grid takes care of the AsmJob by freeing it, which frees the TSam objects. Here is the problem: the first object is freed withot problems but the second one dies when its inherited method (in Destroy destructor) is called.

My first question is how does the AsmJob get assigned to TStringGrid so that the TStringGrid destroys the AsmJob, can you show us?

Second, why create a descendant of TObjectList to get it to store two objects and then free free them instead of creating them yourself and letting the TObjectList destroy them as shown above.

The other thing to try is to download the full FastMM4 package from fastmm.sourceforge.net, install it and use the fulldebug dll to trace out exactly what object is failing. You and I are assuming that it's one of the SAM objects and it might or might not be.

Ryan J. Mills
Hi Ryan. Originally, the ObjectList was set to OwnObj=true, but now I free the objects "manually" to see where the error appears. This is how I pinpointed that the error appears in the call of inherited (destroy) of the TSam objects.ANYWAY! If I create and use the TSam objects without manually (this means without TAsmJob - which is a kind of manager of these objects) everything works just wonderful. I have no errors at all.--------PS: I have already the FastMM in full debug mode.
Altar
+4  A: 

This error means that your code corrupted internal memory manager's structures. Your call stack represents point, when MM detected this. This is not error path or anything related to it. The actual error happens BEFORE this moment. It may or may be not related to mentioned classes.

You should try to use "Range check errors" option (don't forget to make Build, not Compile) and FastMM in full debug mode (with CheckHeapForCorruption, CatchUseOfFreedInterfaces и DetectMMOperationsAfterUninstall options enabled).

You can also turn on FullDebugModeScanMemoryPoolBeforeEveryOperation global variable, to get an error almost immediately after problem occurs, but this option slows down your execution A LOT.

Probably the best choice is call ScanMemoryPoolForCorruptions periodically. Call it in one place. Got an error? Call it sooner. Still got an error? Call it sooner again. No error? Your problem sits somewhere between those last calls. Now you can use FullDebugModeScanMemoryPoolBeforeEveryOperation variable to get precise location. Just turn it on only on this code's area and turn it off right after it.

There is a very similar error: "FastMM detected that a block has been modified after being freed". In this case your code modifies not internal structures, but other memory, which isn't used at all ("free memory").

BTW, your error is NOT double-free! If this is a double-free call, FastMM will tell you that explicitly (it is easy to detect, as you are trying to free not-used or not-existed memory block): "An attempt has been made to free/reallocate an unallocated block".

Alexander
Thanks Alexander. I had no clue about "ScanMemoryPoolForCorruptions". I guess is a function offered by the FastMM DLL. I will go to search about it right now.
Altar
That is the function from standard FastMM4.pas. It is from full standalone version of FastMM. It is not exist in FastMM's version, which is integrated into Delphi.There is no DLL in question here. This is just a function in usual pas file ;)
Alexander