Avatar billede morten_s Nybegynder
03. juni 2002 - 21:01 Der er 13 kommentarer og
1 løsning

Memory Leak

Findes der andre metoder til at afgøre om man har et memory leak end windows jobsliste.

Hvis der gør, hvilke og hvordan ?

Avatar billede pellelil Nybegynder
03. juni 2002 - 21:08 #1
Joblisten kan IKKE bruges som et debug tool, men efter sigende skulle denne her kunne gøre det http://v.mahon.free.fr/pro/freeware/memcheck/
Avatar billede morten_s Nybegynder
03. juni 2002 - 22:42 #2
Pellelil> Tak for det gode link, jeg har allerede installeret det, men hvad er din erfaring med dette værktøj, virker det ?

Hvis det gør har jeg et seriøst problem med et 3. parts produkt til interface til MySql databasen, jeg får fejlrepport lige såsnart jeg klasker et af dem på ???
Avatar billede morten_s Nybegynder
03. juni 2002 - 23:07 #3
Pellelil> Prøv at putte en idTCPClient på en form..... kan det virklig passe at der er en memoryleak i en sådan ?
Avatar billede borrisholt Novice
04. juni 2002 - 08:23 #4
Jeg fik den her af Pelle den anden dag :

(*
MemCheck: the ultimate memory troubles hunter
Created by: Jean Marc Eber & Vincent Mahon, Société Générale, INFI/SGOP/R&D
http://www.multimania.com/vincentmahon/memcheck.htm
Version 2.52    -> Also update OutputFileHeader when changing the version #

Contact...
Jean Marc Eber is at: Jean-Marc.Eber@socgen.com
Vincent Mahon is at: Vincent.Mahon@socgen.com

http://www.multimania.com/vincentmahon/memcheck.htm

Our address is:
  Tour Société Générale
  Infi/Sgop/R&D
  92987 Paris - La Défense cedex
  France

Copyrights...
The authors grant you the right to modify/change the source code as long as the original authors are mentionned.
Please let us know if you make any improvements, so that we can keep an up to date version. We also welcome
all comments, preferably by email.

Portions of this file (all the code dealing with TD32 debug information) where derived from the following work, with permission.
Reuse of this code in a commercial application is not permitted. The portions are identified by a copyright notice.
> DumpFB.C Borland 32-bit Turbo Debugger dumper (FB09 & FB0A)
> Clive Turvey, Electronics Engineer, July 1998
> Copyright (C) Tenth Planet Software Intl., Clive Turvey 1998. All rights reserved.
> Clive Turvey <clive@tbcnet.com> http://www.tbcnet.com/~clive/vcomwinp.html

Disclaimer...
You use MemCheck at your own risks. This means that you cannot hold the authors or Société Générale to be
responsible for any software\hardware problems you may encounter while using this module.

General information...
MemCheck replaces Delphi's memory manager with a home made one. This one logs information each time memory is
allocated, reallocated or freed. When the program ends, information about memory problems is provided in a log file
and exceptions are raised at problematic points.

Basic use...
Set the MemCheckLogFileName option. Call MemChk when you want to start the memory monitoring. Nothing else to do !
When your program terminates and the finalization is executed, MemCheck will report the problems. This is the
behaviour you'll obtain if you change no option in MemCheck.

Features...
- List of memory spaces not deallocated, and raising of EMemoryLeak exception at the exact place in the source code
- Call stack at allocation time. User chooses to see or not to see this call stack at run time (using ShowCallStack),
  when a EMemoryLeak is raised.
- Tracking of virtual method calls after object's destruction (we change the VMT of objects when they are destroyed)
- Tracking of method calls on an interface while the object attached to the interface has been destroyed
- Checking of writes beyond end of allocated blocks (we put a marker at the end of a block on allocation)
- Fill freed block with a byte (this allows for example to set fields of classes to Nil, or buffers to $FF, or whatever)
- Detect writes in deallocated blocks (we do this by not really deallocating block, and checking them on end - this
  can be time consuming)
- Statistics collection about objects allocation (how many objects of a given class are created ?)
- Time stamps can be indicated and will appear in the output

Options and parameters...
- You can specify the log files names (MemCheckLogFileName)
- It is possible to tell MemCheck that you are instanciating an object in a special way - See doc for
  CheckForceAllocatedType
- Clients can specify the depth of the call stack they want to store (StoredCallStackDepth)


Warnings...
- MemCheck is based on a lot of low-level hacks. Some parts of it will not work on other versions of Delphi
without being revisited (as soon as System has been recompiled, MemCheck is very likely to behave strangely,
because for example the address of InitContext will be bad).
- As leaks are reported on end of execution (finalization of this unit), we need as many finalizations to occur
before memcheck's, so that if so memory is freed in these finalizations, it is not reported as leak. In order to
finalize MemCheck as late as possible, we use a trick to change the order of the list of finalizations. After
MemCheck are finalized only SysUtils, System and SysInit. This implies that we must not use any other unit which
has a finalization. For example, we can not use the unit classes, so we have to implement our own lists and string
lists here. Other memory managing products which are available (found easily on the internet) do not have this
problem because they just rely on putting the unit first in the DPR; but they report incorrect leaks ! MemCheck does
not, as far as we know.
- Some debugging tools exploit the map file to return source location information. We chose not to do that, because
we think the way MemCheck raises exceptions at the good places is better. It is still possible to use "find error"
in Delphi.
- Memcheck is not able to report accurate call stack information about a leak of a class which does not redefine
its constructor. For example, if an instance of TStringList is never deallocated, the call stack MemCheck will
report is not very complete. However, the leak is correctly reported by MemCheck.
*)
unit MemCheck;
{$A+}
{$H+}

interface

procedure MemChk;
{Activates MemCheck and resets the allocated blocks stack.
Warning: the old stack is lost ! - It is the client's duty to commit the
releasable blocks by calling CommitReleases(AllocatedBlocks)}

procedure UnMemChk;
{sets back the memory manager that was installed before MemChk was called
If MemCheck is not active, this does not matter. The default delphi memory manager is set.
      You should be very careful about calling this routine and know exactly what it does (see the FAQ on the web site)}

procedure CommitReleases;
{really releases the blocks}

procedure AddTimeStampInformation(const I: string);
{Logs the given information as associated with the current time stamp
Requires that MemCheck is active}

procedure LogSevereExceptions(const WithVersionInfo: string);
{Activates the exception logger}

function MemoryBlockCorrupted(P: Pointer): Boolean;
{Is the given block bad ?
P is a block you may for example have created with GetMem, or P can be an object.
Bad means you have written beyond the block's allocated space or the memory for this object was freed.
If P was allocated before MemCheck was launched, we return False}

function BlockAllocationAddress(P: Pointer): Pointer;
{The address at which P was allocated
If MemCheck was not running when P was allocated (ie we do not find our magic number), we return $00000000}

function IsMemCheckActive: boolean;
{Is MemCheck currently running ?
ie, is the current memory manager memcheck's ?}

function TextualDebugInfoForAddress(const TheAddress: Cardinal): string;

var
    MemCheckLogFileName: string = 'c:\Temp\MemCheck.log';
    {The file memcheck will log information to}

    DeallocateFreedMemoryWhenBlockBiggerThan: Integer = 0;
    {should blocks be really deallocated when FreeMem is called ? If you want all blocks to be deallocated, set this
    constant to 0. If you want blocks to be never deallocated, set the cstte to MaxInt. When blocks are not deallocated,
    NewsFinder can give information about when the second deallocation occured}

    ShowLogFileWhenUseful: Boolean = True;

const
    StoredCallStackDepth = 26;
    {Size of the call stack we store when GetMem is called, must be an EVEN number}

type
    TCallStack = array[0..StoredCallStackDepth] of Pointer;

procedure FillCallStack(var St: TCallStack; const ExcludeFirstLevel: Boolean);
//Fills St with the call stack

function CallStackTextualRepresentation(const S: TCallStack; const LineHeader: string): string;
//Will contain CR/LFs

implementation

uses
    Windows,                            {Windows has no finalization, so is OK to use with no care}
    Math,
    SysUtils;                          {Because of this uses, SysUtils must be finalized after MemCheck - Which is necessary anyway because SysUtils calls DoneExceptions in its finalization}

type
    TKindOfMemory = (MClass, MUser, MReallocedUser);
    {MClass means the block carries an object
    MUser means the block is a buffer of unknown type (in fact we just know this is not an object)
    MReallocedUser means this block was reallocated}

const
    (**************** MEMCHECK OPTIONS ********************)
    DanglingInterfacesVerified = False;
    {When an object is destroyed, should we fill the interface VMT with a special value which
    will allow tracking of calls to this interface after the object was destroyed}

    WipeOutMemoryOnFreeMem = True;
    {This is about what is done on memory freeing:
    - for objects, this option replaces the VMT with a special one which will raise exceptions if a virtual method is called
    - for other memory kinds, this will fill the memory space with the char below}
    CharToUseToWipeOut: char = #0;
    //I choose #0 because this makes objet fields Nil, which is easier to debug. Tell me if you have a better idea !

    CheckWipedBlocksOnTermination = True and WipeOutMemoryOnFreeMem and not (DanglingInterfacesVerified);
    {When iterating on the blocks (in OutputAllocatedBlocks), we check for every block which has been deallocated that it is still
    filled with CharToUseToWipeOut.
    Warning: this is VERY time-consuming
    This is meaningful only when the blocks are wiped out on free mem
    This is incompatible with dangling interfaces checking}
    DoNotCheckWipedBlocksBiggerThan = 4000;

    CollectStatsAboutObjectAllocation = False;
    {Every time FreeMem is called for allocationg an object, this will register information about the class instanciated:
    class name, number of instances, allocated space for one instance
    Note: this has to be done on FreeMem because when GetMem is called, the VMT is not installed yet and we can not know
    this is an object}

    KeepMaxMemoryUsage = CollectStatsAboutObjectAllocation;
    {Will report the biggest memory usage during the execution}

    ComputeMemoryUsageStats = False;
    {Outputs the memory usage along the life of the execution. This output can be easily graphed, in excel for example}
    MemoryUsageStatsStep = 5;
    {Meaningful only when ComputeMemoryUsageStats
    When this is set to 5, we collect information for the stats every 5 call to GetMem, unless size is bigger than StatCollectionForce}
    StatCollectionForce = 1000;

    BlocksToShow: array[TKindOfMemory] of Boolean = (true, true, true);
    {eg if BlocksToShow[MClass] is True, the blocks allocated for class instances will be shown}

    CheckHeapStatus = False;
    // Checks that the heap has not been corrupted since last call to the memory manager
    // Warning: VERY time-consuming

    IdentifyObjectFields = False;
    IdentifyFieldsOfObjectsConformantTo: TClass = Tobject;

    MaxLeak = 1000;
    {This option tells to MemCheck not to display more than a certain quantity of leaks, so that the finalization
    phase does not take too long}

    UseDebugInfos = True;
    //Should use the debug informations which are in the executable ?

  (**************** END OF MEMCHECK OPTIONS ********************)

var
    ShowCallStack: Boolean;
    {When we show an allocated block, should we show the call stack that went to the allocation ? Set to false
    before each block. The usual way to use this is calling Evaluate/Modify just after an EMemoryLeak was raised}

const
    MaxListSize = MaxInt div 16 - 1;

type
    PObjectsArray = ^TObjectsArray;
    TObjectsArray = array[0..MaxListSize] of TObject;

    PStringsArray = ^TStringsArray;
    TStringsArray = array[0..99999999] of string;
    {Used to simulate string lists}

    PIntegersArray = ^TIntegersArray;
    TIntegersArray = array[0..99999999] of integer;
    {Used to simulate lists of integer}

var
    TimeStamps: PStringsArray = nil;
    {Allows associating a string of information with a time stamp}
    TimeStampsCount: integer = 0;
    {Number of time stamps in the array}
    TimeStampsAllocated: integer = 0;
    {Number of positions available in the array}

const
    DeallocateInstancesConformingTo = False;
    InstancesConformingToForDeallocation: TClass = TObject;
    {used only when BlocksToShow[MClass] is True - eg If InstancesConformingTo = TList, only blocks allocated for instances
    of TList and its heirs will be shown}

    InstancesConformingToForReporting: TClass = TObject;
    {used only when BlocksToShow[MClass] is True - eg If InstancesConformingTo = TList, only blocks allocated for instances
    of TList and its heirs will be shown}

    MaxNbSupportedVMTEntries = 200;
    {Don't change this number, its a Hack! jm}

type
    PMemoryBlocHeader = ^TMemoryBlocHeader;
    TMemoryBlocHeader = record
        {
        This is the header we put in front of a memory block
        For each memory allocation, we allocate "size requested + header size + footer size" because we keep information inside the memory zone.
        Therefore, the address returned by GetMem is: [the address we get from OldMemoryManager.GetMem] + HeaderSize.

        . DestructionAdress: an identifier telling if the bloc is active or not (when FreeMem is called we do not really free the mem).
          Nil when the block has not been freed yet; otherwise, contains the address of the caller of the destruction. This will be useful
          for reporting errors such as "this memory has already been freed, at address XXX".
        . PreceedingBlock: link of the linked list of allocated blocs
        . NextBlock: link of the linked list of allocated blocs
        . KindOfBlock: is the data an object or unknown kind of data (such as a buffer)
        . VMT: the classtype of the object
        . CallerAddress: an array containing the call stack at allocation time
        . AllocatedSize: the size allocated for the user (size requested by the user)
        . MagicNumber: an integer we use to recognize a block which was allocated using our own allocator
        }
        DestructionAdress: Pointer;
        PreceedingBlock: Pointer;
        NextBlock: Pointer;
        KindOfBlock: TKindOfMemory;
        VMT: TClass;
        CallerAddress: TCallStack;
        AllocatedSize: integer;        //this is an integer because the parameter of GetMem is an integer
        LastTimeStamp: integer;        //-1 means no time stamp
        NotUsed: Cardinal;              //Because Size of the header must be a multiple 8
        MagicNumber: Cardinal;
    end;

    PMemoryBlockFooter = ^TMemoryBlockFooter;
    TMemoryBlockFooter = Cardinal;
    {This is the end-of-bloc marker we use to check that the user did not write beyond the allowed space}

    EMemoryLeak = class(Exception);
    EStackUnwinding = class(EMemoryLeak);
    EBadInstance = class(Exception);
    {This exception is raised when a virtual method is called on an object which has been freed}
    EFreedBlockDamaged = class(Exception);
    EInterfaceFreedInstance = class(Exception);
    {This exception is raised when a method is called on an interface whom object has been freed}

    VMTTable = array[0..MaxNbSupportedVMTEntries] of pointer;
    pVMTTable = ^VMTTable;
    TMyVMT = record
        A: array[0..19] of byte;
        B: VMTTable;
    end;

    ReleasedInstance = class
        procedure RaiseExcept;
        procedure InterfaceError; stdcall;
        procedure Error; virtual;
    end;

    TFieldInfo = class
        OwnerClass: TClass;
        FieldIndex: integer;

        constructor Create(const TheOwnerClass: TClass; const TheFieldIndex: integer);
    end;

const
    EndOfBlock: Cardinal = $FFFFFFFA;
    Magic: Cardinal = $FFFFFFFF;

var
    FreedInstance: PChar;
    BadObjectVMT: TMyVMT;
    BadInterfaceVMT: VMTTable;
    GIndex: Integer;

    LastBlock: PMemoryBlocHeader;

    MemCheckActive: boolean = False;
    {Is MemCheck currently running ?
    ie, is the current memory manager memcheck's ?}
    MemCheckInitialized: Boolean = False;
    {Has InitializeOnce been called ?
    This variable should ONLY be used by InitializeOnce and the finalization}

  {*** arrays for stats ***}
    AllocatedObjectsClasses: array of TClass;
    NbClasses: integer = 0;

    AllocatedInstances: PIntegersArray = nil; {instances counter}
    AllocStatsCount: integer = 0;
    StatsArraysAllocatedPos: integer = 0;
    {This is used to display some statistics about objects allocated. Each time an object is allocated, we look if its
    class name appears in this list. If it does, we increment the counter of class' instances for this class;
    if it does not appear, we had it with a counter set to one.}

    MemoryUsageStats: PIntegersArray = nil; {instances counter}
    MemoryUsageStatsCount: integer = 0;
    MemoryUsageStatsAllocatedPos: integer = 0;
    MemoryUsageStatsLoop: integer = -1;

    SevereExceptionsLogFile: Text;
    {This is the log file for exceptions}

    OutOfMemory: EOutOfMemory;
    // Because when we have to raise this, we do not want to have to instanciate it (as there is no memory available)

    HeapCorrupted: Exception;

    NotDestroyedFields: PIntegersArray = nil;
    NotDestroyedFieldsInfos: PObjectsArray = nil;
    NotDestroyedFieldsCount: integer = 0;
    NotDestroyedFieldsAllocatedSpace: integer = 0;

    LastHeapStatus: THeapStatus;

    MaxMemoryUsage: Integer = 0;
    // see KeepMaxMemoryUsage

    OldMemoryManager: TMemoryManager;
    //Set by the MemChk routine

type
    TIntegerBinaryTree = class
    protected
        fValue: Cardinal;
        fBigger: TIntegerBinaryTree;
        fSmaller: TIntegerBinaryTree;

        class function StoredValue(const Address: Cardinal): Cardinal;
        constructor _Create(const Address: Cardinal);
        function _Has(const Address: Cardinal): Boolean;
        procedure _Add(const Address: Cardinal);
        procedure _Remove(const Address: Cardinal);

    public
        function Has(const Address: Cardinal): Boolean;
        procedure Add(const Address: Cardinal);
        procedure Remove(const Address: Cardinal);

        property Value: Cardinal read fValue;
    end;

    PCardinal = ^Cardinal;

var
    CurrentlyAllocatedBlocksTree: TIntegerBinaryTree;

type
    TAddressToLine = class
    public
        Address: Cardinal;
        Line: Cardinal;

        constructor Create(const AAddress, ALine: Cardinal);
    end;

    PAddressesArray = ^TAddressesArray;
    TAddressesArray = array[0..MaxInt div 16 - 1] of TAddressToLine;

    TUnitDebugInfos = class
    public
        Name: string;
        Addresses: array of TAddressToLine;

        constructor Create(const AName: string; const NbLines: Cardinal);

        function LineWhichContainsAddress(const Address: Cardinal): string;
    end;

    TRoutineDebugInfos = class
    public
        Name: string;
        StartAddress: Cardinal;
        EndAddress: Cardinal;

        constructor Create(const AName: string; const AStartAddress: Cardinal; const ALength: Cardinal);
    end;

var
    Routines: array of TRoutineDebugInfos;
    RoutinesCount: integer;
    Units: array of TUnitDebugInfos;
    UnitsCount: integer;
    OutputFileHeader: string = 'MemCheck version 2.52'#13#10;

function BlockAllocationAddress(P: Pointer): Pointer;
var
    Block: PMemoryBlocHeader;
begin
    Block := PMemoryBlocHeader(PChar(P) - SizeOf(TMemoryBlocHeader));

    if Block.MagicNumber = Magic then
        Result := Block.CallerAddress[0]
    else
        Result := nil
end;

procedure UpdateLastHeapStatus;
begin
    LastHeapStatus := GetHeapStatus;
end;

function HeapStatusesDifferent(const Old, New: THeapStatus): boolean;
begin
    Result :=
        (Old.TotalAddrSpace <> New.TotalAddrSpace) or
        (Old.TotalUncommitted <> New.TotalUncommitted) or
        (Old.TotalCommitted <> New.TotalCommitted) or
        (Old.TotalAllocated <> New.TotalAllocated) or
        (Old.TotalFree <> New.TotalFree) or
        (Old.FreeSmall <> New.FreeSmall) or
        (Old.FreeBig <> New.FreeBig) or
        (Old.Unused <> New.Unused) or
        (Old.Overhead <> New.Overhead) or
        (Old.HeapErrorCode <> New.HeapErrorCode) or
        (New.TotalUncommitted + New.TotalCommitted <> New.TotalAddrSpace) or
        (New.Unused + New.FreeBig + New.FreeSmall <> New.TotalFree)
end;

class function TIntegerBinaryTree.StoredValue(const Address: Cardinal): Cardinal;
begin
    Result := Address shl 16;
    Result := Result or (Address shr 16);
    Result := Result xor $AAAAAAAA;
end;

constructor TIntegerBinaryTree._Create(const Address: Cardinal);
begin
    //Do not call inherited Create for optimization
    fValue := Address
end;

function TIntegerBinaryTree.Has(const Address: Cardinal): Boolean;
begin
    Result := _Has(StoredValue(Address));
end;

procedure TIntegerBinaryTree.Add(const Address: Cardinal);
begin
    _Add(StoredValue(Address));
end;

procedure TIntegerBinaryTree.Remove(const Address: Cardinal);
begin
    _Remove(StoredValue(Address));
end;

function TIntegerBinaryTree._Has(const Address: Cardinal): Boolean;
begin
    if fValue = Address then
        Result := True
    else
        if (Address > fValue) and (fBigger <> nil) then
            Result := fBigger._Has(Address)
        else
            if (Address < fValue) and (fSmaller <> nil) then
                Result := fSmaller._Has(Address)
            else
                Result := False
end;

procedure TIntegerBinaryTree._Add(const Address: Cardinal);
begin
    Assert(Address <> fValue, 'TIntegerBinaryTree._Add: already in !');

    if (Address > fValue) then
        begin
            if fBigger <> nil then
                fBigger._Add(Address)
            else
                fBigger := TIntegerBinaryTree._Create(Address)
        end
    else
        begin
            if fSmaller <> nil then
                fSmaller._Add(Address)
            else
                fSmaller := TIntegerBinaryTree._Create(Address)
        end
end;

procedure TIntegerBinaryTree._Remove(const Address: Cardinal);
var
    Owner, Node: TIntegerBinaryTree;
    NodeIsOwnersBigger: Boolean;
    Middle, MiddleOwner: TIntegerBinaryTree;
begin
    Owner := nil;
    Node := CurrentlyAllocatedBlocksTree;

    while (Node <> nil) and (Node.fValue <> Address) do
        begin
            Owner := Node;

            if Address > Node.Value then
                Node := Node.fBigger
            else
                Node := Node.fSmaller
        end;

    Assert(Node <> nil, 'TIntegerBinaryTree._Remove: not in');

    NodeIsOwnersBigger := Node = Owner.fBigger;

    if Node.fBigger = nil then
        begin
            if NodeIsOwnersBigger then
                Owner.fBigger := Node.fSmaller
            else
                Owner.fSmaller := Node.fSmaller;
        end
    else
        if Node.fSmaller = nil then
            begin
                if NodeIsOwnersBigger then
                    Owner.fBigger := Node.fBigger
                else
                    Owner.fSmaller := Node.fBigger;
            end
        else
            begin
                Middle := Node.fSmaller;
                MiddleOwner := Node;

                while Middle.fBigger <> nil do
                    begin
                        MiddleOwner := Middle;
                        Middle := Middle.fBigger;
                    end;

                if Middle = Node.fSmaller then
                    begin
                        if NodeIsOwnersBigger then
                            Owner.fBigger := Middle
                        else
                            Owner.fSmaller := Middle;

                        Middle.fBigger := Node.fBigger
                    end
                else
                    begin
                        MiddleOwner.fBigger := Middle.fSmaller;

                        Middle.fSmaller := Node.fSmaller;
                        Middle.fBigger := Node.fBigger;

                        if NodeIsOwnersBigger then
                            Owner.fBigger := Middle
                        else
                            Owner.fSmaller := Middle
                    end;
            end;

    Node.Destroy;
end;

function EBP8Caller: Pointer;
asm
            mov    eax,[EBP+8];
            sub    eax, 5
    end;

constructor TFieldInfo.Create(const TheOwnerClass: TClass; const TheFieldIndex: integer);
begin
    inherited Create;

    OwnerClass := TheOwnerClass;
    FieldIndex := TheFieldIndex;
end;

function EBP8_10: Pointer;
asm
            mov    eax,[EBP+8]
            sub    eax, 9
    end;

function DestroyCaller: Pointer;
asm
            mov    eax,[EBP]
    end;

const
    TObjectVirtualMethodNames: array[1..8] of string = ('SafeCallException', 'AfterConstruction', 'BeforeDestruction', 'Dispatch', 'DefaultHandler', 'NewInstance', 'FreeInstance', 'Destroy');

procedure ReleasedInstance.RaiseExcept;
var
    t: TMemoryBlocHeader;
    i: integer;
begin
    t := PMemoryBlocHeader((PChar(Self) - SizeOf(TMemoryBlocHeader)))^;

    try
        i := MaxNbSupportedVMTEntries - GIndex + 1;

        if i in [1..8] then
            raise EBadInstance.Create('Call ' + TObjectVirtualMethodNames[i] + ' on a FREED instance of ' + T.VMT.ClassName + ' (destroyed at ' + TextualDebugInfoForAddress(Cardinal(T.DestructionAdress)) + ')')at DestroyCaller
        else
            raise EBadInstance.Create('Call ' + IntToStr(i) + '° virtual method of ' + T.VMT.ClassName + ' on a FREED instance (TObject has 8 virtual methods)')at DestroyCaller;
    except
        on EBadInstance do ;
    end;

    if ShowCallStack then
        for i := 1 to StoredCallStackDepth do
            if Integer(T.CallerAddress[i]) > 0 then
                try
                    raise EStackUnwinding.Create('Unwinding level ' + chr(ord('0') + i))at T.CallerAddress[i]
                except
                    on EStackUnwinding do ;
                end;

    ShowCallStack := False;
end;

function InterfaceErrorCaller: Pointer;
{Returns EBP + 16, which is OK only for InterfaceError !
It would be nice to make this routine local to InterfaceError, but I do not know hot to
implement it in this case - VM}
asm
            mov    eax,[EBP+16];
            sub    eax, 5
    end;

procedure ReleasedInstance.InterfaceError;
begin
    try
        OutputFileHeader := OutputFileHeader + #13#10'Exception: Calling an interface method on an freed Pascal instance @ ' + TextualDebugInfoForAddress(Cardinal(InterfaceErrorCaller)) + #13#10;
        raise EInterfaceFreedInstance.Create('Calling an interface method on an freed Pascal instance')at InterfaceErrorCaller
    except
        on EInterfaceFreedInstance do
            ;
    end;
end;

procedure ReleasedInstance.Error;
{Don't change this, its a Hack! jm}
asm
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);Inc(GIndex);
        JMP ReleasedInstance.RaiseExcept;
    end;

function MemoryBlockDump(Block: PMemoryBlocHeader): string;
const
    MaxDump = 80;
var
    i,
        count: integer;
    s: string[MaxDump];
begin
    count := Block.AllocatedSize;

    if count > MaxDump then
        Count := MaxDump;

    Byte(s[0]) := count;
    move((PChar(Block) + SizeOf(TMemoryBlocHeader))^, s[1], Count);

    for i := 1 to Length(s) do
        if s[i] = #0 then s[i] := '.' else
            if s[i] < ' ' then
                s[i] := '?';

    Result := '  Dump: [' + s + ']';
end;

function EBP36_12: Pointer;
asm
            mov    eax,[EBP+36]
            sub    eax, 12
    end;

function EBP32_12: Pointer;
asm
            mov    eax,[EBP+32]
            sub    eax, 12
    end;

function EBP12Caller: Pointer;
asm
            mov    eax,[EBP+12];
            sub    eax, 5
    end;

function EBP52Caller: Pointer;
asm
            mov    eax,[EBP+52];
            sub    eax, 5
    end;

function EBP56Caller: Pointer;
asm
            mov    eax,[EBP+56];
            sub    eax, 5
    end;

function Caller: Pointer;
asm
            mov     eax,[ebp+4];
{
            mov     eax,[ebp];
            mov     eax,[eax+4];
            sub    eax,5
}
    end;

procedure FillCallStack(var St: TCallStack; const ExcludeFirstLevel: Boolean);
var
    i: integer;
    _EBP: Integer;
    _ESP: Integer;
begin
    asm
                mov  eax, ESP
                mov  _ESP,eax
                mov  eax, EBP
                mov  _EBP, eax
            end;
    if ExcludeFirstLevel then
        begin
            _ESP := _EBP;
            _EBP := PInteger(_EBP)^;
        end;
    FillChar(St, SizeOf(St), 0);
    if (_EBP < _ESP) or (_EBP - _ESP > 30000) then Exit;
    for i := 0 to StoredCallStackDepth do
        begin
            _ESP := _EBP;
            _EBP := PInteger(_EBP)^;
            if (_EBP < _ESP) or (_EBP - _ESP > 30000) then Exit;
            St[i] := Pointer(PInteger(_EBP + 4)^ - 4);
        end;
end;

procedure AddAllocatedObjectsClass(const C: TClass);
begin
    if NbClasses >= Length(AllocatedObjectsClasses) then
        begin
            UnMemChk;
            SetLength(AllocatedObjectsClasses, NbClasses * 2);
            MemChk;
        end;

    AllocatedObjectsClasses[NbClasses] := C;
    NbClasses := NbClasses + 1;
end;

procedure CollectNewInstanceOfClassForStats(const TheClass: TClass);
var
    i: integer;
begin
    i := 0;
    while (i < AllocStatsCount) and (AllocatedObjectsClasses[i] <> TheClass) do
        i := i + 1;

    if i = AllocStatsCount then
        begin
            if AllocStatsCount = StatsArraysAllocatedPos then
                begin
                    if StatsArraysAllocatedPos = 0 then
                        StatsArraysAllocatedPos := 10;
                    StatsArraysAllocatedPos := StatsArraysAllocatedPos * 2;
                    UnMemChk;
                    ReallocMem(AllocatedInstances, StatsArraysAllocatedPos * sizeof(Integer));
                    MemChk;
                end;

            AddAllocatedObjectsClass(TheClass);
            AllocatedInstances[AllocStatsCount] := 1;
            AllocStatsCount := AllocStatsCount + 1;
        end
    else
        AllocatedInstances[i] := AllocatedInstances[i] + 1;
end;

function EBP_8_13: Pointer;
asm
            mov    eax,[EBP+8]
            sub    eax, 13
    end;

function AddressOf_NewAnsiString: pointer;
asm
            mov eax, offset System.@NewAnsiString;
end;

function LeakTrackingGetMem(Size: Integer): Pointer;
begin
    if (EBP8_10 = PChar(@TObject.NewInstance)) {comming from TObject.NewInstance ?} then
        begin
            Result := OldMemoryManager.GetMem(Size + (SizeOf(TMemoryBlocHeader)));
            if Result = nil then
                raise OutOfMemory;
            PMemoryBlocHeader(Result).KindOfBlock := MClass;
            if EBP36_12 = PChar(@TObject.Create) then
                begin
                    if StoredCallStackDepth > 0 then
                        FillCallStack(PMemoryBlocHeader(Result).CallerAddress, True);
                    PMemoryBlocHeader(Result).CallerAddress[0] := EBP56Caller;
                end
            else if EBP32_12 = PChar(@TObject.Create) then
                begin
                    if StoredCallStackDepth > 0 then
                        FillCallStack(PMemoryBlocHeader(Result).CallerAddress, True);
                    PMemoryBlocHeader(Result).CallerAddress[0] := EBP52Caller;
                end
            else
                begin
                    if StoredCallStackDepth > 0 then
                        FillCallStack(PMemoryBlocHeader(Result).CallerAddress, True);
                    PMemoryBlocHeader(Result).CallerAddress[0] := Caller;
                end;
        end
    else
        if EBP_8_13 = AddressOf_NewAnsiString then
            {We do not log memory allocations for reference counted strings. This would take time and some leaks would be reported
            uselessly. However, if you want to know about this, you can just out-comment this part}
            begin
                Result := OldMemoryManager.GetMem(Size);
                if Result = nil then
                    raise OutOfMemory;
                Exit;
            end
        else
            begin                      {Neither an object nor a string, this is a MUser}
                Result := OldMemoryManager.GetMem(Size + (SizeOf(TMemoryBlocHeader) + SizeOf(TMemoryBlockFooter)));
                if Result = nil then
                    raise OutOfMemory;
                PMemoryBlocHeader(Result).KindOfBlock := MUser;
                if StoredCallStackDepth > 0 then
                    FillCallStack(PMemoryBlocHeader(Result).CallerAddress, True);
                PMemoryBlocHeader(Result).CallerAddress[0] := EBP8Caller;
                PMemoryBlockFooter(PChar(Result) + SizeOf(TMemoryBlocHeader) + Size)^ := EndOfBlock;
            end;

    PMemoryBlocHeader(Result).PreceedingBlock := LastBlock;
    PMemoryBlocHeader(Result).LastTimeStamp := TimeStampsCount - 1;
    PMemoryBlocHeader(Result).NextBlock := nil;
    if LastBlock <> nil then
        LastBlock.NextBlock := Result;
    LastBlock := Result;
    LastBlock.DestructionAdress := nil;
    LastBlock.AllocatedSize := Size;
    LastBlock.MagicNumber := Magic;

    if IdentifyObjectFields then
        begin
            UnMemChk;
            CurrentlyAllocatedBlocksTree.Add(integer(LastBlock));
            MemChk;
        end;

    Inc(integer(Result), SizeOf(TMemoryBlocHeader));

    if ComputeMemoryUsageStats then
        begin
            MemoryUsageStatsLoop := MemoryUsageStatsLoop + 1;
            if MemoryUsageStatsLoop = MemoryUsageStatsStep then
                MemoryUsageStatsLoop := 0;

            if (MemoryUsageStatsLoop = 0) or (Size > StatCollectionForce) then
                begin
                    if MemoryUsageStatsCount = MemoryUsageStatsAllocatedPos then
                        begin
                            if MemoryUsageStatsAllocatedPos = 0 then
                                MemoryUsageStatsAllocatedPos := 10;
                            MemoryUsageStatsAllocatedPos := MemoryUsageStatsAllocatedPos * 2;
                            UnMemChk;
                            ReallocMem(MemoryUsageStats, MemoryUsageStatsAllocatedPos * sizeof(Integer));
                            MemChk;
                        end;

                    MemoryUsageStats[MemoryUsageStatsCount] := AllocMemSize;
                    MemoryUsageStatsCount := MemoryUsageStatsCount + 1;
                end;
        end;

    if KeepMaxMemoryUsage and (AllocMemSize > MaxMemoryUsage) then
        MaxMemoryUsage := AllocMemSize;
end;

function HeapCheckingGetMem(Size: Integer): Pointer;
begin
    if HeapStatusesDifferent(LastHeapStatus, GetHeapStatus) then
        raise HeapCorrupted;

    Result := OldMemoryManager.GetMem(Size);

    UpdateLastHeapStatus;
end;

function MemoryBlockCorrupted(P: Pointer): Boolean;
var
    Block: PMemoryBlocHeader;
begin
    if PCardinal(PChar(P) - 4)^ = Magic then
        begin
            Block := PMemoryBlocHeader(PChar(P) - SizeOf(TMemoryBlocHeader));
            Result := (Block.DestructionAdress <> nil);
            Result := Result or (PMemoryBlockFooter(PChar(P) + Block.AllocatedSize)^ <> EndOfBlock)
        end
    else
        Result := False
end;

procedure ReplaceInterfacesWithBadInterface(AClass: TClass; Instance: Pointer);
{copied and modified from System.Pas: replaces all INTERFACES in Pascal Objects
with a reference to our dummy INTERFACE VMT}
asm
                PUSH    EBX
                PUSH    ESI
                PUSH    EDI
                MOV    EBX,EAX
                MOV    EAX,EDX
                MOV    EDX,ESP
        @@0:    MOV    ECX,[EBX].vmtIntfTable
                TEST    ECX,ECX
                JE      @@1
                PUSH    ECX
        @@1:    MOV    EBX,[EBX].vmtParent
                TEST    EBX,EBX
                JE      @@2
                MOV    EBX,[EBX]
                JMP    @@0
        @@2:    CMP    ESP,EDX
                JE      @@5
        @@3:    POP    EBX
                MOV    ECX,[EBX].TInterfaceTable.EntryCount
                ADD    EBX,4
        @@4:    LEA    ESI, BadInterfaceVMT // mettre dans ESI l'adresse du début de MyInterfaceVMT: correct ?????
                MOV    EDI,[EBX].TInterfaceEntry.IOffset
                MOV    [EAX+EDI],ESI
                ADD    EBX,TYPE TInterfaceEntry
                DEC    ECX
                JNE    @@4
                CMP    ESP,EDX
                JNE    @@3
        @@5:    POP    EDI
                POP    ESI
                POP    EBX
    end;

function FindMem(Base, ToFind: pointer; Nb: integer): integer;
// Base = instance, Nb = nombre de bloc (HORS VMT!)
asm
            // eax=base; edx=Tofind; ecx=Nb
            @loop:
            cmp [eax+ecx*4], edx
            je @found
            dec ecx
            jne  @loop

            @found:
            mov eax,ecx
    end;

procedure AddFieldInfo(const FieldAddress: Pointer; const OwnerClass: TClass; const FieldPos: integer);
begin
    UnMemChk;

    if NotDestroyedFieldsCount = NotDestroyedFieldsAllocatedSpace then
        begin
            if NotDestroyedFieldsAllocatedSpace = 0 then
                NotDestroyedFieldsAllocatedSpace := 10;
            NotDestroyedFieldsAllocatedSpace := NotDestroyedFieldsAllocatedSpace * 2;
            ReallocMem(NotDestroyedFields, NotDestroyedFieldsAllocatedSpace * sizeof(integer));
            ReallocMem(NotDestroyedFieldsInfos, NotDestroyedFieldsAllocatedSpace * sizeof(integer));
        end;

    NotDestroyedFields[NotDestroyedFieldsCount] := integer(FieldAddress);
    NotDestroyedFieldsInfos[NotDestroyedFieldsCount] := TFieldInfo.Create(OwnerClass, FieldPos);
    NotDestroyedFieldsCount := NotDestroyedFieldsCount + 1;

    MemChk;
end;

function LeakTrackingFreeMem(P: Pointer): Integer;
var
    Block: PMemoryBlocHeader;
    i: integer;
begin
    if PCardinal(PChar(P) - 4)^ = Magic then
        {we recognize a block we marked}
        begin
            Block := PMemoryBlocHeader(PChar(P) - SizeOf(TMemoryBlocHeader));

            if CollectStatsAboutObjectAllocation and (Block.KindOfBlock = MClass) then
                CollectNewInstanceOfClassForStats(TObject(P).ClassType);

            if IdentifyObjectFields then
                begin
                    if (Block.KindOfBlock = MClass) and (TObject(P).InheritsFrom(IdentifyFieldsOfObjectsConformantTo)) then
                        for i := 1 to (Block.AllocatedSize div 4) - 1 do
                            if (PInteger(PChar(P) + i * 4)^ > SizeOf(TMemoryBlocHeader)) and CurrentlyAllocatedBlocksTree.Has(PInteger(PChar(P) + i * 4)^ - SizeOf(TMemoryBlocHeader)) then
                                AddFieldInfo(Pointer(PInteger(PChar(P) + i * 4)^ - SizeOf(TMemoryBlocHeader)), TObject(P).ClassType, i);

                    UnMemChk;
                    if Block.DestructionAdress = nil then
                        begin
                            Assert(CurrentlyAllocatedBlocksTree.Has(integer(Block)), 'freemem: block not among allocated ones');
                            CurrentlyAllocatedBlocksTree.Remove(integer(Block));
                        end;
                    MemChk;
                end;

            if (Block.AllocatedSize > DeallocateFreedMemoryWhenBlockBiggerThan) or
                (DeallocateInstancesConformingTo and (Block.KindOfBlock = MClass) and (TObject(P) is InstancesConformingToForDeallocation)) then
                {we really deallocate the block}
                begin
                    if Block.NextBlock <> nil then
                        PMemoryBlocHeader(Block.NextBlock).PreceedingBlock := Block.PreceedingBlock;
                    if Block.PreceedingBlock <> nil then
                        PMemoryBlocHeader(Block.PreceedingBlock).NextBlock := Block.NextBlock;
                    if LastBlock = Block then
                        LastBlock := Block.PreceedingBlock;

                    OldMemoryManager.FreeMem(Block);
                end
            else
                if Block.DestructionAdress <> nil then
                    begin
                        try
                            OutputFileHeader := OutputFileHeader + #13#10'Exception: second release of block attempt, allocated at ' + TextualDebugInfoForAddress(Cardinal(Block.CallerAddress[0])) + #13#10;
                            raise EMemoryLeak.Create('second release of block attempt, allocated')at Block.CallerAddress[0];
                        except
                            on EMemoryLeak do ;
                        end;

                        if ShowCallStack then
                            for i := 1 to StoredCallStackDepth do
                                if Integer(Block.CallerAddress[i]) > 0 then
                                    try
                                        raise EStackUnwinding.Create('Unwinding level ' + chr(ord('0') + i))at Block.CallerAddress[i]
                                    except
                                        on EStackUnwinding do ;
                                    end;

                        ShowCallStack := False;
                    end
                else
                    begin
                        if (Block.KindOfBlock <> MClass) and MemoryBlockCorrupted(P) then
                            begin
                                try
                                    OutputFileHeader := OutputFileHeader + #13#10'Exception: memory damaged beyond block allocated space, allocated at ' + TextualDebugInfoForAddress(Cardinal(BlockAllocationAddress(P))) + #13#10;
                                    raise EMemoryLeak.Create('memory damaged beyond block allocated space, allocated at ' + TextualDebugInfoForAddress(Cardinal(BlockAllocationAddress(P))));
                                except
                                    on EMemoryLeak do ;
                                end;
                            end;

                        Block.DestructionAdress := Caller;

                        FillCallStack(Block.CallerAddress, True);

                        if WipeOutMemoryOnFreeMem then
                            if Block.KindOfBlock = MClass then
                                begin
                                    Block.VMT := TObject(P).ClassType;
                                    FillChar((PChar(P) + 4)^, Block.AllocatedSize - 4, CharToUseToWipeOut);
                                    PInteger(P)^ := Integer(FreedInstance);
                                    if DanglingInterfacesVerified then
                                        ReplaceInterfacesWithBadInterface(Block.VMT, TObject(P))
                                end
                            else
                                FillChar(P^, Block.AllocatedSize, CharToUseToWipeOut);
                    end;

            Result := 0;
        end
    else
        Result := OldMemoryManager.FreeMem(P);
end;

function HeapCheckingFreeMem(P: Pointer): Integer;
begin
    if HeapStatusesDifferent(LastHeapStatus, GetHeapStatus) then
        raise HeapCorrupted;

    Result := OldMemoryManager.FreeMem(P);

    UpdateLastHeapStatus;
end;

function LeakTrackingReallocMem(P: Pointer; Size: Integer): Pointer;
begin
    if PCardinal(PChar(P) - 4)^ = Magic then
        begin
            GetMem(Result, Size);
            if StoredCallStackDepth > 0 then
                FillCallStack(LastBlock.CallerAddress, True);
            LastBlock.CallerAddress[0] := EBP12Caller; {12 because _ReallocMem push one more argument}
            LastBlock.KindOfBlock := MReallocedUser;

            if Size > PMemoryBlocHeader(PChar(P) - SizeOf(TMemoryBlocHeader)).AllocatedSize then
                Move(P^, Result^, PMemoryBlocHeader(PChar(P) - SizeOf(TMemoryBlocHeader)).AllocatedSize)
            else
                Move(P^, Result^, Size);

            LeakTrackingFreeMem(P);
        end
    else
        Result := OldMemoryManager.ReallocMem(P, Size);
end;

function HeapCheckingReallocMem(P: Pointer; Size: Integer): Pointer;
begin
    if HeapStatusesDifferent(LastHeapStatus, GetHeapStatus) then
        raise HeapCorrupted;

    Result := OldMemoryManager.ReallocMem(P, Size);

    UpdateLastHeapStatus;
end;

procedure UnMemChk;
begin
    SetMemoryManager(OldMemoryManager);
    MemCheckActive := False;
end;

function IsMemFilledWithChar(P: Pointer; N: Integer; C: Char): boolean;
{is the memory at P made of C on N bytes ?}
asm
        {    ->EAX    Pointer to memory }
        {      EDX    count  }
        {      CL      value  }
        @loop:
        cmp  [eax+edx-1],cl
        jne  @diff
        dec  edx
        jne  @loop
        mov  eax,1
        ret
        @diff:
        xor  eax,eax
    end;

procedure GoThroughAllocatedBlocks;
{traverses the allocated blocks list and for each one, raises exceptions showing the memory leaks}
var
    Block: PMemoryBlocHeader;
    i: integer;
    S: ShortString;
begin
    UnMemChk;

    {defaults for blocks displaying}
    Block := LastBlock;

    ShowCallStack := False;            {for first block}

    while Block <> nil do
        begin
            if BlocksToShow[Block.KindOfBlock] then
                begin
                    if Block.DestructionAdress = nil then
                        {this is a leak}
                        begin
                            case Block.KindOfBlock of
                                MClass:
                                    S := TObject(PChar(Block) + SizeOf(TMemoryBlocHeader)).ClassName;
                                MUser:
                                    S := 'User';
                                MReallocedUser:
                                    S := 'Realloc';
                            end;

                            if (BlocksToShow[Block.KindOfBlock]) and ((Block.KindOfBlock <> MClass) or (TObject(PChar(Block) + SizeOf(TMemoryBlocHeader)) is InstancesConformingToForReporting)) then
                                try
                                    raise EMemoryLeak.Create(S + ' allocated at ' + TextualDebugInfoForAddress(Cardinal(Block.CallerAddress[0])))at Block.CallerAddress[0];
                                except
                                    on EMemoryLeak do ;
                                end;

                            if ShowCallStack then
                                for i := 1 to StoredCallStackDepth do
                                    if Integer(Block.CallerAddress[i]) > 0 then
                                        try
                                            raise EStackUnwinding.Create(S + ' unwinding level ' + chr(ord('0') + i))at Block.CallerAddress[i]
                                        except
                                            on EStackUnwinding do ;
                                        end;

                            ShowCallStack := False;
                        end            {Block.DestructionAdress = Nil}
                    else
                        {this is not a leak}
                        if CheckWipedBlocksOnTermination and (Block.AllocatedSize > 5) and (Block.AllocatedSize <= DoNotCheckWipedBlocksBiggerThan) and (not IsMemFilledWithChar(pchar(Block) + SizeOf(TMemoryBlocHeader) + 4, Block.AllocatedSize - 5, CharToUseToWipeOut)) then
                            begin
                                try
                                    raise EFreedBlockDamaged.Create('Destroyed block damaged - Block allocated at ' + TextualDebugInfoForAddress(Cardinal(Block.CallerAddress[0])) + ' - destroyed at ' + TextualDebugInfoForAddress(Cardinal(Block.DestructionAdress)))at Block.CallerAddress[0]
                                except
                                    on EFreedBlockDamaged do ;
                                end;
                            end;
                end;

            Block := Block.PreceedingBlock;
        end;
end;

procedure dummy; forward;

(*** The hack below arranges for MemCheck's finalization to be called after all others ***)
type
    JmpInstruction =
        packed record
        opCode: Byte;
        distance: Longint;
    end;

    TExcDescEntry =
        record
        vTable: Pointer;
        handler: Pointer;
    end;
    PExcDesc = ^TExcDesc;
    TExcDesc =
        packed record
        jmp: JmpInstruction;
        case Integer of
            0: (instructions: array[0..0] of Byte);
            1 {...}: (cnt: Integer; excTab: array[0..0 {cnt-1}] of TExcDescEntry);
    end;

    PExcFrame = ^TExcFrame;
    TExcFrame =
        record
        next: PExcFrame;
        desc: PExcDesc;
        hEBP: Pointer;
        case Integer of
            0: ();
            1: (ConstructedObject: Pointer);
            2: (SelfOfMethod: Pointer);
    end;

    PInitContext = ^TInitContext;
    TInitContext = record
        OuterContext: PInitContext;    { saved InitContext  }
        ExcFrame: PExcFrame;            { bottom exc handler  }
        InitTable: PackageInfo;        { unit init info      }
        InitCount: Integer;            { how far we got      }
        Module: PLibModule;            { ptr to module desc  }
        DLLSaveEBP: Pointer;            { saved regs for DLLs }
        DLLSaveEBX: Pointer;            { saved regs for DLLs }
        DLLSaveESI: Pointer;            { saved regs for DLLs }
        DLLSaveEDI: Pointer;            { saved regs for DLLs }
        DLLInitState: Byte;
        ExitProcessTLS: procedure;      { Shutdown for TLS    }
    end;

procedure ChangeFinalizationsOrder;
{we are going to change the order in which Finalizations will occur
- SysInit is always finalized last (#0 in the list), and we can not change that (we can not recompile System, which has a "uses SysInit")
- System is always finalized just before (#1), and we can not change that
- SysUtils has to be finalized after MemCheck (#2), because SysUtils has "DoneExceptions" in its finalization, and this prevents GoThroughAllocatedBlocks from working OK
- MemCheck will be next (#3) - You could say MemCheck has uses FileUtil & Windows, but they have neither finalization nor initialization
}
const
    NewIndexOfSysutilsFinalization = 2;
    NewIndexOfMemcheckFinalization = NewIndexOfSysutilsFinalization + 1;
var
    InitContext: PInitContext;
    Table: PUnitEntryTable;
    i: integer;
    MemCheckFinalizationIndex, SysUtilsFinalizationIndex: integer;
    BytesRead: DWord;
    h: THandle;
    MemCheckUnitEntry, SysUtilsUnitEntry: Pointer;
    CurrentEntry: pointer;
    DistToMemCheckFinalization: int64;
begin
    InitContext := PInitContext(PChar(@AllocMemSize) + 31 * 4);
    Table := InitContext.InitTable^.UnitInfo;

    {seek memcheck's finalization: find the nearest finalization from Dummy}
    DistToMemCheckFinalization := MaxInt;
    MemCheckFinalizationIndex := -1;
    for i := InitContext.InitCount - 1 downto 0 do
        if (Cardinal(@Table^[i].Finit) > Cardinal(@dummy)) and (Cardinal(@Table^[i].Finit) - Cardinal(@dummy) < DistToMemCheckFinalization) then
            begin
                MemCheckFinalizationIndex := i;
                DistToMemCheckFinalization := abs(integer(@Table^[i].Finit) - integer(@dummy));
            end;

    {Seek Sysutils' finalization: find it exactly thanks to unitname.unitname notation !}
    SysUtilsFinalizationIndex := -1;
    i := 0;
    while (i < InitContext.InitCount - 1) and (SysUtilsFinalizationIndex = -1) do
        if @Table^[i].Init = @Sysutils.SysUtils then
            SysUtilsFinalizationIndex := i
        else
            i := i + 1;

    Assert(SysUtilsFinalizationIndex <> -1, 'MemCheck: Finalization of unit SysUtils not found');
    Assert(MemCheckFinalizationIndex <> -1, 'MemCheck: Finalization not found');

    GetMem(CurrentEntry, 8);

    h := getcurrentprocessid;
    h := openprocess(PROCESS_ALL_ACCESS, true, h);

    {bring SysUtils to its new pos}
    GetMem(SysUtilsUnitEntry, 8);
    ReadProcessMemory(h, pointer(pchar(Table) + SysUtilsFinalizationIndex * sizeof(PackageUnitEntry)), SysUtilsUnitEntry, 8, BytesRead);
    {for an obscure reason, we are not able to write into "table" directly - Using WriteProcessMemory works}
    for i := SysUtilsFinalizationIndex - 1 downto NewIndexOfSysUtilsFinalization do
        begin
            ReadProcessMemory(h, pointer(pchar(Table) + i * sizeof(PackageUnitEntry)), CurrentEntry, 8, BytesRead);
            WriteProcessMemory(h, pointer(pchar(Table) + (i + 1) * sizeof(PackageUnitEntry)), CurrentEntry, 8, BytesRead);
        end;
    WriteProcessMemory(h, pointer(pchar(Table) + NewIndexOfSysUtilsFinalization * sizeof(PackageUnitEntry)), SysUtilsUnitEntry, 8, BytesRead);
    FreeMem(SysUtilsUnitEntry);

    {bring MemCheck to its new pos}
    GetMem(MemCheckUnitEntry, 8);
    ReadProcessMemory(h, pointer(pchar(Table) + MemCheckFinalizationIndex * sizeof(PackageUnitEntry)), MemCheckUnitEntry, 8, BytesRead);
    for i := MemCheckFinalizationIndex - 1 downto NewIndexOfMemcheckFinalization do
        begin
            ReadProcessMemory(h, pointer(pchar(Table) + i * sizeof(PackageUnitEntry)), CurrentEntry, 8, BytesRead);
            WriteProcessMemory(h, pointer(pchar(Table) + (i + 1) * sizeof(PackageUnitEntry)), CurrentEntry, 8, BytesRead);
        end;
    WriteProcessMemory(h, pointer(pchar(Table) + NewIndexOfMemcheckFinalization * sizeof(PackageUnitEntry)), MemCheckUnitEntry, 8, BytesRead);
    FreeMem(MemCheckUnitEntry);
end;

function UnitWhichContainsAddress(const Address: Cardinal): TUnitDebugInfos;
var
    Start, Finish, Pivot: integer;
begin
    Start := 0;
    Finish := UnitsCount - 1;
    Result := nil;

    while Start <= Finish do
        begin
            Pivot := Start + (Finish - Start) div 2;

            if TUnitDebugInfos(Units[Pivot]).Addresses[0].Address > Address then
                Finish := Pivot - 1
            else
                if TUnitDebugInfos(Units[Pivot]).Addresses[Length(TUnitDebugInfos(Units[Pivot]).Addresses) - 1].Address < Address then
                    Start := Pivot + 1
                else
                    begin
                        Result := Units[Pivot];
                        Start := Finish + 1;
                    end;
        end;
end;

function RoutineWhichContainsAddress(const Address: Cardinal): string;
var
    Start, Finish, Pivot: integer;
begin
    Start := 0;
    Result := '(no debug info)';
    Finish := RoutinesCount - 1;

    while Start <= Finish do
        begin
            Pivot := Start + (Finish - Start) div 2;

            if TRoutineDebugInfos(Routines[Pivot]).StartAddress > Address then
                Finish := Pivot - 1
            else
                if TRoutineDebugInfos(Routines[Pivot]).EndAddress < Address then
                    Start := Pivot + 1
                else
                    begin
                        Result := ' Routine ' + TRoutineDebugInfos(Routines[Pivot]).Name;
                        Start := Finish + 1;
                    end;
        end;
end;

procedure LogCallStack(var F: Text);
var
    i: integer;
    _EBP: Integer;
    _ESP: Integer;
begin
    asm
                mov  eax, ESP
                mov  _ESP,eax
                mov  eax, EBP
                mov  _EBP, eax
            end;

    _ESP := _EBP;
    _EBP := PInteger(_EBP)^;

    if (_EBP < _ESP) or (_EBP - _ESP > 30000) then
        Exit;

    i := 0;

    while i < 25 do
        begin
            _ESP := _EBP;
            _EBP := PInteger(_EBP)^;

            if (_EBP < _ESP) or (_EBP - _ESP > 30000) or (PCardinal(_EBP + 4)^ < 4) then
                Exit;

            Writeln(F, #9 + TextualDebugInfoForAddress(PCardinal(_EBP + 4)^ - 4));

            i := i + 1;
        end;
end;

type
    TExceptionProc = procedure(Exc: TObject; Addr: Pointer);

var
    InitialExceptionProc: TExceptionProc;
    VersionInfo: string;

procedure MyExceptProc(Exc: TObject; Addr: Pointer);
begin
    Writeln(SevereExceptionsLogFile, '');
    Writeln(SevereExceptionsLogFile, '********* Severe exception detected - ' + DateTimeToStr(Now) + ' *********');
    Writeln(SevereExceptionsLogFile, VersionInfo);
    Writeln(SevereExceptionsLogFile, 'Exception code: ' + Exc.ClassName);
    Writeln(SevereExceptionsLogFile, 'Exception address: ' + TextualDebugInfoForAddress(Cardinal(Addr)));
    Writeln(SevereExceptionsLogFile, #13#10'Call stack (oldest call at bottom):');
    LogCallStack(SevereExceptionsLogFile);
    Writeln(SevereExceptionsLogFile, '*****************************************************************');
    Writeln(SevereExceptionsLogFile, '');

    InitialExceptionProc(Exc, Addr);

    {The closing of the file is done in the finalization}
end;

procedure LogSevereExceptions(const WithVersionInfo: string);
const
    FileNameBufSize = 1000;
var
    LogFileName: string;
begin
    if ExceptProc <> @MyExceptProc then
        {not installed yet ?}
        begin
            try
                SetLength(LogFileName, FileNameBufSize);
                GetModuleFileName(0, PChar(LogFileName), FileNameBufSize);
                LogFileName := copy(LogFileName, 1, pos('.', LogFileName)) + 'log';

                AssignFile(SevereExceptionsLogFile, LogFileName);

                if FileExists(LogFileName) then
                    Append(SevereExceptionsLogFile)
                else
                    Rewrite(SevereExceptionsLogFile);
            except
                Exit;
            end;

            InitialExceptionProc := ExceptProc;
            ExceptProc := @MyExceptProc;
            VersionInfo := WithVersionInfo;
        end;
end;

function IsMemCheckActive: boolean;
begin
    Result := MemCheckActive
end;

constructor TUnitDebugInfos.Create(const AName: string; const NbLines: Cardinal);
begin
    Name := AName;

    SetLength(Addresses, NbLines);
end;

constructor TRoutineDebugInfos.Create(const AName: string; const AStartAddress: Cardinal; const ALength: Cardinal);
begin
    Name := AName;
    StartAddress := AStartAddress;
    EndAddress := StartAddress + ALength - 1;
end;

constructor TAddressToLine.Create(const AAddress, ALine: Cardinal);
begin
    Address := AAddress;
    Line := ALine
end;

function TUnitDebugInfos.LineWhichContainsAddress(const Address: Cardinal): string;
var
    Start, Finish, Pivot: Cardinal;
begin
    if Addresses[0].Address > Address then
        Result := ''
    else
        begin
            Start := 0;
            Finish := Length(Addresses) - 1;

            while Start < Finish - 1 do
                begin
                    Pivot := Start + (Finish - Start) div 2;

                    if Addresses[Pivot].Address = Address then
                        begin
                            Start := Pivot;
                            Finish := Start
                        end
                    else
                        if Addresses[Pivot].Address > Address then
                            Finish := Pivot
                        else
                            Start := Pivot
                end;

            Result := ' Line ' + IntToStr(Addresses[Start].Line);
        end;
end;

type
    SRCMODHDR = packed record
        _cFile: Word;
        _cSeg: Word;
        _baseSrcFile: array[0..MaxListSize] of Integer;
    end;

    SRCFILE = packed record
        _cSeg: Word;
        _nName: Integer;
        _baseSrcLn: array[0..MaxListSize] of Integer;
    end;

    SRCLN = packed record
        _Seg: Word;
        _cPair: Word;
        _Offset: array[0..MaxListSize] of Integer;
    end;

    PSRCMODHDR = ^SRCMODHDR;
    PSRCFILE = ^SRCFILE;
    PSRCLN = ^SRCLN;

    TArrayOfByte = array[0..MaxListSize] of Byte;
    TArrayOfWord = array[0..MaxListSize] of Word;
    PArrayOfByte = ^TArrayOfByte;
    PArrayOfWord = ^TArrayOfWord;
    PArrayOfPointer = ^TArrayOfPointer;
    TArrayOfPointer = array[0..MaxListSize] of PArrayOfByte;

procedure AddRoutine(const Name: string; const Start, Len: Cardinal);
begin
    if Length(Routines) <= RoutinesCount then
        SetLength(Routines, Max(RoutinesCount * 2, 1000));

    Routines[RoutinesCount] := TRoutineDebugInfos.Create(Name, Start, Len);
    RoutinesCount := RoutinesCount + 1;
end;

procedure AddUnit(const U: TUnitDebugInfos);
begin
    if Length(Units) <= UnitsCount then
        SetLength(Units, Max(UnitsCount * 2, 1000));

    Units[UnitsCount] := U;
    UnitsCount := UnitsCount + 1;
end;

procedure dumpsymbols(NameTbl: PArrayOfPointer; sstptr: PArrayOfByte; size: integer);
//Copyright (C) Tenth Planet Software Intl., Clive Turvey 1998. All rights reserved. - Reused & modified by SG with permission
var
    len, sym: integer;
begin
    while size > 0 do
        begin
            len := PWord(@sstptr^[0])^;
            sym := PWord(@sstptr^[2])^;

            INC(len, 2);

            if ((sym = $205) or (sym = $204)) and (PInteger(@sstptr^[40])^ > 0) then
                AddRoutine(PChar(NameTbl^[PInteger(@sstptr^[40])^ - 1]), PInteger(@sstptr^[28])^, PInteger(@sstptr^[16])^);

            if (len = 2) then
                size := 0
            else
                begin
                    sstptr := PArrayOfByte(@sstptr^[len]);
                    DEC(size, len);
                end;
        end;
end;

procedure dumplines(NameTbl: PArrayOfPointer; sstptr: PArrayOfByte; size: word);
//Copyright (C) Tenth Planet Software Intl., Clive Turvey 1998. All rights reserved. - Reused & modified by SG with permission
var
    srcmodhdr: PSRCMODHDR;
    i: Word;
    srcfile: PSRCFILE;
    srcln: PSRCLN;
    k: Word;
    CurrentUnit: TUnitDebugInfos;
begin
    if size > 0 then
        begin
            srcmodhdr := PSRCMODHDR(sstptr);

            for i := 0 to pred(srcmodhdr^._cFile) do
                begin
                    srcfile := PSRCFILE(@sstptr^[srcmodhdr^._baseSrcFile[i]]);

                    if srcfile^._nName > 0 then
                        //note: I assume that the code is always in segment #1. If this is not the case, Houston !  - VM
                        begin
                            srcln := PSRCLN(@sstptr^[srcfile^._baseSrcLn[0]]);

                            CurrentUnit := TUnitDebugInfos.Create(ExtractFileName(PChar(NameTbl^[srcfile^._nName - 1])), srcln^._cPair);
                            AddUnit(CurrentUnit);

                            for k := 0 to pred(srcln^._cPair) do
             
Avatar billede morten_s Nybegynder
04. juni 2002 - 08:52 #5
Mærkværdigvis får man fejl fra memcheck på en lang række af de komponenter som er med i D6 når man sætter "ikke visuelle komponenter" på sin form

Bruger du memcheck på dine applikationer ????
Avatar billede pellelil Nybegynder
04. juni 2002 - 09:13 #6
morten_s> jeg har faktisk ikke selv prøvet MemCheck, men fik den anbefalet (for en lille uges tid siden) fra en anden Delphi "Kode-karl" , som havde brugt den meget og var positiv over dens funktionallitet. Men jeg vil da lige prøve den med noget af min egen source, for at se hvorvid den fejl rapportere ikke eksisterende leaks.

Du må endelig ikke begå den fejl at du tror "3. parts" source-kode er fejlfri (herunder også med hensyn til MemoryLeaks), blot fordi der er tale om "købe-software". Selvfølgelig er (Prof.) virksomheder/udviklere som regel mere påpasselig med at få undersøgt deres source inden denne frigives, men det er set før at selv Borland har haft Memory-leaks i Delphi-sourcen.
Avatar billede martinlind Nybegynder
04. juni 2002 - 09:14 #7
Hvis du har nogle problemmer med INDY's comp. kan jeg anbefale at skrive til deres support, det har jeg gjort med held

/Martin
Avatar billede borrisholt Novice
04. juni 2002 - 09:17 #8
pelle>> Du har principielt ret. Dog glemmer du at fortælle at både du og jeg er fejlfri ! Og altid laver perfekt programmering :-)

Jens B
Avatar billede pellelil Nybegynder
04. juni 2002 - 09:27 #9
LOL - jeg ved sgu ikke rigtigt Jens: Umiddelbart ville jeg helst give dig ret men jeg har lige prøvet MemCheck på noget af mit eget source og indtil videre er konklutionen af enten virker MemCheck ikke eller også så er jeg ikke ufejlbar.

Spøg til side, så ser resultatet af MemCheck meget fornuftigt ud så jeg må hellere komme i gang med at kigge efter mine "leaks".
Avatar billede morten_s Nybegynder
04. juni 2002 - 11:13 #10
Pelle>> Prøv en helt ny form, sæt memcheck til i dpr filen og klask f.eks. en TActionList på ??? det giver fejl hos mig
Avatar billede pellelil Nybegynder
04. juni 2002 - 11:33 #11
Ingen fejl hos mig. Hvilken version af Delphi bruger du (hvilken SP) ?
Avatar billede pellelil Nybegynder
04. juni 2002 - 11:37 #12
Et alternativ til MemCheck kunne være: http://www.automatedqa.com/products/memproof.asp
Avatar billede morten_s Nybegynder
04. juni 2002 - 11:40 #13
Jeg bruger D6 SP2
Avatar billede pellelil Nybegynder
04. juni 2002 - 11:45 #14
Jeg bruger selv D6(Ent) SP2, men har fra Borlands hjemmeside efterfølgende hentet de opdateringer der er lavet efter SP2 (opdatering af: Variants, TMultiReadExclusive... og ActionBands). Disse opdateringer kan hentes fra Borlands Community-sider.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester