7731 lines
280 KiB
ObjectPascal
7731 lines
280 KiB
ObjectPascal
|
(*
|
|||
|
|
|||
|
Fast Memory Manager 4.64
|
|||
|
|
|||
|
Description:
|
|||
|
A fast replacement memory manager for Borland Delphi Win32 applications that
|
|||
|
scales well under multi-threaded usage, is not prone to memory fragmentation,
|
|||
|
and supports shared memory without the use of external .DLL files.
|
|||
|
|
|||
|
Homepage:
|
|||
|
http://fastmm.sourceforge.net
|
|||
|
|
|||
|
Advantages:
|
|||
|
- Fast
|
|||
|
- Low overhead. FastMM is designed for an average of 5% and maximum of 10%
|
|||
|
overhead per block.
|
|||
|
- Supports up to 3GB of user mode address space under Windows 32-bit and 4GB
|
|||
|
under Windows 64-bit. Add the "$SetPEFlags $20" option (in curly braces)
|
|||
|
to your .dpr to enable this.
|
|||
|
- Highly aligned memory blocks. Can be configured for either 8-byte or 16-byte
|
|||
|
alignment.
|
|||
|
- Good scaling under multi-threaded applications
|
|||
|
- Intelligent reallocations. Avoids slow memory move operations through
|
|||
|
not performing unneccesary downsizes and by having a minimum percentage
|
|||
|
block size growth factor when an in-place block upsize is not possible.
|
|||
|
- Resistant to address space fragmentation
|
|||
|
- No external DLL required when sharing memory between the application and
|
|||
|
external libraries (provided both use this memory manager)
|
|||
|
- Optionally reports memory leaks on program shutdown. (This check can be set
|
|||
|
to be performed only if Delphi is currently running on the machine, so end
|
|||
|
users won't be bothered by the error message.)
|
|||
|
- Supports Delphi 4 (or later), C++ Builder 5 (or later), Kylix 3.
|
|||
|
|
|||
|
Usage:
|
|||
|
Delphi:
|
|||
|
Place this unit as the very first unit under the "uses" section in your
|
|||
|
project's .dpr file. When sharing memory between an application and a DLL
|
|||
|
(e.g. when passing a long string or dynamic array to a DLL function), both the
|
|||
|
main application and the DLL must be compiled using this memory manager (with
|
|||
|
the required conditional defines set). There are some conditional defines
|
|||
|
(inside FastMM4Options.inc) that may be used to tweak the memory manager. To
|
|||
|
enable support for a user mode address space greater than 2GB you will have to
|
|||
|
use the EditBin* tool to set the LARGE_ADDRESS_AWARE flag in the EXE header.
|
|||
|
This informs Windows x64 or Windows 32-bit (with the /3GB option set) that the
|
|||
|
application supports an address space larger than 2GB (up to 4GB). In Delphi 7
|
|||
|
and later you can also specify this flag through the compiler directive
|
|||
|
{$SetPEFlags $20}
|
|||
|
*The EditBin tool ships with the MS Visual C compiler.
|
|||
|
C++ Builder 6:
|
|||
|
Refer to the instructions inside FastMM4BCB.cpp.
|
|||
|
|
|||
|
License:
|
|||
|
This work is copyright Professional Software Development / Pierre le Riche. It
|
|||
|
is released under a dual license, and you may choose to use it under either the
|
|||
|
Mozilla Public License 1.1 (MPL 1.1, available from
|
|||
|
http://www.mozilla.org/MPL/MPL-1.1.html) or the GNU Lesser General Public
|
|||
|
License 2.1 (LGPL 2.1, available from
|
|||
|
http://www.opensource.org/licenses/lgpl-license.php). If you find FastMM useful
|
|||
|
or you would like to support further development, a donation would be much
|
|||
|
appreciated. My banking details are:
|
|||
|
Country: South Africa
|
|||
|
Bank: ABSA Bank Ltd
|
|||
|
Branch: Somerset West
|
|||
|
Branch Code: 334-712
|
|||
|
Account Name: PSD (Distribution)
|
|||
|
Account No.: 4041827693
|
|||
|
Swift Code: ABSAZAJJ
|
|||
|
My PayPal account is:
|
|||
|
bof@psd.co.za
|
|||
|
|
|||
|
Contact Details:
|
|||
|
My contact details are shown below if you would like to get in touch with me.
|
|||
|
If you use this memory manager I would like to hear from you: please e-mail me
|
|||
|
your comments - good and bad.
|
|||
|
Snailmail:
|
|||
|
PO Box 2514
|
|||
|
Somerset West
|
|||
|
7129
|
|||
|
South Africa
|
|||
|
E-mail:
|
|||
|
plr@psd.co.za
|
|||
|
|
|||
|
Support:
|
|||
|
If you have trouble using FastMM, you are welcome to drop me an e-mail at the
|
|||
|
address above, or you may post your questions in the BASM newsgroup on the
|
|||
|
Borland news server (which is where I hang out quite frequently).
|
|||
|
|
|||
|
Disclaimer:
|
|||
|
FastMM has been tested extensively with both single and multithreaded
|
|||
|
applications on various hardware platforms, but unfortunately I am not in a
|
|||
|
position to make any guarantees. Use it at your own risk.
|
|||
|
|
|||
|
Acknowledgements (for version 4):
|
|||
|
- Eric Grange for his RecyclerMM on which the earlier versions of FastMM were
|
|||
|
based. RecyclerMM was what inspired me to try and write my own memory
|
|||
|
manager back in early 2004.
|
|||
|
- Dennis Christensen for his tireless efforts with the Fastcode project:
|
|||
|
helping to develop, optimize and debug the growing Fastcode library.
|
|||
|
- Pierre Y. for his suggestions regarding the extension of the memory leak
|
|||
|
checking options.
|
|||
|
- Anders Isaksson and Greg for finding and identifying the "DelphiIsRunning"
|
|||
|
bug under Delphi 5.
|
|||
|
- Francois Malan for finding the bug that caused compilation to fail when
|
|||
|
both the "AssumeMultiThreaded" and "CheckHeapForCorruption" options were set.
|
|||
|
- Craig Peterson for helping me identify the cache associativity issues that
|
|||
|
could arise due to medium blocks always being an exact multiple of 256 bytes.
|
|||
|
Also for various other bug reports and enhancement suggestions.
|
|||
|
- Jarek Karciarz, Vladimir Ulchenko (Vavan) and Bob Gonder for their help in
|
|||
|
implementing the BCB support.
|
|||
|
- Ben Taylor for his suggestion to display the object class of all memory
|
|||
|
leaks.
|
|||
|
- Jean Marc Eber and Vincent Mahon (the Memcheck guys) for the call stack
|
|||
|
trace code and also the method used to catch virtual method calls on freed
|
|||
|
objects.
|
|||
|
- Nahan Hyn for the suggestion to be able to enable or disable memory leak
|
|||
|
reporting through a global variable (the "ManualLeakReportingControl"
|
|||
|
option.)
|
|||
|
- Leonel Togniolli for various suggestions with regard to enhancing the bug
|
|||
|
tracking features of FastMM and other helpful advice.
|
|||
|
- Joe Bain and Leonel Togniolli for the workaround to QC#10922 affecting
|
|||
|
compilation under Delphi 2005.
|
|||
|
- Robert Marquardt for the suggestion to make localisation of FastMM easier by
|
|||
|
having all string constants together.
|
|||
|
- Simon Kissel and Fikret Hasovic for their help in implementing Kylix support.
|
|||
|
- Matthias Thoma, Petr Vones, Robert Rossmair and the rest of the JCL team for
|
|||
|
their debug info library used in the debug info support DLL and also the
|
|||
|
code used to check for a valid call site in the "raw" stack trace code.
|
|||
|
- Andreas Hausladen for the suggestion to use an external DLL to enable the
|
|||
|
reporting of debug information.
|
|||
|
- Alexander Tabakov for various good suggestions regarding the debugging
|
|||
|
facilities of FastMM.
|
|||
|
- M. Skloff for some useful suggestions and bringing to my attention some
|
|||
|
compiler warnings.
|
|||
|
- Martin Aignesberger for the code to use madExcept instead of the JCL library
|
|||
|
inside the debug info support DLL.
|
|||
|
- Diederik and Dennis Passmore for the suggestion to be able to register
|
|||
|
expected leaks.
|
|||
|
- Dario Tiraboschi and Mark Gebauer for pointing out the problems that occur
|
|||
|
when range checking and complete boolean evaluation is turned on.
|
|||
|
- Hanspeter Widmer for his suggestion to have an option to display install and
|
|||
|
uninstall debug messages and moving options to a separate file.
|
|||
|
- Arthur Hoornweg for notifying me of the image base being incorrect for
|
|||
|
borlndmm.dll.
|
|||
|
- Theo Carr-Brion and Hanspeter Widmer for finding the false alarm error
|
|||
|
message "Block Header Has Been Corrupted" bug in FullDebugMode.
|
|||
|
- Danny Heijl for reporting the compiler error in "release" mode.
|
|||
|
- Omar Zelaya for reporting the BCB support regression bug.
|
|||
|
- Dan Miser for various good suggestions, e.g. not logging expected leaks to
|
|||
|
file, enhancements the stack trace and messagebox functionality, etc.
|
|||
|
- Arjen de Ruijter for fixing the bug in GetMemoryLeakType that caused it
|
|||
|
to not properly detect expected leaks registered by class when in
|
|||
|
"FullDebugMode".
|
|||
|
- Aleksander Oven for reporting the installation problem when trying to use
|
|||
|
FastMM in an application together with libraries that all use runtime
|
|||
|
packages.
|
|||
|
- Kristofer Skaug for reporting the bug that sometimes causes the leak report
|
|||
|
to be shown, even when all the leaks have been registered as expected leaks.
|
|||
|
Also for some useful enhancement suggestions.
|
|||
|
- G<EFBFBD>nther Schoch for the "RequireDebuggerPresenceForLeakReporting" option.
|
|||
|
- Jan Schl<EFBFBD>ter for the "ForceMMX" option.
|
|||
|
- Hallvard Vassbotn for various good enhancement suggestions.
|
|||
|
- Mark Edington for some good suggestions and bug reports.
|
|||
|
- Paul Ishenin for reporting the compilation error when the NoMessageBoxes
|
|||
|
option is set and also the missing call stack entries issue when "raw" stack
|
|||
|
traces are enabled, as well as for the Russian translation.
|
|||
|
- Cristian Nicola for reporting the compilation bug when the
|
|||
|
CatchUseOfFreedInterfaces option was enabled (4.40).
|
|||
|
- Mathias Rauen (madshi) for improving the support for madExcept in the debug
|
|||
|
info support DLL.
|
|||
|
- Roddy Pratt for the BCB5 support code.
|
|||
|
- Rene Mihula for the Czech translation.
|
|||
|
- Artur Redzko for the Polish translation.
|
|||
|
- Bart van der Werf for helping me solve the DLL unload order problem when
|
|||
|
using the debug mode borlndmm.dll library.
|
|||
|
- JRG ("The Delphi Guy") for the Spanish translation.
|
|||
|
- Justus Janssen for Delphi 4 support.
|
|||
|
- Vadim Lopushansky and Charles Vinal for reporting the Delphi 5 compile error
|
|||
|
in version 4.50.
|
|||
|
- Johni Jeferson Capeletto for the Brazilian Portuguese translation.
|
|||
|
- Kurt Fitzner for reporting the BCb6 compiler error in 4.52.
|
|||
|
- Michal Niklas for reporting the Kylix compiler error in 4.54.
|
|||
|
- Thomas Speck and Uwe Queisser for German translations.
|
|||
|
- Zaenal Mutaqin for the Indonesian translation.
|
|||
|
- Carlos Macao for the Portuguese translation.
|
|||
|
- Michael Winter for catching the performance issue when reallocating certain
|
|||
|
block sizes.
|
|||
|
- dzmitry[li] for the Belarussian translation.
|
|||
|
- Marcelo Montenegro for the updated Spanish translation.
|
|||
|
- Jud Cole for finding and reporting the bug which may trigger a read access
|
|||
|
violation when upsizing certain small block sizes together with the
|
|||
|
"UseCustomVariableSizeMoveRoutines" option.
|
|||
|
- Zdenek Vasku for reporting and fixing the memory manager sharing bug
|
|||
|
affecting Windows 95/98/Me.
|
|||
|
- Any other Fastcoders or supporters that I have forgotten, and also everyone
|
|||
|
that helped with the older versions.
|
|||
|
|
|||
|
Change log:
|
|||
|
Version 1.00 (28 June 2004):
|
|||
|
- First version (called PSDMemoryManager). Based on RecyclerMM (free block
|
|||
|
stack approach) by Eric Grange.
|
|||
|
Version 2.00 (3 November 2004):
|
|||
|
- Complete redesign and rewrite from scratch. Name changed to FastMM to
|
|||
|
reflect this fact. Uses a linked-list approach. Is faster, has less memory
|
|||
|
overhead, and will now catch most bad pointers on FreeMem calls.
|
|||
|
Version 3.00 (1 March 2005):
|
|||
|
- Another rewrite. Reduced the memory overhead by: (a) not having a separate
|
|||
|
memory area for the linked list of free blocks (uses space inside free
|
|||
|
blocks themselves) (b) batch managers are allocated as part of chunks (c)
|
|||
|
block size lookup table size reduced. This should make FastMM more CPU
|
|||
|
cache friendly.
|
|||
|
Version 4.00 (7 June 2005):
|
|||
|
- Yet another rewrite. FastMM4 is in fact three memory managers in one: Small
|
|||
|
blocks (up to a few KB) are managed through the binning model in the same
|
|||
|
way as previous versions, medium blocks (from a few KB up to approximately
|
|||
|
256K) are allocated in a linked-list fashion, and large blocks are grabbed
|
|||
|
directly from the system through VirtualAlloc. This 3-layered design allows
|
|||
|
very fast operation with the most frequently used block sizes (small
|
|||
|
blocks), while also minimizing fragmentation and imparting significant
|
|||
|
overhead savings with blocks larger than a few KB.
|
|||
|
Version 4.01 (8 June 2005):
|
|||
|
- Added the options "RequireDebugInfoForLeakReporting" and
|
|||
|
"RequireIDEPresenceForLeakReporting" as suggested by Pierre Y.
|
|||
|
- Fixed the "DelphiIsRunning" function not working under Delphi 5, and
|
|||
|
consequently no leak checking. (Reported by Anders Isaksson and Greg.)
|
|||
|
Version 4.02 (8 June 2005):
|
|||
|
- Fixed the compilation error when both the "AssumeMultiThreaded" and
|
|||
|
"CheckHeapForCorruption options were set. (Reported by Francois Malan.)
|
|||
|
Version 4.03 (9 June 2005):
|
|||
|
- Added descriptive error messages when FastMM4 cannot be installed because
|
|||
|
another MM has already been installed or memory has already been allocated.
|
|||
|
Version 4.04 (13 June 2005):
|
|||
|
- Added a small fixed offset to the size of medium blocks (previously always
|
|||
|
exact multiples of 256 bytes). This makes performance problems due to CPU
|
|||
|
cache associativity limitations much less likely. (Reported by Craig
|
|||
|
Peterson.)
|
|||
|
Version 4.05 (17 June 2005):
|
|||
|
- Added the Align16Bytes option. Disable this option to drop the 16 byte
|
|||
|
alignment restriction and reduce alignment to 8 bytes for the smallest
|
|||
|
block sizes. Disabling Align16Bytes should lower memory consumption at the
|
|||
|
cost of complicating the use of aligned SSE move instructions. (Suggested
|
|||
|
by Craig Peterson.)
|
|||
|
- Added a support unit for C++ Builder 6 - Add FastMM4BCB.cpp and
|
|||
|
FastMM4.pas to your BCB project to use FastMM instead of the RTL MM. Memory
|
|||
|
leak checking is not supported because (unfortunately) once an MM is
|
|||
|
installed under BCB you cannot uninstall it... at least not without
|
|||
|
modifying the RTL code in exit.c or patching the RTL code runtime. (Thanks
|
|||
|
to Jarek Karciarz, Vladimir Ulchenko and Bob Gonder.)
|
|||
|
Version 4.06 (22 June 2005):
|
|||
|
- Displays the class of all leaked objects on the memory leak report and also
|
|||
|
tries to identify leaked long strings. Previously it only displayed the
|
|||
|
sizes of all leaked blocks. (Suggested by Ben Taylor.)
|
|||
|
- Added support for displaying the sizes of medium and large block memory
|
|||
|
leaks. Previously it only displayed details for small block leaks.
|
|||
|
Version 4.07 (22 June 2005):
|
|||
|
- Fixed the detection of the class of leaked objects not working under
|
|||
|
Windows 98/Me.
|
|||
|
Version 4.08 (27 June 2005):
|
|||
|
- Added a BorlndMM.dpr project to allow you to build a borlndmm.dll that uses
|
|||
|
FastMM4 instead of the default memory manager. You may replace the old
|
|||
|
DLL in the Delphi \Bin directory to make the IDE use this memory manager
|
|||
|
instead.
|
|||
|
Version 4.09 (30 June 2005):
|
|||
|
- Included a patch fix for the bug affecting replacement borlndmm.dll files
|
|||
|
with Delphi 2005 (QC#14007). Compile the patch, close Delphi, and run it
|
|||
|
once to patch your vclide90.bpl. You will now be able to use the
|
|||
|
replacement borlndmm.dll to speed up the Delphi 2005 IDE as well.
|
|||
|
Version 4.10 (7 July 2005):
|
|||
|
- Due to QC#14070 ("Delphi IDE attempts to free memory after the shutdown
|
|||
|
code of borlndmm.dll has been called"), FastMM cannot be uninstalled
|
|||
|
safely when used inside a replacement borlndmm.dll for the IDE. Added a
|
|||
|
conditional define "NeverUninstall" for this purpose.
|
|||
|
- Added the "FullDebugMode" option to pad all blocks with a header and footer
|
|||
|
to help you catch memory overwrite bugs in your applications. All blocks
|
|||
|
returned to freemem are also zeroed out to help catch bugs involving the
|
|||
|
use of previously freed blocks. Also catches attempts at calling virtual
|
|||
|
methods of freed objects provided the block in question has not been reused
|
|||
|
since the object was freed. Displays stack traces on error to aid debugging.
|
|||
|
- Added the "LogErrorsToFile" option to log all errors to a text file in the
|
|||
|
same folder as the application.
|
|||
|
- Added the "ManualLeakReportingControl" option (suggested by Nahan Hyn) to
|
|||
|
enable control over whether the memory leak report should be done or not
|
|||
|
via a global variable.
|
|||
|
Version 4.11 (7 July 2005):
|
|||
|
- Fixed a compilation error under Delphi 2005 due to QC#10922. (Thanks to Joe
|
|||
|
Bain and Leonel Togniolli.)
|
|||
|
- Fixed leaked object classes not displaying in the leak report in
|
|||
|
"FullDebugMode".
|
|||
|
Version 4.12 (8 July 2005):
|
|||
|
- Moved all the string constants to one place to make it easier to do
|
|||
|
translations into other languages. (Thanks to Robert Marquardt.)
|
|||
|
- Added support for Kylix. Some functionality is currently missing: No
|
|||
|
support for detecting the object class on leaks and also no MM sharing.
|
|||
|
(Thanks to Simon Kissel and Fikret Hasovic).
|
|||
|
Version 4.13 (11 July 2005):
|
|||
|
- Added the FastMM_DebugInfo.dll support library to display debug info for
|
|||
|
stack traces.
|
|||
|
- Stack traces for the memory leak report is now logged to the log file in
|
|||
|
"FullDebugMode".
|
|||
|
Version 4.14 (14 July 2005):
|
|||
|
- Fixed string leaks not being detected as such in "FullDebugMode". (Thanks
|
|||
|
to Leonel Togniolli.)
|
|||
|
- Fixed the compilation error in "FullDebugMode" when "LogErrorsToFile" is
|
|||
|
not set. (Thanks to Leonel Togniolli.)
|
|||
|
- Added a "Release" option to allow the grouping of various options and to
|
|||
|
make it easier to make debug and release builds. (Thanks to Alexander
|
|||
|
Tabakov.)
|
|||
|
- Added a "HideMemoryLeakHintMessage" option to not display the hint below
|
|||
|
the memory leak message. (Thanks to Alexander Tabakov.)
|
|||
|
- Changed the fill character for "FullDebugMode" from zero to $80 to be able
|
|||
|
to differentiate between invalid memory accesses using nil pointers to
|
|||
|
invalid memory accesses using fields of freed objects. FastMM tries to
|
|||
|
reserve the 64K block starting at $80800000 at startup to ensure that an
|
|||
|
A/V will occur when this block is accessed. (Thanks to Alexander Tabakov.)
|
|||
|
- Fixed some compiler warnings. (Thanks to M. Skloff)
|
|||
|
- Fixed some display bugs in the memory leak report. (Thanks to Leonel
|
|||
|
Togniolli.)
|
|||
|
- Added a "LogMemoryLeakDetailToFile" option. Some applications leak a lot of
|
|||
|
memory and can make the log file grow very large very quickly.
|
|||
|
- Added the option to use madExcept instead of the JCL Debug library in the
|
|||
|
debug info support DLL. (Thanks to Martin Aignesberger.)
|
|||
|
- Added procedures "GetMemoryManagerState" and "GetMemoryMap" to retrieve
|
|||
|
statistics about the current state of the memory manager and memory pool.
|
|||
|
(A usage tracker form together with a demo is also available.)
|
|||
|
Version 4.15 (14 July 2005):
|
|||
|
- Fixed a false 4GB(!) memory leak reported in some instances.
|
|||
|
Version 4.16 (15 July 2005):
|
|||
|
- Added the "CatchUseOfFreedInterfaces" option to catch the use of interfaces
|
|||
|
of freed objects. This option is not compatible with checking that a freed
|
|||
|
block has not been modified, so enable this option only when hunting an
|
|||
|
invalid interface reference. (Only relevant if "FullDebugMode" is set.)
|
|||
|
- During shutdown FastMM now checks that all free blocks have not been
|
|||
|
modified since being freed. (Only when "FullDebugMode" is set and
|
|||
|
"CatchUseOfFreedInterfaces" is disabled.)
|
|||
|
Version 4.17 (15 July 2005):
|
|||
|
- Added the AddExpectedMemoryLeaks and RemoveExpectedMemoryLeaks procedures to
|
|||
|
register/unregister expected leaks, thus preventing the leak report from
|
|||
|
displaying if only expected leaks occurred. (Thanks to Diederik and Dennis
|
|||
|
Passmore for the suggestion.)
|
|||
|
- Fixed the "LogMemoryLeakDetailToFile" not logging memory leak detail to file
|
|||
|
as it is supposed to. (Thanks to Leonel Togniolli.)
|
|||
|
Version 4.18 (18 July 2005):
|
|||
|
- Fixed some issues when range checking or complete boolean evaluation is
|
|||
|
switched on. (Thanks to Dario Tiraboschi and Mark Gebauer.)
|
|||
|
- Added the "OutputInstallUninstallDebugString" option to display a message when
|
|||
|
FastMM is installed or uninstalled. (Thanks to Hanspeter Widmer.)
|
|||
|
- Moved the options to a separate include file. (Thanks to Hanspeter Widmer.)
|
|||
|
- Moved message strings to a separate file for easy translation.
|
|||
|
Version 4.19 (19 July 2005):
|
|||
|
- Fixed Kylix support that was broken in 4.14.
|
|||
|
Version 4.20 (20 July 2005):
|
|||
|
- Fixed a false memory overwrite report at shutdown in "FullDebugMode". If you
|
|||
|
consistently got a "Block Header Has Been Corrupted" error message during
|
|||
|
shutdown at address $xxxx0070 then it was probably a false alarm. (Thanks to
|
|||
|
Theo Carr-Brion and Hanspeter Widmer.}
|
|||
|
Version 4.21 (27 July 2005):
|
|||
|
- Minor change to the block header flags to make it possible to immediately
|
|||
|
tell whether a medium block is being used as a small block pool or not.
|
|||
|
(Simplifies the leak checking and status reporting code.)
|
|||
|
- Expanded the functionality around the management of expected memory leaks.
|
|||
|
- Added the "ClearLogFileOnStartup" option. Deletes the log file during
|
|||
|
initialization. (Thanks to M. Skloff.)
|
|||
|
- Changed "OutputInstallUninstallDebugString" to use OutputDebugString instead
|
|||
|
of MessageBox. (Thanks to Hanspeter Widmer.)
|
|||
|
Version 4.22 (1 August 2005):
|
|||
|
- Added a FastAllocMem function that avoids an unnecessary FillChar call with
|
|||
|
large blocks.
|
|||
|
- Changed large block resizing behavior to be a bit more conservative. Large
|
|||
|
blocks will be downsized if the new size is less than half of the old size
|
|||
|
(the threshold was a quarter previously).
|
|||
|
Version 4.23 (6 August 2005):
|
|||
|
- Fixed BCB6 support (Thanks to Omar Zelaya).
|
|||
|
- Renamed "OutputInstallUninstallDebugString" to "UseOutputDebugString", and
|
|||
|
added debug string output on memory leak or error detection.
|
|||
|
Version 4.24 (11 August 2005):
|
|||
|
- Added the "NoMessageBoxes" option to suppress the display of message boxes,
|
|||
|
which is useful for services that should not be interrupted. (Thanks to Dan
|
|||
|
Miser).
|
|||
|
- Changed the stack trace code to return the line number of the caller and not
|
|||
|
the line number of the return address. (Thanks to Dan Miser).
|
|||
|
Version 4.25 (15 August 2005):
|
|||
|
- Fixed GetMemoryLeakType not detecting expected leaks registered by class
|
|||
|
when in "FullDebugMode". (Thanks to Arjen de Ruijter).
|
|||
|
Version 4.26 (18 August 2005):
|
|||
|
- Added a "UseRuntimePackages" option that allows FastMM to be used in a main
|
|||
|
application together with DLLs that all use runtime packages. (Thanks to
|
|||
|
Aleksander Oven.)
|
|||
|
Version 4.27 (24 August 2005):
|
|||
|
- Fixed a bug that sometimes caused the leak report to be shown even though all
|
|||
|
leaks were registered as expected leaks. (Thanks to Kristofer Skaug.)
|
|||
|
Version 4.29 (30 September 2005):
|
|||
|
- Added the "RequireDebuggerPresenceForLeakReporting" option to only display
|
|||
|
the leak report if the application is run inside the IDE. (Thanks to G<EFBFBD>nther
|
|||
|
Schoch.)
|
|||
|
- Added the "ForceMMX" option, which when disabled will check the CPU for
|
|||
|
MMX compatibility before using MMX. (Thanks to Jan Schl<EFBFBD>ter.)
|
|||
|
- Added the module name to the title of error dialogs to more easily identify
|
|||
|
which application caused the error. (Thanks to Kristofer Skaug.)
|
|||
|
- Added an ASCII dump to the "FullDebugMode" memory dumps. (Thanks to Hallvard
|
|||
|
Vassbotn.)
|
|||
|
- Added the option "HideExpectedLeaksRegisteredByPointer" to suppress the
|
|||
|
display and logging of expected memory leaks that were registered by pointer.
|
|||
|
(Thanks to Dan Miser.) Leaks registered by size or class are often ambiguous,
|
|||
|
so these expected leaks are always logged to file (in FullDebugMode) and are
|
|||
|
never hidden from the leak display (only displayed if there is at least one
|
|||
|
unexpected leak).
|
|||
|
- Added a procedure "GetRegisteredMemoryLeaks" to return a list of all
|
|||
|
registered memory leaks. (Thanks to Dan Miser.)
|
|||
|
- Added the "RawStackTraces" option to perform "raw" stack traces, negating
|
|||
|
the need for stack frames. This will usually result in more complete stack
|
|||
|
traces in FullDebugMode error reports, but it is significantly slower.
|
|||
|
(Thanks to Hallvard Vassbotn, Dan Miser and the JCL team.)
|
|||
|
Version 4.31 (2 October 2005):
|
|||
|
- Fixed the crash bug when both "RawStackTraces" and "FullDebugMode" were
|
|||
|
enabled. (Thanks to Dan Miser and Mark Edington.)
|
|||
|
Version 4.33 (6 October 2005):
|
|||
|
- Added a header corruption check to all memory blocks that are identified as
|
|||
|
leaks in FullDebugMode. This allows better differentiation between memory
|
|||
|
pool corruption bugs and actual memory leaks.
|
|||
|
- Fixed the stack overflow bug when using "RawStackTraces".
|
|||
|
Version 4.35 (6 October 2005):
|
|||
|
- Fixed a compilation error when the "NoMessageBoxes" option is set. (Thanks
|
|||
|
to Paul Ishenin.)
|
|||
|
- Before performing a "raw" stack trace, FastMM now checks whether exception
|
|||
|
handling is in place. If exception handling is not in place FastMM falls
|
|||
|
back to stack frame tracing. (Exception handling is required to handle the
|
|||
|
possible A/Vs when reading invalid call addresses. Exception handling is
|
|||
|
usually always available except when SysUtils hasn't been initialized yet or
|
|||
|
after SysUtils has been finalized.)
|
|||
|
Version 4.37 (8 October 2005):
|
|||
|
- Fixed the missing call stack trace entry issue when dynamically loading DLLs.
|
|||
|
(Thanks to Paul Ishenin.)
|
|||
|
Version 4.39 (12 October 2005):
|
|||
|
- Restored the performance with "RawStackTraces" enabled back to the level it
|
|||
|
was in 4.35.
|
|||
|
- Fixed the stack overflow error when using "RawStackTraces" that I thought I
|
|||
|
had fixed in 4.31, but unfortunately didn't. (Thanks to Craig Peterson.)
|
|||
|
Version 4.40 (13 October 2005):
|
|||
|
- Improved "RawStackTraces" to have less incorrect extra entries. (Thanks to
|
|||
|
Craig Peterson.)
|
|||
|
- Added the Russian (by Paul Ishenin) and Afrikaans translations of
|
|||
|
FastMM4Messages.pas.
|
|||
|
Version 4.42 (13 October 2005):
|
|||
|
- Fixed the compilation error when "CatchUseOfFreedInterfaces" is enabled.
|
|||
|
(Thanks to Cristian Nicola.)
|
|||
|
Version 4.44 (25 October 2005):
|
|||
|
- Implemented a FastGetHeapStatus function in analogy with GetHeapStatus.
|
|||
|
(Suggested by Cristian Nicola.)
|
|||
|
- Shifted more of the stack trace code over to the support dll to allow third
|
|||
|
party vendors to make available their own stack tracing and stack trace
|
|||
|
logging facilities.
|
|||
|
- Mathias Rauen (madshi) improved the support for madExcept in the debug info
|
|||
|
support DLL. Thanks!
|
|||
|
- Added support for BCB5. (Thanks to Roddy Pratt.)
|
|||
|
- Added the Czech translation by Rene Mihula.
|
|||
|
- Added the "DetectMMOperationsAfterUninstall" option. This will catch
|
|||
|
attempts to use the MM after FastMM has been uninstalled, and is useful for
|
|||
|
debugging.
|
|||
|
Version 4.46 (26 October 2005):
|
|||
|
- Renamed FastMM_DebugInfo.dll to FastMM_FullDebugMode.dll and made the
|
|||
|
dependency on this library a static one. This solves a DLL unload order
|
|||
|
problem when using FullDebugMode together with the replacement
|
|||
|
borlndmm.dll. (Thanks to Bart van der Werf.)
|
|||
|
- Added the Polish translation by Artur Redzko.
|
|||
|
Version 4.48 (10 November 2005):
|
|||
|
- Fixed class detection for objects leaked in dynamically loaded DLLs that
|
|||
|
were relocated.
|
|||
|
- Fabio Dell'Aria implemented support for EurekaLog in the FullDebugMode
|
|||
|
support DLL. Thanks!
|
|||
|
- Added the Spanish translation by JRG ("The Delphi Guy").
|
|||
|
Version 4.49 (10 November 2005):
|
|||
|
- Implemented support for installing replacement AllocMem and leak
|
|||
|
registration mechanisms for Delphi/BCB versions that support it.
|
|||
|
- Added support for Delphi 4. (Thanks to Justus Janssen.)
|
|||
|
Version 4.50 (5 December 2005):
|
|||
|
- Renamed the ReportMemoryLeaks global variable to ReportMemoryLeaksOnShutdown
|
|||
|
to be more consistent with the Delphi 2006 memory manager.
|
|||
|
- Improved the handling of large blocks. Large blocks can now consist of
|
|||
|
several consecutive segments allocated through VirtualAlloc. This
|
|||
|
significantly improves speed when frequently resizing large blocks, since
|
|||
|
these blocks can now often be upsized in-place.
|
|||
|
Version 4.52 (7 December 2005):
|
|||
|
- Fixed the compilation error with Delphi 5. (Thanks to Vadim Lopushansky and
|
|||
|
Charles Vinal for reporting the error.)
|
|||
|
Version 4.54 (15 December 2005):
|
|||
|
- Added the Brazilian Portuguese translation by Johni Jeferson Capeletto.
|
|||
|
- Fixed the compilation error with BCB6. (Thanks to Kurt Fitzner.)
|
|||
|
Version 4.56 (20 December 2005):
|
|||
|
- Fixed the Kylix compilation problem. (Thanks to Michal Niklas.)
|
|||
|
Version 4.58 (1 February 2006):
|
|||
|
- Added the German translations by Thomas Speck and Uwe Queisser.
|
|||
|
- Added the Indonesian translation by Zaenal Mutaqin.
|
|||
|
- Added the Portuguese translation by Carlos Macao.
|
|||
|
Version 4.60 (21 February 2006):
|
|||
|
- Fixed a performance issue due to an unnecessary block move operation when
|
|||
|
allocating a block in the range 1261-1372 bytes and then reallocating it in
|
|||
|
the range 1373-1429 bytes twice. (Thanks to Michael Winter.)
|
|||
|
- Added the Belarussian translation by dzmitry[li].
|
|||
|
- Added the updated Spanish translation by Marcelo Montenegro.
|
|||
|
- Added a new option "EnableSharingWithDefaultMM". This option allows FastMM
|
|||
|
to be shared with the default MM of Delphi 2006. It is on by default, but
|
|||
|
MM sharing has to be enabled otherwise it has no effect (refer to the
|
|||
|
documentation for the "ShareMM" and "AttemptToUseSharedMM" options).
|
|||
|
Version 4.62 (22 February 2006):
|
|||
|
- Fixed a possible read access violation in the MoveX16L4 routine when the
|
|||
|
UseCustomVariableSizeMoveRoutines option is enabled. (Thanks to Jud Cole for
|
|||
|
some great detective work in finding this bug.)
|
|||
|
- Improved the downsizing behaviour of medium blocks to better correlate with
|
|||
|
the reallocation behaviour of small blocks. This change reduces the number
|
|||
|
of transitions between small and medium block types when reallocating blocks
|
|||
|
in the 0.7K to 2.6K range. It cuts down on the number of memory move
|
|||
|
operations and improves performance.
|
|||
|
Version 4.64 (31 March 2006):
|
|||
|
- Added the following functions for use with FullDebugMode (and added the
|
|||
|
exports to the replacement BorlndMM.dll): SetMMLogFileName,
|
|||
|
GetCurrentAllocationGroup, PushAllocationGroup, PopAllocationGroup and
|
|||
|
LogAllocatedBlocksToFile. The purpose of these functions are to allow you to
|
|||
|
identify and log related memory leaks while your application is still
|
|||
|
running.
|
|||
|
- Fixed a bug in the memory manager sharing mechanism affecting Windows
|
|||
|
95/98/ME. (Thanks to Zdenek Vasku.)
|
|||
|
|
|||
|
*)
|
|||
|
|
|||
|
unit FastMM4;
|
|||
|
|
|||
|
interface
|
|||
|
|
|||
|
{$Include FastMM4Options.inc}
|
|||
|
|
|||
|
{$RANGECHECKS OFF}
|
|||
|
{$BOOLEVAL OFF}
|
|||
|
{$OVERFLOWCHECKS OFF}
|
|||
|
{$OPTIMIZATION ON}
|
|||
|
{$TYPEDADDRESS OFF}
|
|||
|
|
|||
|
{Some features not currently supported under Kylix}
|
|||
|
{$ifdef Linux}
|
|||
|
{$undef LogErrorsToFile}
|
|||
|
{$undef LogMemoryLeakDetailToFile}
|
|||
|
{$undef ShareMM}
|
|||
|
{$undef AttemptToUseSharedMM}
|
|||
|
{$undef RequireIDEPresenceForLeakReporting}
|
|||
|
{$undef UseOutputDebugString}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Do we require debug info for leak checking?}
|
|||
|
{$ifdef RequireDebugInfoForLeakReporting}
|
|||
|
{$ifopt D-}
|
|||
|
{$undef EnableMemoryLeakReporting}
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Enable heap checking and leak reporting in full debug mode}
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{$STACKFRAMES ON}
|
|||
|
{$define CheckHeapForCorruption}
|
|||
|
{$ifndef CatchUseOfFreedInterfaces}
|
|||
|
{$define CheckUseOfFreedBlocksOnShutdown}
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
{Error logging requires FullDebugMode}
|
|||
|
{$undef LogErrorsToFile}
|
|||
|
{$undef CatchUseOfFreedInterfaces}
|
|||
|
{$undef RawStackTraces}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Only the pascal version supports extended heap corruption checking.}
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{$undef ASMVersion}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifdef UseRuntimePackages}
|
|||
|
{$define AssumeMultiThreaded}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Delphi versions}
|
|||
|
{$ifndef BCB}
|
|||
|
{$ifdef ver120}
|
|||
|
{$define Delphi4or5}
|
|||
|
{$endif}
|
|||
|
{$ifdef ver130}
|
|||
|
{$define Delphi4or5}
|
|||
|
{$endif}
|
|||
|
{$ifdef ver140}
|
|||
|
{$define Delphi6}
|
|||
|
{$endif}
|
|||
|
{$ifdef ver150}
|
|||
|
{$define Delphi7}
|
|||
|
{$endif}
|
|||
|
{$ifdef ver170}
|
|||
|
{$define Delphi2005}
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
{Cannot uninstall safely under BCB}
|
|||
|
{$define NeverUninstall}
|
|||
|
{Disable memory leak reporting}
|
|||
|
{$undef EnableMemoryLeakReporting}
|
|||
|
{for BCB5, use the Delphi 5 codepath}
|
|||
|
{$ifdef ver130}
|
|||
|
{$define Delphi4or5}
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{$ifdef ver180}
|
|||
|
{$define BDS2006}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef Delphi4or5}
|
|||
|
{$ifndef BCB}
|
|||
|
{$define Delphi6AndUp}
|
|||
|
{$endif}
|
|||
|
{$ifndef Delphi6}
|
|||
|
{$define BCB6OrDelphi7AndUp}
|
|||
|
{$ifndef BCB}
|
|||
|
{$define Delphi7AndUp}
|
|||
|
{$endif}
|
|||
|
{$ifndef BCB}
|
|||
|
{$ifndef Delphi7}
|
|||
|
{$ifndef Delphi2005}
|
|||
|
{$define BDS2006AndUp}
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifdef Delphi6AndUp}
|
|||
|
{$WARN SYMBOL_PLATFORM OFF}
|
|||
|
{$WARN SYMBOL_DEPRECATED OFF}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Leak detail logging requires error logging}
|
|||
|
{$ifndef LogErrorsToFile}
|
|||
|
{$undef LogMemoryLeakDetailToFile}
|
|||
|
{$undef ClearLogFileOnStartup}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef EnableMemoryLeakReporting}
|
|||
|
{Manual leak reporting control requires leak reporting to be enabled}
|
|||
|
{$undef ManualLeakReportingControl}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef EnableMMX}
|
|||
|
{$undef ForceMMX}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-------------------------Public constants-----------------------------}
|
|||
|
const
|
|||
|
{The number of small block types}
|
|||
|
{$ifdef Align16Bytes}
|
|||
|
NumSmallBlockTypes = 46;
|
|||
|
{$else}
|
|||
|
NumSmallBlockTypes = 55;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{----------------------------Public types------------------------------}
|
|||
|
type
|
|||
|
TSmallBlockTypeState = packed record
|
|||
|
{The internal size of the block type}
|
|||
|
InternalBlockSize: Cardinal;
|
|||
|
{Useable block size: The number of non-reserved bytes inside the block.}
|
|||
|
UseableBlockSize: Cardinal;
|
|||
|
{The number of allocated blocks}
|
|||
|
AllocatedBlockCount: Cardinal;
|
|||
|
{The total address space reserved for this block type (both allocated and
|
|||
|
free blocks)}
|
|||
|
ReservedAddressSpace: Cardinal;
|
|||
|
end;
|
|||
|
TSmallBlockTypeStates = array[0..NumSmallBlockTypes - 1] of TSmallBlockTypeState;
|
|||
|
|
|||
|
TMemoryManagerState = packed record
|
|||
|
{Small block type states}
|
|||
|
SmallBlockTypeStates: TSmallBlockTypeStates;
|
|||
|
{Medium block stats}
|
|||
|
AllocatedMediumBlockCount: Cardinal;
|
|||
|
TotalAllocatedMediumBlockSize: Cardinal;
|
|||
|
ReservedMediumBlockAddressSpace: Cardinal;
|
|||
|
{Large block stats}
|
|||
|
AllocatedLargeBlockCount: Cardinal;
|
|||
|
TotalAllocatedLargeBlockSize: Cardinal;
|
|||
|
ReservedLargeBlockAddressSpace: Cardinal;
|
|||
|
end;
|
|||
|
|
|||
|
{Memory map}
|
|||
|
TChunkStatus = (csUnallocated, csAllocated, csReserved,
|
|||
|
csSysAllocated, csSysReserved);
|
|||
|
TMemoryMap = array[0..65535] of TChunkStatus;
|
|||
|
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
{List of registered leaks}
|
|||
|
TRegisteredMemoryLeak = packed record
|
|||
|
LeakAddress: Pointer;
|
|||
|
LeakedClass: TClass;
|
|||
|
LeakSize: Integer;
|
|||
|
LeakCount: Integer;
|
|||
|
end;
|
|||
|
TRegisteredMemoryLeaks = array of TRegisteredMemoryLeak;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{--------------------------Public variables----------------------------}
|
|||
|
{$ifdef ManualLeakReportingControl}
|
|||
|
{Variable is declared in system.pas in newer Delphi versions.}
|
|||
|
{$ifndef BDS2006AndUp}
|
|||
|
var
|
|||
|
ReportMemoryLeaksOnShutdown: Boolean;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-------------------------Public procedures----------------------------}
|
|||
|
{Installation procedures must be exposed for the BCB helper unit FastMM4BCB.cpp}
|
|||
|
{$ifdef BCB}
|
|||
|
procedure InitializeMemoryManager;
|
|||
|
function CheckCanInstallMemoryManager: boolean;
|
|||
|
procedure InstallMemoryManager;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{The standard memory manager functions}
|
|||
|
function FastGetMem(ASize: Integer): Pointer;
|
|||
|
function FastFreeMem(APointer: Pointer): Integer;
|
|||
|
function FastReallocMem(APointer: Pointer; ANewSize: Integer): Pointer;
|
|||
|
function FastAllocMem(ASize: Cardinal): Pointer;
|
|||
|
{$else}
|
|||
|
{The FullDebugMode memory manager functions}
|
|||
|
function DebugGetMem(ASize: Integer): Pointer;
|
|||
|
function DebugFreeMem(APointer: Pointer): Integer;
|
|||
|
function DebugReallocMem(APointer: Pointer; ANewSize: Integer): Pointer;
|
|||
|
function DebugAllocMem(ASize: Cardinal): Pointer;
|
|||
|
{Specify the full path and name for the filename to be used for logging memory
|
|||
|
errors, etc. If ALogFileName is nil or points to an empty string it will
|
|||
|
revert to the default log file name.}
|
|||
|
procedure SetMMLogFileName(ALogFileName: PChar = nil);
|
|||
|
{Returns the current "allocation group". Whenever a GetMem request is serviced
|
|||
|
in FullDebugMode, the current "allocation group" is stored in the block header.
|
|||
|
This may help with debugging. Note that if a block is subsequently reallocated
|
|||
|
that it keeps its original "allocation group" and "allocation number" (all
|
|||
|
allocations are also numbered sequentially).}
|
|||
|
function GetCurrentAllocationGroup: Cardinal;
|
|||
|
{Allocation groups work in a stack like fashion. Group numbers are pushed onto
|
|||
|
and popped off the stack. Note that the stack size is limited, so every push
|
|||
|
should have a matching pop.}
|
|||
|
procedure PushAllocationGroup(ANewCurrentAllocationGroup: Cardinal);
|
|||
|
procedure PopAllocationGroup;
|
|||
|
{Logs detail about currently allocated memory blocks for the specified range of
|
|||
|
allocation groups. if ALastAllocationGroupToLog is less than
|
|||
|
AFirstAllocationGroupToLog or it is zero, then all allocation groups are
|
|||
|
logged. This routine also checks the memory pool for consistency at the same
|
|||
|
time.}
|
|||
|
procedure LogAllocatedBlocksToFile(AFirstAllocationGroupToLog, ALastAllocationGroupToLog: Cardinal);
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Releases all allocated memory (use with extreme care)}
|
|||
|
procedure FreeAllMemory;
|
|||
|
|
|||
|
{Returns summarised information about the state of the memory manager. (For
|
|||
|
backward compatibility.)}
|
|||
|
function FastGetHeapStatus: THeapStatus;
|
|||
|
{Returns statistics about the current state of the memory manager}
|
|||
|
procedure GetMemoryManagerState(var AMemoryManagerState: TMemoryManagerState);
|
|||
|
{$ifndef LINUX}
|
|||
|
{Gets the state of every 64K block in the 4GB address space}
|
|||
|
procedure GetMemoryMap(var AMemoryMap: TMemoryMap);
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
{Registers expected memory leaks. Returns true on success. The list of leaked
|
|||
|
blocks is limited, so failure is possible if the list is full.}
|
|||
|
function RegisterExpectedMemoryLeak(ALeakedPointer: Pointer): boolean; overload;
|
|||
|
function RegisterExpectedMemoryLeak(ALeakedObjectClass: TClass; ACount: Integer = 1): boolean; overload;
|
|||
|
function RegisterExpectedMemoryLeak(ALeakedBlockSize: Integer; ACount: Integer = 1): boolean; overload;
|
|||
|
{Removes expected memory leaks. Returns true on success.}
|
|||
|
function UnregisterExpectedMemoryLeak(ALeakedPointer: Pointer): boolean; overload;
|
|||
|
function UnregisterExpectedMemoryLeak(ALeakedObjectClass: TClass; ACount: Integer = 1): boolean; overload;
|
|||
|
function UnregisterExpectedMemoryLeak(ALeakedBlockSize: Integer; ACount: Integer = 1): boolean; overload;
|
|||
|
{Returns a list of all expected memory leaks}
|
|||
|
function GetRegisteredMemoryLeaks: TRegisteredMemoryLeaks;
|
|||
|
{$endif}
|
|||
|
|
|||
|
implementation
|
|||
|
|
|||
|
uses
|
|||
|
{$ifndef Linux}
|
|||
|
Windows,
|
|||
|
{$else}
|
|||
|
Libc,
|
|||
|
{$endif}
|
|||
|
FastMM4Messages;
|
|||
|
|
|||
|
{Fixed size move procedures}
|
|||
|
procedure Move12(const ASource; var ADest; ACount: Integer); forward;
|
|||
|
procedure Move20(const ASource; var ADest; ACount: Integer); forward;
|
|||
|
procedure Move28(const ASource; var ADest; ACount: Integer); forward;
|
|||
|
procedure Move36(const ASource; var ADest; ACount: Integer); forward;
|
|||
|
procedure Move44(const ASource; var ADest; ACount: Integer); forward;
|
|||
|
procedure Move52(const ASource; var ADest; ACount: Integer); forward;
|
|||
|
procedure Move60(const ASource; var ADest; ACount: Integer); forward;
|
|||
|
procedure Move68(const ASource; var ADest; ACount: Integer); forward;
|
|||
|
|
|||
|
{$ifdef DetectMMOperationsAfterUninstall}
|
|||
|
{Invalid handlers to catch MM operations after uninstall}
|
|||
|
function InvalidFreeMem(APointer: Pointer): Integer; forward;
|
|||
|
function InvalidGetMem(ASize: Integer): Pointer; forward;
|
|||
|
function InvalidReallocMem(APointer: Pointer; ANewSize: Integer): Pointer; forward;
|
|||
|
function InvalidAllocMem(ASize: Cardinal): Pointer; forward;
|
|||
|
function InvalidRegisterAndUnRegisterMemoryLeak(APointer: Pointer): Boolean; forward;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-------------------------Private constants----------------------------}
|
|||
|
const
|
|||
|
{The size of a medium block pool. This is allocated through VirtualAlloc and
|
|||
|
is used to serve medium blocks. The size must be a multiple of 16 and at
|
|||
|
least 4 bytes less than a multiple of 4K (the page size) to prevent a
|
|||
|
possible read access violation when reading past the end of a memory block
|
|||
|
in the optimized move routine (MoveX16L4). In Full Debug mode we leave a
|
|||
|
trailing 256 bytes to be able to safely do a memory dump.}
|
|||
|
MediumBlockPoolSize = 20 * 64 * 1024{$ifndef FullDebugMode} - 16{$else} - 256{$endif};
|
|||
|
{The granularity of small blocks}
|
|||
|
{$ifdef Align16Bytes}
|
|||
|
SmallBlockGranularity = 16;
|
|||
|
{$else}
|
|||
|
SmallBlockGranularity = 8;
|
|||
|
{$endif}
|
|||
|
{The granularity of medium blocks. Newly allocated medium blocks are
|
|||
|
a multiple of this size plus MediumBlockSizeOffset, to avoid cache line
|
|||
|
conflicts}
|
|||
|
MediumBlockGranularity = 256;
|
|||
|
MediumBlockSizeOffset = 48;
|
|||
|
{The granularity of large blocks}
|
|||
|
LargeBlockGranularity = 65536;
|
|||
|
{The maximum size of a small block. Blocks Larger than this are either
|
|||
|
medium or large blocks.}
|
|||
|
MaximumSmallBlockSize = 2608;
|
|||
|
{The smallest medium block size. (Medium blocks are rounded up to the nearest
|
|||
|
multiple of MediumBlockGranularity plus MediumBlockSizeOffset)}
|
|||
|
MinimumMediumBlockSize = 11 * 256 + MediumBlockSizeOffset;
|
|||
|
{The number of bins reserved for medium blocks}
|
|||
|
MediumBlockBinsPerGroup = 32;
|
|||
|
MediumBlockBinGroupCount = 32;
|
|||
|
MediumBlockBinCount = MediumBlockBinGroupCount * MediumBlockBinsPerGroup;
|
|||
|
{The maximum size allocatable through medium blocks. Blocks larger than this
|
|||
|
fall through to VirtualAlloc ( = large blocks).}
|
|||
|
MaximumMediumBlockSize = MinimumMediumBlockSize + (MediumBlockBinCount - 1) * MediumBlockGranularity;
|
|||
|
{The target number of small blocks per pool. The actual number of blocks per
|
|||
|
pool may be much greater for very small sizes and less for larger sizes. The
|
|||
|
cost of allocating the small block pool is amortized across all the small
|
|||
|
blocks in the pool, however the blocks may not all end up being used so they
|
|||
|
may be lying idle.}
|
|||
|
TargetSmallBlocksPerPool = 48;
|
|||
|
{The minimum number of small blocks per pool. Any available medium block must
|
|||
|
have space for roughly this many small blocks (or more) to be useable as a
|
|||
|
small block pool.}
|
|||
|
MinimumSmallBlocksPerPool = 12;
|
|||
|
{The lower and upper limits for the optimal small block pool size}
|
|||
|
OptimalSmallBlockPoolSizeLowerLimit = 29 * 1024 - MediumBlockGranularity + MediumBlockSizeOffset;
|
|||
|
OptimalSmallBlockPoolSizeUpperLimit = 64 * 1024 - MediumBlockGranularity + MediumBlockSizeOffset;
|
|||
|
{The maximum small block pool size. If a free block is this size or larger
|
|||
|
then it will be split.}
|
|||
|
MaximumSmallBlockPoolSize = OptimalSmallBlockPoolSizeUpperLimit + MinimumMediumBlockSize;
|
|||
|
{-------------Block type flags--------------}
|
|||
|
{The lower 3 bits in the dword header of small blocks (4 bits in medium and
|
|||
|
large blocks) are used as flags to indicate the state of the block}
|
|||
|
{Set if the block is not in use}
|
|||
|
IsFreeBlockFlag = 1;
|
|||
|
{Set if this is a medium block}
|
|||
|
IsMediumBlockFlag = 2;
|
|||
|
{Set if it is a medium block being used as a small block pool. Only valid if
|
|||
|
IsMediumBlockFlag is set.}
|
|||
|
IsSmallBlockPoolInUseFlag = 4;
|
|||
|
{Set if it is a large block. Only valid if IsMediumBlockFlag is not set.}
|
|||
|
IsLargeBlockFlag = 4;
|
|||
|
{Is the medium block preceding this block available? (Only used by medium
|
|||
|
blocks)}
|
|||
|
PreviousMediumBlockIsFreeFlag = 8;
|
|||
|
{Is this large block segmented? I.e. is it actually built up from more than
|
|||
|
one chunk allocated through VirtualAlloc? (Only used by large blocks.)}
|
|||
|
LargeBlockIsSegmented = 8;
|
|||
|
{The flags masks for small blocks}
|
|||
|
DropSmallFlagsMask = -8;
|
|||
|
ExtractSmallFlagsMask = 7;
|
|||
|
{The flags masks for medium and large blocks}
|
|||
|
DropMediumAndLargeFlagsMask = -16;
|
|||
|
ExtractMediumAndLargeFlagsMask = 15;
|
|||
|
{-------------Block resizing constants---------------}
|
|||
|
SmallBlockDownsizeCheckAdder = 64;
|
|||
|
SmallBlockUpsizeAdder = 32;
|
|||
|
{When a medium block is reallocated to a size smaller than this, then it must
|
|||
|
be reallocated to a small block and the data moved. If not, then it is
|
|||
|
shrunk in place down to MinimumMediumBlockSize. Currently the limit is set
|
|||
|
at a quarter of the minimum medium block size.}
|
|||
|
MediumInPlaceDownsizeLimit = MinimumMediumBlockSize div 4;
|
|||
|
{-------------Memory leak reporting constants---------------}
|
|||
|
ExpectedMemoryLeaksListSize = 64 * 1024;
|
|||
|
{-------------FullDebugMode constants---------------}
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{The stack trace depth}
|
|||
|
StackTraceDepth = 9;
|
|||
|
{The number of entries in the allocation group stack}
|
|||
|
AllocationGroupStackSize = 1000;
|
|||
|
{The number of fake VMT entries - used to track virtual method calls on
|
|||
|
freed objects. Do not change this value without also updating TFreedObject.GetVirtualMethodIndex}
|
|||
|
MaxFakeVMTEntries = 200;
|
|||
|
{The pattern used to fill unused memory}
|
|||
|
DebugFillByte = $80;
|
|||
|
DebugFillDWord = $01010101 * Cardinal(DebugFillByte);
|
|||
|
{The address that is reserved so that accesses to the address of the fill
|
|||
|
pattern will result in an A/V}
|
|||
|
DebugReservedAddress = $01010000 * Cardinal(DebugFillByte);
|
|||
|
{$endif}
|
|||
|
{-------------Other constants---------------}
|
|||
|
{Sleep time when a resource (small/medium/large block manager) is in use}
|
|||
|
InitialSleepTime = 0;
|
|||
|
{Used when the resource is still in use after the first sleep}
|
|||
|
AdditionalSleepTime = 10;
|
|||
|
{Hexadecimal characters}
|
|||
|
HexTable: array[0..15] of char = '0123456789ABCDEF';
|
|||
|
{Copyright message - not used anywhere in the code}
|
|||
|
Copyright: string = 'FastMM4 <20> 2004, 2005, 2006 Pierre le Riche / Professional Software Development';
|
|||
|
|
|||
|
{-------------------------Private types----------------------------}
|
|||
|
type
|
|||
|
|
|||
|
{$ifdef Delphi4or5}
|
|||
|
{Delphi 5 Compatibility}
|
|||
|
PCardinal = ^Cardinal;
|
|||
|
PPointer = ^Pointer;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Move procedure type}
|
|||
|
TMoveProc = procedure(const ASource; var ADest; ACount: Integer);
|
|||
|
|
|||
|
{Registers structure (for GetCPUID)}
|
|||
|
TRegisters = record
|
|||
|
RegEAX, RegEBX, RegECX, RegEDX: Integer;
|
|||
|
end;
|
|||
|
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
{Different kinds of memory leaks}
|
|||
|
TMemoryLeakType = (mltUnexpectedLeak, mltExpectedLeakRegisteredByPointer,
|
|||
|
mltExpectedLeakRegisteredByClass, mltExpectedLeakRegisteredBySize);
|
|||
|
{$endif}
|
|||
|
|
|||
|
{---------------Small block structures-------------}
|
|||
|
|
|||
|
{Pointer to the header of a small block pool}
|
|||
|
PSmallBlockPoolHeader = ^TSmallBlockPoolHeader;
|
|||
|
|
|||
|
{Small block type (Size = 32)}
|
|||
|
PSmallBlockType = ^TSmallBlockType;
|
|||
|
TSmallBlockType = packed record
|
|||
|
{True = Block type is locked}
|
|||
|
BlockTypeLocked: boolean;
|
|||
|
{Bitmap indicating which of the first 8 medium block groups contain blocks
|
|||
|
of a suitable size for a block pool.}
|
|||
|
AllowedGroupsForBlockPoolBitmap: byte;
|
|||
|
{The block size for this block type}
|
|||
|
BlockSize: Word;
|
|||
|
{The first partially free pool for the given small block type (offset = +4
|
|||
|
for typecast compatibility with TSmallBlockPoolHeader). This is a circular
|
|||
|
buffer.}
|
|||
|
NextPartiallyFreePool: PSmallBlockPoolHeader;
|
|||
|
{The offset of the last block that was served sequentially (0ffset = +8 to
|
|||
|
to be at the same offset as the "FirstFreeBlock" of TSmallBlockPoolHeader}
|
|||
|
NextSequentialFeedBlockAddress: Pointer;
|
|||
|
{The last block that can be served sequentially. Offset is at +12 to be
|
|||
|
at the same address as the "BlocksInUse" field of TSmallBlockPoolHeader}
|
|||
|
MaxSequentialFeedBlockAddress: Pointer;
|
|||
|
{The pool that is current being used to serve blocks in sequential order}
|
|||
|
CurrentSequentialFeedPool: PSmallBlockPoolHeader;
|
|||
|
{The previous partially free pool for the small block type (offset = +20
|
|||
|
for typecast compatibility with TSmallBlockPoolHeader)}
|
|||
|
PreviousPartiallyFreePool: PSmallBlockPoolHeader;
|
|||
|
{The minimum and optimal size of a small block pool for this block type}
|
|||
|
MinimumBlockPoolSize: Word;
|
|||
|
OptimalBlockPoolSize: Word;
|
|||
|
{$ifdef UseCustomFixedSizeMoveRoutines}
|
|||
|
{The fixed size move procedure used to move data for this block size when
|
|||
|
it is upsized. When a block is downsized (which usually does not occur
|
|||
|
that often) the variable size move routine is used.}
|
|||
|
UpsizeMoveProcedure: TMoveProc;
|
|||
|
{$else}
|
|||
|
Reserved1: Cardinal;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
{Small block pool (Size = 32 bytes)}
|
|||
|
TSmallBlockPoolHeader = packed record
|
|||
|
{BlockType}
|
|||
|
BlockType: PSmallBlockType;
|
|||
|
{The next pool that has free blocks of this size. Must be at offset +4
|
|||
|
to be typecast compatible with TSmallBlockType}
|
|||
|
NextPartiallyFreePool: PSmallBlockPoolHeader;
|
|||
|
{Pointer to the first free block inside this pool. Must be at offset + 8
|
|||
|
to be at the same offset as "NextSequentialFeedBlockAddress" of
|
|||
|
TSmallBlockType}
|
|||
|
FirstFreeBlock: Pointer;
|
|||
|
{The number of blocks allocated in this pool. Must be at offset + 12
|
|||
|
to be at the same offset as "MaxSequentialFeedBlockAddress" of
|
|||
|
TSmallBlockType}
|
|||
|
BlocksInUse: Cardinal;
|
|||
|
{Reserved}
|
|||
|
Reserved1: Cardinal;
|
|||
|
{The previous pool that has free blocks of this size. Must be at offset +20
|
|||
|
to be compatible with TSmallBlockType}
|
|||
|
PreviousPartiallyFreePool: PSmallBlockPoolHeader;
|
|||
|
{Reserved}
|
|||
|
Reserved2: Cardinal;
|
|||
|
{The pool pointer and flags of the first block}
|
|||
|
FirstBlockPoolPointerAndFlags: Cardinal;
|
|||
|
end;
|
|||
|
|
|||
|
{Small block layout:
|
|||
|
Offset: -4 = Flags + address of the small block pool
|
|||
|
Offset: BlockSize - 4 = Flags + address of the small block pool for the next small block
|
|||
|
}
|
|||
|
|
|||
|
{----------------------Medium block structures----------------------}
|
|||
|
|
|||
|
{The medium block pool from which medium blocks are drawn}
|
|||
|
PMediumBlockPoolHeader = ^TMediumBlockPoolHeader;
|
|||
|
TMediumBlockPoolHeader = packed record
|
|||
|
{Points to the previous and next medium block pools. This circular linked
|
|||
|
list is used to track memory leaks on program shutdown.}
|
|||
|
PreviousMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
NextMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
{Unused dword}
|
|||
|
Reserved: Cardinal;
|
|||
|
{The block size and flags of the first medium block in the block pool}
|
|||
|
FirstMediumBlockSizeAndFlags: Cardinal;
|
|||
|
end;
|
|||
|
|
|||
|
{Medium block layout:
|
|||
|
Offset: -8 = Previous Block Size (only if the previous block is free)
|
|||
|
Offset: -4 = This block size and flags
|
|||
|
Offset: 0 = User data / Previous Free Block (if this block is free)
|
|||
|
Offset: 4 = Next Free Block (if this block is free)
|
|||
|
Offset: BlockSize - 8 = Size of this block (if this block is free)
|
|||
|
Offset: BlockSize - 4 = Size of the next block and flags
|
|||
|
|
|||
|
{A medium block that is unused}
|
|||
|
PMediumFreeBlock = ^TMediumFreeBlock;
|
|||
|
TMediumFreeBlock = packed record
|
|||
|
PreviousFreeBlock: PMediumFreeBlock;
|
|||
|
NextFreeBlock: PMediumFreeBlock;
|
|||
|
end;
|
|||
|
|
|||
|
{-------------------------Large block structures--------------------}
|
|||
|
|
|||
|
{Large block header record (size = 16)}
|
|||
|
PLargeBlockHeader = ^TLargeBlockHeader;
|
|||
|
TLargeBlockHeader = packed record
|
|||
|
{Points to the previous and next large blocks. This circular linked
|
|||
|
list is used to track memory leaks on program shutdown.}
|
|||
|
PreviousLargeBlockHeader: PLargeBlockHeader;
|
|||
|
NextLargeBlockHeader: PLargeBlockHeader;
|
|||
|
{The user allocated size of the Large block}
|
|||
|
UserAllocatedSize: Cardinal;
|
|||
|
{The size of this block plus the flags}
|
|||
|
BlockSizeAndFlags: Cardinal;
|
|||
|
end;
|
|||
|
|
|||
|
{-------------------------Expected Memory Leak Structures--------------------}
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
|
|||
|
{The layout of an expected leak. All fields may not be specified, in which
|
|||
|
case it may be harder to determine which leaks are expected and which are
|
|||
|
not.}
|
|||
|
PExpectedMemoryLeak = ^TExpectedMemoryLeak;
|
|||
|
PPExpectedMemoryLeak = ^PExpectedMemoryLeak;
|
|||
|
TExpectedMemoryLeak = packed record
|
|||
|
{Linked list pointers}
|
|||
|
PreviousLeak, NextLeak: PExpectedMemoryLeak;
|
|||
|
{Information about the expected leak}
|
|||
|
LeakAddress: Pointer;
|
|||
|
LeakedClass: TClass;
|
|||
|
LeakSize: Integer;
|
|||
|
LeakCount: Integer;
|
|||
|
end;
|
|||
|
|
|||
|
TExpectedMemoryLeaks = packed record
|
|||
|
{The number of entries used in the expected leaks buffer}
|
|||
|
EntriesUsed: Integer;
|
|||
|
{Freed entries}
|
|||
|
FirstFreeSlot: PExpectedMemoryLeak;
|
|||
|
{Entries with the address specified}
|
|||
|
FirstEntryByAddress: PExpectedMemoryLeak;
|
|||
|
{Entries with no address specified, but with the class specified}
|
|||
|
FirstEntryByClass: PExpectedMemoryLeak;
|
|||
|
{Entries with only size specified}
|
|||
|
FirstEntryBySizeOnly: PExpectedMemoryLeak;
|
|||
|
{The expected leaks buffer}
|
|||
|
ExpectedLeaks: packed array[0..(ExpectedMemoryLeaksListSize - 20) div SizeOf(TExpectedMemoryLeak) - 1] of TExpectedMemoryLeak;
|
|||
|
end;
|
|||
|
PExpectedMemoryLeaks = ^TExpectedMemoryLeaks;
|
|||
|
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-------------------------Full Debug Mode Structures--------------------}
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
|
|||
|
PStackTrace = ^TStackTrace;
|
|||
|
TStackTrace = array[0..StackTraceDepth - 1] of Cardinal;
|
|||
|
|
|||
|
TBlockOperation = (boBlockCheck, boGetMem, boFreeMem, boReallocMem);
|
|||
|
|
|||
|
PFullDebugBlockHeader = ^TFullDebugBlockHeader;
|
|||
|
TFullDebugBlockHeader = packed record
|
|||
|
{Space used by the medium block manager for previous/next block management.
|
|||
|
If a medium block is binned then these two dwords will be modified.}
|
|||
|
Reserved1: Cardinal;
|
|||
|
Reserved2: Cardinal;
|
|||
|
{Is the block currently allocated?}
|
|||
|
BlockInUse: LongBool;
|
|||
|
{The allocation group: Can be used in the debugging process to group
|
|||
|
related memory leaks together}
|
|||
|
AllocationGroup: Cardinal;
|
|||
|
{The allocation number: All new allocations are numbered sequentially. This
|
|||
|
number may be useful in memory leak analysis. If it reaches 4GB it wraps
|
|||
|
back to 0.}
|
|||
|
AllocationNumber: Cardinal;
|
|||
|
{The call stack when the block was allocated}
|
|||
|
AllocationStackTrace: TStackTrace;
|
|||
|
{The call stack when the block was freed}
|
|||
|
FreeStackTrace: TStackTrace;
|
|||
|
{The user requested size for the block. 0 if this is the first time the
|
|||
|
block is used.}
|
|||
|
UserSize: Cardinal;
|
|||
|
{The object class this block was used for the previous time it was
|
|||
|
allocated. When a block is freed, the dword that would normally be in the
|
|||
|
space of the class pointer is copied here, so if it is detected that
|
|||
|
the block was used after being freed we have an idea what class it is.}
|
|||
|
PreviouslyUsedByClass: Cardinal;
|
|||
|
{The sum of all the dwords excluding reserved dwords.}
|
|||
|
HeaderCheckSum: Cardinal;
|
|||
|
end;
|
|||
|
{The last four bytes of the actual allocated block is the inverse of the
|
|||
|
header checksum}
|
|||
|
|
|||
|
{The class used to catch attempts to execute a virtual method of a freed
|
|||
|
object}
|
|||
|
TFreedObject = class
|
|||
|
public
|
|||
|
procedure GetVirtualMethodIndex;
|
|||
|
procedure VirtualMethodError;
|
|||
|
{$ifdef CatchUseOfFreedInterfaces}
|
|||
|
procedure InterfaceError;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-------------------------Private constants----------------------------}
|
|||
|
const
|
|||
|
{$ifndef BCB6OrDelphi7AndUp}
|
|||
|
reInvalidPtr = 2;
|
|||
|
{$endif}
|
|||
|
{The size of the block header in front of small and medium blocks}
|
|||
|
BlockHeaderSize = 4;
|
|||
|
{The size of a small block pool header}
|
|||
|
SmallBlockPoolHeaderSize = SizeOf(TSmallBlockPoolHeader);
|
|||
|
{The size of a medium block pool header}
|
|||
|
MediumBlockPoolHeaderSize = SizeOf(TMediumBlockPoolHeader);
|
|||
|
{The size of the header in front of Large blocks}
|
|||
|
LargeBlockHeaderSize = SizeOf(TLargeBlockHeader);
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{We need space for the header. 4 bytes for the trailer and 4 bytes for the
|
|||
|
trailing block size when then block is free}
|
|||
|
FullDebugBlockOverhead = SizeOf(TFullDebugBlockHeader) + 2 * SizeOf(Pointer);
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-------------------------Private variables----------------------------}
|
|||
|
var
|
|||
|
{-----------------Small block management------------------}
|
|||
|
{The small block types. Sizes include the leading 4-byte overhead. Sizes are
|
|||
|
picked to limit maximum wastage to about 10% or 256 bytes (whichever is
|
|||
|
less) where possible.}
|
|||
|
SmallBlockTypes: packed array[0..NumSmallBlockTypes - 1] of TSmallBlockType =(
|
|||
|
{8/16 byte jumps}
|
|||
|
(BlockSize: 16 {$ifdef UseCustomFixedSizeMoveRoutines}; UpsizeMoveProcedure: Move12{$endif}),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 24 {$ifdef UseCustomFixedSizeMoveRoutines}; UpsizeMoveProcedure: Move20{$endif}),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 32 {$ifdef UseCustomFixedSizeMoveRoutines}; UpsizeMoveProcedure: Move28{$endif}),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 40 {$ifdef UseCustomFixedSizeMoveRoutines}; UpsizeMoveProcedure: Move36{$endif}),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 48 {$ifdef UseCustomFixedSizeMoveRoutines}; UpsizeMoveProcedure: Move44{$endif}),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 56 {$ifdef UseCustomFixedSizeMoveRoutines}; UpsizeMoveProcedure: Move52{$endif}),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 64 {$ifdef UseCustomFixedSizeMoveRoutines}; UpsizeMoveProcedure: Move60{$endif}),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 72 {$ifdef UseCustomFixedSizeMoveRoutines}; UpsizeMoveProcedure: Move68{$endif}),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 80),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 88),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 96),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 104),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 112),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 120),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 128),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 136),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 144),
|
|||
|
{$ifndef Align16Bytes}
|
|||
|
(BlockSize: 152),
|
|||
|
{$endif}
|
|||
|
(BlockSize: 160),
|
|||
|
{16 byte jumps}
|
|||
|
(BlockSize: 176),
|
|||
|
(BlockSize: 192),
|
|||
|
(BlockSize: 208),
|
|||
|
(BlockSize: 224),
|
|||
|
(BlockSize: 240),
|
|||
|
(BlockSize: 256),
|
|||
|
(BlockSize: 272),
|
|||
|
(BlockSize: 288),
|
|||
|
(BlockSize: 304),
|
|||
|
(BlockSize: 320),
|
|||
|
{32 byte jumps}
|
|||
|
(BlockSize: 352),
|
|||
|
(BlockSize: 384),
|
|||
|
(BlockSize: 416),
|
|||
|
(BlockSize: 448),
|
|||
|
(BlockSize: 480),
|
|||
|
{48 byte jumps}
|
|||
|
(BlockSize: 528),
|
|||
|
(BlockSize: 576),
|
|||
|
(BlockSize: 624),
|
|||
|
(BlockSize: 672),
|
|||
|
{64 byte jumps}
|
|||
|
(BlockSize: 736),
|
|||
|
(BlockSize: 800),
|
|||
|
{80 byte jumps}
|
|||
|
(BlockSize: 880),
|
|||
|
(BlockSize: 960),
|
|||
|
{96 byte jumps}
|
|||
|
(BlockSize: 1056),
|
|||
|
(BlockSize: 1152),
|
|||
|
{112 byte jumps}
|
|||
|
(BlockSize: 1264),
|
|||
|
(BlockSize: 1376),
|
|||
|
{128 byte jumps}
|
|||
|
(BlockSize: 1504),
|
|||
|
{144 byte jumps}
|
|||
|
(BlockSize: 1648),
|
|||
|
{160 byte jumps}
|
|||
|
(BlockSize: 1808),
|
|||
|
{176 byte jumps}
|
|||
|
(BlockSize: 1984),
|
|||
|
{192 byte jumps}
|
|||
|
(BlockSize: 2176),
|
|||
|
{208 byte jumps}
|
|||
|
(BlockSize: 2384),
|
|||
|
{224 byte jumps}
|
|||
|
(BlockSize: MaximumSmallBlockSize),
|
|||
|
{The last block size occurs three times. If, during a GetMem call, the
|
|||
|
requested block size is already locked by another thread then up to two
|
|||
|
larger block sizes may be used instead. Having the last block size occur
|
|||
|
three times avoids the need to have a size overflow check.}
|
|||
|
(BlockSize: MaximumSmallBlockSize),
|
|||
|
(BlockSize: MaximumSmallBlockSize));
|
|||
|
{Size to small block type translation table}
|
|||
|
AllocSize2SmallBlockTypeIndX4: packed array[0..(MaximumSmallBlockSize - 1) div SmallBlockGranularity] of byte;
|
|||
|
{-----------------Medium block management------------------}
|
|||
|
{A dummy medium block pool header: Maintains a circular list of all medium
|
|||
|
block pools to enable memory leak detection on program shutdown.}
|
|||
|
MediumBlockPoolsCircularList: TMediumBlockPoolHeader;
|
|||
|
{Are medium blocks locked?}
|
|||
|
MediumBlocksLocked: boolean;
|
|||
|
{The sequential feed medium block pool.}
|
|||
|
LastSequentiallyFedMediumBlock: Pointer;
|
|||
|
MediumSequentialFeedBytesLeft: Cardinal;
|
|||
|
{The medium block bins are divided into groups of 32 bins. If a bit
|
|||
|
is set in this group bitmap, then at least one bin in the group has free
|
|||
|
blocks.}
|
|||
|
MediumBlockBinGroupBitmap: Cardinal;
|
|||
|
{The medium block bins: total of 32 * 32 = 1024 bins of a certain
|
|||
|
minimum size.}
|
|||
|
MediumBlockBinBitmaps: packed array[0..MediumBlockBinGroupCount - 1] of Cardinal;
|
|||
|
{The medium block bins. There are 1024 LIFO circular linked lists each
|
|||
|
holding blocks of a specified minimum size. The sizes vary in size from
|
|||
|
MinimumMediumBlockSize to MaximumMediumBlockSize. The bins are treated as
|
|||
|
type TMediumFreeBlock to avoid pointer checks.}
|
|||
|
MediumBlockBins: packed array[0..MediumBlockBinCount - 1] of TMediumFreeBlock;
|
|||
|
{-----------------Large block management------------------}
|
|||
|
{Are large blocks locked?}
|
|||
|
LargeBlocksLocked: boolean;
|
|||
|
{A dummy large block header: Maintains a list of all allocated large blocks
|
|||
|
to enable memory leak detection on program shutdown.}
|
|||
|
LargeBlocksCircularList: TLargeBlockHeader;
|
|||
|
{-------------------------Expected Memory Leak Structures--------------------}
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
{The expected memory leaks}
|
|||
|
ExpectedMemoryLeaks: PExpectedMemoryLeaks;
|
|||
|
ExpectedMemoryLeaksListLocked: Boolean;
|
|||
|
{$endif}
|
|||
|
{---------------------Full Debug Mode structures--------------------}
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{The allocation group stack}
|
|||
|
AllocationGroupStack: array[0..AllocationGroupStackSize - 1] of Cardinal;
|
|||
|
{The allocation group stack top (it is an index into AllocationGroupStack)}
|
|||
|
AllocationGroupStackTop: Cardinal;
|
|||
|
{The last allocation number used}
|
|||
|
CurrentAllocationNumber: Cardinal;
|
|||
|
{The current log file name}
|
|||
|
MMLogFileName: array[0..1023] of char;
|
|||
|
{The 64K block of reserved memory used to trap invalid memory accesses using
|
|||
|
fields in a freed object.}
|
|||
|
ReservedBlock: Pointer;
|
|||
|
{The virtual method index count - used to get the virtual method index for a
|
|||
|
virtual method call on a freed object.}
|
|||
|
VMIndex: Integer;
|
|||
|
{The fake VMT used to catch virtual method calls on freed objects.}
|
|||
|
FreedObjectVMT: packed record
|
|||
|
VMTData: array[vmtSelfPtr .. vmtParent + 3] of byte;
|
|||
|
VMTMethods: array[4 + vmtParent .. MaxFakeVMTEntries * 4 + vmtParent + 3] of Byte;
|
|||
|
end;
|
|||
|
{$ifdef CatchUseOfFreedInterfaces}
|
|||
|
VMTBadInterface: array[0..MaxFakeVMTEntries - 1] of Pointer;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{--------------Other info--------------}
|
|||
|
{The memory manager that was replaced}
|
|||
|
OldMemoryManager: {$ifndef BDS2006AndUp}TMemoryManager{$else}TMemoryManagerEx{$endif};
|
|||
|
{The replacement memory manager}
|
|||
|
NewMemoryManager: {$ifndef BDS2006AndUp}TMemoryManager{$else}TMemoryManagerEx{$endif};
|
|||
|
{$ifdef DetectMMOperationsAfterUninstall}
|
|||
|
{Invalid handlers to catch MM operations after uninstall}
|
|||
|
InvalidMemoryManager: {$ifndef BDS2006AndUp}TMemoryManager{$else}TMemoryManagerEx{$endif} = (
|
|||
|
GetMem: InvalidGetMem;
|
|||
|
FreeMem: InvalidFreeMem;
|
|||
|
ReallocMem: InvalidReallocMem
|
|||
|
{$ifdef BDS2006AndUp};
|
|||
|
AllocMem: InvalidAllocMem;
|
|||
|
RegisterExpectedMemoryLeak: InvalidRegisterAndUnRegisterMemoryLeak;
|
|||
|
UnRegisterExpectedMemoryLeak: InvalidRegisterAndUnRegisterMemoryLeak;
|
|||
|
{$endif}
|
|||
|
);
|
|||
|
{$endif}
|
|||
|
|
|||
|
{A string uniquely identifying the current process (for sharing the memory
|
|||
|
manager between DLLs and the main application)}
|
|||
|
UniqueProcessIDString: String[20] = '????????_PID_FastMM'#0;
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
UniqueProcessIDStringBE: String[23] = '????????_PID_FastMM_BE'#0;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifdef ShareMM}
|
|||
|
{$ifndef Linux}
|
|||
|
{The handle of the MM window}
|
|||
|
MMWindow: HWND;
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
{The handle of the MM window (for default MM of Delphi 2006 compatibility)}
|
|||
|
MMWindowBE: HWND;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{Has FastMM been installed?}
|
|||
|
FastMMIsInstalled: Boolean;
|
|||
|
{Is the MM in place a shared memory manager?}
|
|||
|
IsMemoryManagerOwner: Boolean;
|
|||
|
{Must MMX be used for move operations?}
|
|||
|
{$ifdef EnableMMX}
|
|||
|
{$ifndef ForceMMX}
|
|||
|
UseMMX: Boolean;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{----------------Utility Functions------------------}
|
|||
|
|
|||
|
{$ifdef EnableMMX}
|
|||
|
{$ifndef ForceMMX}
|
|||
|
{Returns true if the CPUID instruction is supported}
|
|||
|
function CPUID_Supported: Boolean;
|
|||
|
asm
|
|||
|
pushfd
|
|||
|
pop eax
|
|||
|
mov edx, eax
|
|||
|
xor eax, $200000
|
|||
|
push eax
|
|||
|
popfd
|
|||
|
pushfd
|
|||
|
pop eax
|
|||
|
xor eax, edx
|
|||
|
setnz al
|
|||
|
end;
|
|||
|
|
|||
|
{Gets the CPUID}
|
|||
|
function GetCPUID(AInfoRequired: Integer): TRegisters;
|
|||
|
asm
|
|||
|
push ebx
|
|||
|
push esi
|
|||
|
mov esi, edx
|
|||
|
{cpuid instruction}
|
|||
|
{$ifdef Delphi4or5}
|
|||
|
db $0f, $a2
|
|||
|
{$else}
|
|||
|
cpuid
|
|||
|
{$endif}
|
|||
|
{Save registers}
|
|||
|
mov TRegisters[esi].RegEAX, eax
|
|||
|
mov TRegisters[esi].RegEBX, ebx
|
|||
|
mov TRegisters[esi].RegECX, ecx
|
|||
|
mov TRegisters[esi].RegEDX, edx
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
end;
|
|||
|
|
|||
|
{Returns true if the CPU supports MMX}
|
|||
|
function MMX_Supported: Boolean;
|
|||
|
var
|
|||
|
LReg: TRegisters;
|
|||
|
begin
|
|||
|
if CPUID_Supported then
|
|||
|
begin
|
|||
|
{Get the CPUID}
|
|||
|
LReg := GetCPUID(1);
|
|||
|
{Bit 23 must be set for MMX support}
|
|||
|
Result := LReg.RegEDX and $800000 <> 0;
|
|||
|
end
|
|||
|
else
|
|||
|
Result := False;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Compare [AAddress], CompareVal:
|
|||
|
If Equal: [AAddress] := NewVal and result = CompareVal
|
|||
|
If Unequal: Result := [AAddress]}
|
|||
|
function LockCmpxchg(CompareVal, NewVal: byte; AAddress: PByte): Byte;
|
|||
|
asm
|
|||
|
{On entry:
|
|||
|
al = CompareVal,
|
|||
|
dl = NewVal,
|
|||
|
ecx = AAddress}
|
|||
|
{$ifndef LINUX}
|
|||
|
lock cmpxchg [ecx], dl
|
|||
|
{$else}
|
|||
|
{Workaround for Kylix compiler bug}
|
|||
|
db $F0, $0F, $B0, $11
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
{$ifndef AsmVersion}
|
|||
|
{Gets the first set bit and resets it, returning the bit index}
|
|||
|
function FindFirstSetBit(ACardinal: Cardinal): Cardinal;
|
|||
|
asm
|
|||
|
{On entry:
|
|||
|
eax = ACardinal}
|
|||
|
bsf eax, eax
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Copies the name of the module followed by the given string to the buffer,
|
|||
|
returning the pointer following the buffer.}
|
|||
|
function AppendStringToModuleName(AString, ABuffer: PChar): PChar;
|
|||
|
var
|
|||
|
LModuleNameLength: Cardinal;
|
|||
|
LCopyStart: PChar;
|
|||
|
begin
|
|||
|
{Get the name of the application}
|
|||
|
LModuleNameLength := GetModuleFileName(0, ABuffer, 512);
|
|||
|
{Replace the last few characters}
|
|||
|
if LModuleNameLength > 0 then
|
|||
|
begin
|
|||
|
{Find the last backslash}
|
|||
|
LCopyStart := PChar(Cardinal(ABuffer) + LModuleNameLength - 1);
|
|||
|
LModuleNameLength := 0;
|
|||
|
while (Cardinal(LCopyStart) >= Cardinal(ABuffer))
|
|||
|
and (LCopyStart^ <> '\') do
|
|||
|
begin
|
|||
|
Inc(LModuleNameLength);
|
|||
|
Dec(LCopyStart);
|
|||
|
end;
|
|||
|
{Copy the name to the start of the buffer}
|
|||
|
Inc(LCopyStart);
|
|||
|
System.Move(LCopyStart^, ABuffer^, LModuleNameLength);
|
|||
|
Inc(ABuffer, LModuleNameLength);
|
|||
|
ABuffer^ := ':';
|
|||
|
Inc(ABuffer);
|
|||
|
ABuffer^ := ' ';
|
|||
|
Inc(ABuffer);
|
|||
|
end;
|
|||
|
{Append the string}
|
|||
|
while AString^ <> #0 do
|
|||
|
begin
|
|||
|
ABuffer^ := AString^;
|
|||
|
Inc(ABuffer);
|
|||
|
{Next char}
|
|||
|
Inc(AString);
|
|||
|
end;
|
|||
|
ABuffer^ := #0;
|
|||
|
Result := ABuffer;
|
|||
|
end;
|
|||
|
|
|||
|
{----------------Faster Move Procedures-------------------}
|
|||
|
|
|||
|
{Fixed size move operations ignore the size parameter. All moves are assumed to
|
|||
|
be non-overlapping.}
|
|||
|
|
|||
|
procedure Move12(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
mov ecx, [eax]
|
|||
|
mov [edx], ecx
|
|||
|
mov ecx, [eax + 4]
|
|||
|
mov eax, [eax + 8]
|
|||
|
mov [edx + 4], ecx
|
|||
|
mov [edx + 8], eax
|
|||
|
end;
|
|||
|
|
|||
|
procedure Move20(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
mov ecx, [eax]
|
|||
|
mov [edx], ecx
|
|||
|
mov ecx, [eax + 4]
|
|||
|
mov [edx + 4], ecx
|
|||
|
mov ecx, [eax + 8]
|
|||
|
mov [edx + 8], ecx
|
|||
|
mov ecx, [eax + 12]
|
|||
|
mov eax, [eax + 16]
|
|||
|
mov [edx + 12], ecx
|
|||
|
mov [edx + 16], eax
|
|||
|
end;
|
|||
|
|
|||
|
procedure Move28(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
mov ecx, [eax]
|
|||
|
mov [edx], ecx
|
|||
|
mov ecx, [eax + 4]
|
|||
|
mov [edx + 4], ecx
|
|||
|
mov ecx, [eax + 8]
|
|||
|
mov [edx + 8], ecx
|
|||
|
mov ecx, [eax + 12]
|
|||
|
mov [edx + 12], ecx
|
|||
|
mov ecx, [eax + 16]
|
|||
|
mov [edx + 16], ecx
|
|||
|
mov ecx, [eax + 20]
|
|||
|
mov eax, [eax + 24]
|
|||
|
mov [edx + 20], ecx
|
|||
|
mov [edx + 24], eax
|
|||
|
end;
|
|||
|
|
|||
|
procedure Move36(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
fild qword ptr [eax]
|
|||
|
fild qword ptr [eax + 8]
|
|||
|
fild qword ptr [eax + 16]
|
|||
|
fild qword ptr [eax + 24]
|
|||
|
mov ecx, [eax + 32]
|
|||
|
mov [edx + 32], ecx
|
|||
|
fistp qword ptr [edx + 24]
|
|||
|
fistp qword ptr [edx + 16]
|
|||
|
fistp qword ptr [edx + 8]
|
|||
|
fistp qword ptr [edx]
|
|||
|
end;
|
|||
|
|
|||
|
procedure Move44(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
fild qword ptr [eax]
|
|||
|
fild qword ptr [eax + 8]
|
|||
|
fild qword ptr [eax + 16]
|
|||
|
fild qword ptr [eax + 24]
|
|||
|
fild qword ptr [eax + 32]
|
|||
|
mov ecx, [eax + 40]
|
|||
|
mov [edx + 40], ecx
|
|||
|
fistp qword ptr [edx + 32]
|
|||
|
fistp qword ptr [edx + 24]
|
|||
|
fistp qword ptr [edx + 16]
|
|||
|
fistp qword ptr [edx + 8]
|
|||
|
fistp qword ptr [edx]
|
|||
|
end;
|
|||
|
|
|||
|
procedure Move52(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
fild qword ptr [eax]
|
|||
|
fild qword ptr [eax + 8]
|
|||
|
fild qword ptr [eax + 16]
|
|||
|
fild qword ptr [eax + 24]
|
|||
|
fild qword ptr [eax + 32]
|
|||
|
fild qword ptr [eax + 40]
|
|||
|
mov ecx, [eax + 48]
|
|||
|
mov [edx + 48], ecx
|
|||
|
fistp qword ptr [edx + 40]
|
|||
|
fistp qword ptr [edx + 32]
|
|||
|
fistp qword ptr [edx + 24]
|
|||
|
fistp qword ptr [edx + 16]
|
|||
|
fistp qword ptr [edx + 8]
|
|||
|
fistp qword ptr [edx]
|
|||
|
end;
|
|||
|
|
|||
|
procedure Move60(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
fild qword ptr [eax]
|
|||
|
fild qword ptr [eax + 8]
|
|||
|
fild qword ptr [eax + 16]
|
|||
|
fild qword ptr [eax + 24]
|
|||
|
fild qword ptr [eax + 32]
|
|||
|
fild qword ptr [eax + 40]
|
|||
|
fild qword ptr [eax + 48]
|
|||
|
mov ecx, [eax + 56]
|
|||
|
mov [edx + 56], ecx
|
|||
|
fistp qword ptr [edx + 48]
|
|||
|
fistp qword ptr [edx + 40]
|
|||
|
fistp qword ptr [edx + 32]
|
|||
|
fistp qword ptr [edx + 24]
|
|||
|
fistp qword ptr [edx + 16]
|
|||
|
fistp qword ptr [edx + 8]
|
|||
|
fistp qword ptr [edx]
|
|||
|
end;
|
|||
|
|
|||
|
procedure Move68(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
fild qword ptr [eax]
|
|||
|
fild qword ptr [eax + 8]
|
|||
|
fild qword ptr [eax + 16]
|
|||
|
fild qword ptr [eax + 24]
|
|||
|
fild qword ptr [eax + 32]
|
|||
|
fild qword ptr [eax + 40]
|
|||
|
fild qword ptr [eax + 48]
|
|||
|
fild qword ptr [eax + 56]
|
|||
|
mov ecx, [eax + 64]
|
|||
|
mov [edx + 64], ecx
|
|||
|
fistp qword ptr [edx + 56]
|
|||
|
fistp qword ptr [edx + 48]
|
|||
|
fistp qword ptr [edx + 40]
|
|||
|
fistp qword ptr [edx + 32]
|
|||
|
fistp qword ptr [edx + 24]
|
|||
|
fistp qword ptr [edx + 16]
|
|||
|
fistp qword ptr [edx + 8]
|
|||
|
fistp qword ptr [edx]
|
|||
|
end;
|
|||
|
|
|||
|
{Variable size move procedure: Assumes ACount is 4 less than a multiple of 16.
|
|||
|
Always moves at least 12 bytes, irrespective of ACount.}
|
|||
|
procedure MoveX16L4(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
{Make the counter negative based: The last 12 bytes are moved separately}
|
|||
|
sub ecx, 12
|
|||
|
add eax, ecx
|
|||
|
add edx, ecx
|
|||
|
{$ifdef EnableMMX}
|
|||
|
{$ifndef ForceMMX}
|
|||
|
cmp UseMMX, True
|
|||
|
jne @FPUMove
|
|||
|
{$endif}
|
|||
|
{Make the counter negative based: The last 12 bytes are moved separately}
|
|||
|
neg ecx
|
|||
|
jns @MMXMoveLast12
|
|||
|
@MMXMoveLoop:
|
|||
|
{Move a 16 byte block}
|
|||
|
{$ifdef Delphi4or5}
|
|||
|
{Delphi 5 compatibility}
|
|||
|
db $0f, $6f, $04, $01
|
|||
|
db $0f, $6f, $4c, $01, $08
|
|||
|
db $0f, $7f, $04, $11
|
|||
|
db $0f, $7f, $4c, $11, $08
|
|||
|
{$else}
|
|||
|
movq mm0, [eax + ecx]
|
|||
|
movq mm1, [eax + ecx + 8]
|
|||
|
movq [edx + ecx], mm0
|
|||
|
movq [edx + ecx + 8], mm1
|
|||
|
{$endif}
|
|||
|
{Are there another 16 bytes to move?}
|
|||
|
add ecx, 16
|
|||
|
js @MMXMoveLoop
|
|||
|
@MMXMoveLast12:
|
|||
|
{Do the last 12 bytes}
|
|||
|
{$ifdef Delphi4or5}
|
|||
|
{Delphi 5 compatibility}
|
|||
|
db $0f, $6f, $04, $01
|
|||
|
{$else}
|
|||
|
movq mm0, [eax + ecx]
|
|||
|
{$endif}
|
|||
|
mov eax, [eax + ecx + 8]
|
|||
|
{$ifdef Delphi4or5}
|
|||
|
{Delphi 5 compatibility}
|
|||
|
db $0f, $7f, $04, $11
|
|||
|
{$else}
|
|||
|
movq [edx + ecx], mm0
|
|||
|
{$endif}
|
|||
|
mov [edx + ecx + 8], eax
|
|||
|
{Exit MMX state}
|
|||
|
{$ifdef Delphi4or5}
|
|||
|
{Delphi 5 compatibility}
|
|||
|
db $0f, $77
|
|||
|
{$else}
|
|||
|
emms
|
|||
|
{$endif}
|
|||
|
{$ifndef ForceMMX}
|
|||
|
ret
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{FPU code is only used if MMX is not forced}
|
|||
|
{$ifndef ForceMMX}
|
|||
|
@FPUMove:
|
|||
|
neg ecx
|
|||
|
jns @FPUMoveLast12
|
|||
|
@FPUMoveLoop:
|
|||
|
{Move a 16 byte block}
|
|||
|
fild qword ptr [eax + ecx]
|
|||
|
fild qword ptr [eax + ecx + 8]
|
|||
|
fistp qword ptr [edx + ecx + 8]
|
|||
|
fistp qword ptr [edx + ecx]
|
|||
|
{Are there another 16 bytes to move?}
|
|||
|
add ecx, 16
|
|||
|
js @FPUMoveLoop
|
|||
|
@FPUMoveLast12:
|
|||
|
{Do the last 12 bytes}
|
|||
|
fild qword ptr [eax + ecx]
|
|||
|
fistp qword ptr [edx + ecx]
|
|||
|
mov eax, [eax + ecx + 8]
|
|||
|
mov [edx + ecx + 8], eax
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
{Variable size move procedure: Assumes ACount is 4 less than a multiple of 8.
|
|||
|
Always moves at least 12 bytes, irrespective of ACount.}
|
|||
|
procedure MoveX8L4(const ASource; var ADest; ACount: Integer);
|
|||
|
asm
|
|||
|
{Make the counter negative based: The last 4 bytes are moved separately}
|
|||
|
sub ecx, 4
|
|||
|
add eax, ecx
|
|||
|
add edx, ecx
|
|||
|
neg ecx
|
|||
|
{$ifdef EnableMMX}
|
|||
|
{$ifndef ForceMMX}
|
|||
|
cmp UseMMX, True
|
|||
|
jne @FPUMoveLoop
|
|||
|
{$endif}
|
|||
|
@MMXMoveLoop:
|
|||
|
{Move an 8 byte block}
|
|||
|
{$ifdef Delphi4or5}
|
|||
|
{Delphi 5 compatibility}
|
|||
|
db $0f, $6f, $04, $01
|
|||
|
db $0f, $7f, $04, $11
|
|||
|
{$else}
|
|||
|
movq mm0, [eax + ecx]
|
|||
|
movq [edx + ecx], mm0
|
|||
|
{$endif}
|
|||
|
{Are there another 8 bytes to move?}
|
|||
|
add ecx, 8
|
|||
|
js @MMXMoveLoop
|
|||
|
{Exit MMX state}
|
|||
|
{$ifdef Delphi4or5}
|
|||
|
{Delphi 5 compatibility}
|
|||
|
db $0f, $77
|
|||
|
{$else}
|
|||
|
emms
|
|||
|
{$endif}
|
|||
|
{Do the last 4 bytes}
|
|||
|
mov eax, [eax + ecx]
|
|||
|
mov [edx + ecx], eax
|
|||
|
{$ifndef ForceMMX}
|
|||
|
ret
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{FPU code is only used if MMX is not forced}
|
|||
|
{$ifndef ForceMMX}
|
|||
|
@FPUMoveLoop:
|
|||
|
{Move an 8 byte block}
|
|||
|
fild qword ptr [eax + ecx]
|
|||
|
fistp qword ptr [edx + ecx]
|
|||
|
{Are there another 8 bytes to move?}
|
|||
|
add ecx, 8
|
|||
|
js @FPUMoveLoop
|
|||
|
{Do the last 4 bytes}
|
|||
|
mov eax, [eax + ecx]
|
|||
|
mov [edx + ecx], eax
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
{----------------Windows Emulation Functions for Kylix Support-----------------}
|
|||
|
|
|||
|
{$ifdef Linux}
|
|||
|
|
|||
|
const
|
|||
|
{Messagebox constants}
|
|||
|
MB_OK = 0;
|
|||
|
MB_ICONERROR = $10;
|
|||
|
MB_TASKMODAL = $2000;
|
|||
|
{Virtual memory constants}
|
|||
|
MEM_COMMIT = $1000;
|
|||
|
MEM_RELEASE = $8000;
|
|||
|
MEM_TOP_DOWN = $100000;
|
|||
|
PAGE_READWRITE = 4;
|
|||
|
|
|||
|
procedure MessageBox(hWnd: Cardinal; AMessageText, AMessageTitle: PChar; uType: Cardinal); stdcall;
|
|||
|
begin
|
|||
|
writeln(AMessageText);
|
|||
|
end;
|
|||
|
|
|||
|
function VirtualAlloc(lpvAddress: Pointer; dwSize, flAllocationType, flProtect: Cardinal): Pointer; stdcall;
|
|||
|
begin
|
|||
|
Result := valloc(dwSize);
|
|||
|
end;
|
|||
|
|
|||
|
function VirtualFree(lpAddress: Pointer; dwSize, dwFreeType: Cardinal): LongBool; stdcall;
|
|||
|
begin
|
|||
|
free(lpAddress);
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
procedure Sleep(dwMilliseconds: Cardinal); stdcall;
|
|||
|
begin
|
|||
|
{Convert to microseconds (more or less)}
|
|||
|
usleep(dwMilliseconds shl 10);
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-----------------Debugging Support Functions and Procedures------------------}
|
|||
|
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{The stack trace procedure. The stack trace module is external since it may
|
|||
|
raise handled access violations that result in the creation of exception
|
|||
|
objects and the stack trace code is not re-entrant.}
|
|||
|
procedure GetStackTrace(AReturnAddresses: PCardinal;
|
|||
|
AMaxDepth, ASkipFrames: Cardinal); external FullDebugModeLibraryName
|
|||
|
name {$ifdef RawStackTraces}'GetRawStackTrace'{$else}'GetFrameBasedStackTrace'{$endif};
|
|||
|
{The exported procedure in the FastMM_FullDebugMode.dll library used to convert
|
|||
|
the return addresses of a stack trace to a text string.}
|
|||
|
function LogStackTrace(AReturnAddresses: PCardinal;
|
|||
|
AMaxDepth: Cardinal; ABuffer: PChar): PChar; external FullDebugModeLibraryName
|
|||
|
name 'LogStackTrace';
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef Linux}
|
|||
|
function DelphiIsRunning: boolean;
|
|||
|
begin
|
|||
|
Result := FindWindow('TAppBuilder', nil) <> 0;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Converts a cardinal to string at the buffer location, returning the new
|
|||
|
buffer position.}
|
|||
|
function CardinalToStrBuf(ACardinal: Cardinal; ABuffer: PChar): PChar;
|
|||
|
asm
|
|||
|
{On entry: eax = ACardinal, edx = ABuffer}
|
|||
|
push edi
|
|||
|
mov edi, edx //Pointer to the first character in edi
|
|||
|
//Calculate leading digit: divide the number by 1e9
|
|||
|
add eax, 1 //Increment the number
|
|||
|
mov edx, $89705F41 //1e9 reciprocal
|
|||
|
mul edx //Multplying with reciprocal
|
|||
|
shr eax, 30 //Save fraction bits
|
|||
|
mov ecx, edx //First digit in bits <31:29>
|
|||
|
and edx, $1FFFFFFF //Filter fraction part edx<28:0>
|
|||
|
shr ecx, 29 //Get leading digit into accumulator
|
|||
|
lea edx, [edx+4*edx] //Calculate ...
|
|||
|
add edx, eax //... 5*fraction
|
|||
|
mov eax, ecx //Copy leading digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #2
|
|||
|
mov eax, edx //Point format such that 1.0 = 2^28
|
|||
|
cmp ecx, 1 //Any non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 28 //Next digit
|
|||
|
and edx, $0fffffff //Fraction part edx<27:0>
|
|||
|
or ecx, eax //Accumulate next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #3
|
|||
|
lea eax, [edx*4+edx] //5*fraction, new digit eax<31:27>
|
|||
|
lea edx, [edx*4+edx] //5*fraction, new fraction edx<26:0>
|
|||
|
cmp ecx, 1 //Any non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 27 //Next digit
|
|||
|
and edx, $07ffffff //Fraction part
|
|||
|
or ecx, eax //Accumulate next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #4
|
|||
|
lea eax, [edx*4+edx] //5*fraction, new digit eax<31:26>
|
|||
|
lea edx, [edx*4+edx] //5*fraction, new fraction edx<25:0>
|
|||
|
cmp ecx, 1 //Any non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 26 //Next digit
|
|||
|
and edx, $03ffffff //Fraction part
|
|||
|
or ecx, eax //Accumulate next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #5
|
|||
|
lea eax, [edx*4+edx] //5*fraction, new digit eax<31:25>
|
|||
|
lea edx, [edx*4+edx] //5*fraction, new fraction edx<24:0>
|
|||
|
cmp ecx, 1 //Any non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 25 //Next digit
|
|||
|
and edx, $01ffffff //Fraction part
|
|||
|
or ecx, eax //Accumulate next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #6
|
|||
|
lea eax, [edx*4+edx] //5*fraction, new digit eax<31:24>
|
|||
|
lea edx, [edx*4+edx] //5*fraction, new fraction edx<23:0>
|
|||
|
cmp ecx, 1 //Any non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 24 //Next digit
|
|||
|
and edx, $00ffffff //Fraction part
|
|||
|
or ecx, eax //Accumulate next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #7
|
|||
|
lea eax, [edx*4+edx] //5*fraction, new digit eax<31:23>
|
|||
|
lea edx, [edx*4+edx] //5*fraction, new fraction edx<31:23>
|
|||
|
cmp ecx, 1 //Any non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 23 //Next digit
|
|||
|
and edx, $007fffff //Fraction part
|
|||
|
or ecx, eax //Accumulate next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #8
|
|||
|
lea eax, [edx*4+edx] //5*fraction, new digit eax<31:22>
|
|||
|
lea edx, [edx*4+edx] //5*fraction, new fraction edx<22:0>
|
|||
|
cmp ecx, 1 //Any non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 22 //Next digit
|
|||
|
and edx, $003fffff //Fraction part
|
|||
|
or ecx, eax //Accumulate next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #9
|
|||
|
lea eax, [edx*4+edx] //5*fraction, new digit eax<31:21>
|
|||
|
lea edx, [edx*4+edx] //5*fraction, new fraction edx<21:0>
|
|||
|
cmp ecx, 1 //Any non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 21 //Next digit
|
|||
|
and edx, $001fffff //Fraction part
|
|||
|
or ecx, eax //Accumulate next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store digit out to memory
|
|||
|
//Calculate digit #10
|
|||
|
lea eax, [edx*4+edx] //5*fraction, new digit eax<31:20>
|
|||
|
cmp ecx, 1 //Any-non-zero digit yet ?
|
|||
|
sbb edi, -1 //Yes->increment ptr, No->keep old ptr
|
|||
|
shr eax, 20 //Next digit
|
|||
|
or eax, '0' //Convert digit to ASCII
|
|||
|
mov [edi], al //Store last digit and end marker out to memory
|
|||
|
{Return a pointer to the next character}
|
|||
|
lea eax, [edi + 1]
|
|||
|
{Restore edi}
|
|||
|
pop edi
|
|||
|
end;
|
|||
|
|
|||
|
{Converts a cardinal to a hexadecimal string at the buffer location, returning
|
|||
|
the new buffer position.}
|
|||
|
function CardinalToHexBuf(ACardinal: integer; ABuffer: PChar): PChar;
|
|||
|
asm
|
|||
|
{On entry:
|
|||
|
eax = ACardinal
|
|||
|
edx = ABuffer}
|
|||
|
push ebx
|
|||
|
push edi
|
|||
|
{Save ACardinal in ebx}
|
|||
|
mov ebx, eax
|
|||
|
{Get a pointer to the first character in edi}
|
|||
|
mov edi, edx
|
|||
|
{Get the number in ecx as well}
|
|||
|
mov ecx, eax
|
|||
|
{Keep the low nibbles in ebx and the high nibbles in ecx}
|
|||
|
and ebx, $0f0f0f0f
|
|||
|
and ecx, $f0f0f0f0
|
|||
|
{Swap the bytes into the right order}
|
|||
|
ror ebx, 16
|
|||
|
ror ecx, 20
|
|||
|
{Get nibble 7}
|
|||
|
movzx eax, ch
|
|||
|
mov dl, ch
|
|||
|
mov al, byte ptr HexTable[eax]
|
|||
|
mov [edi], al
|
|||
|
cmp dl, 1
|
|||
|
sbb edi, -1
|
|||
|
{Get nibble 6}
|
|||
|
movzx eax, bh
|
|||
|
or dl, bh
|
|||
|
mov al, byte ptr HexTable[eax]
|
|||
|
mov [edi], al
|
|||
|
cmp dl, 1
|
|||
|
sbb edi, -1
|
|||
|
{Get nibble 5}
|
|||
|
movzx eax, cl
|
|||
|
or dl, cl
|
|||
|
mov al, byte ptr HexTable[eax]
|
|||
|
mov [edi], al
|
|||
|
cmp dl, 1
|
|||
|
sbb edi, -1
|
|||
|
{Get nibble 4}
|
|||
|
movzx eax, bl
|
|||
|
or dl, bl
|
|||
|
mov al, byte ptr HexTable[eax]
|
|||
|
mov [edi], al
|
|||
|
cmp dl, 1
|
|||
|
sbb edi, -1
|
|||
|
{Rotate ecx and ebx so we get access to the rest}
|
|||
|
shr ebx, 16
|
|||
|
shr ecx, 16
|
|||
|
{Get nibble 3}
|
|||
|
movzx eax, ch
|
|||
|
or dl, ch
|
|||
|
mov al, byte ptr HexTable[eax]
|
|||
|
mov [edi], al
|
|||
|
cmp dl, 1
|
|||
|
sbb edi, -1
|
|||
|
{Get nibble 2}
|
|||
|
movzx eax, bh
|
|||
|
or dl, bh
|
|||
|
mov al, byte ptr HexTable[eax]
|
|||
|
mov [edi], al
|
|||
|
cmp dl, 1
|
|||
|
sbb edi, -1
|
|||
|
{Get nibble 1}
|
|||
|
movzx eax, cl
|
|||
|
or dl, cl
|
|||
|
mov al, byte ptr HexTable[eax]
|
|||
|
mov [edi], al
|
|||
|
cmp dl, 1
|
|||
|
sbb edi, -1
|
|||
|
{Get nibble 0}
|
|||
|
movzx eax, bl
|
|||
|
mov al, byte ptr HexTable[eax]
|
|||
|
mov [edi], al
|
|||
|
{Return a pointer to the end of the string}
|
|||
|
lea eax, [edi + 1]
|
|||
|
{Restore registers}
|
|||
|
pop edi
|
|||
|
pop ebx
|
|||
|
end;
|
|||
|
|
|||
|
{Appends the source text to the destination and returns the new destination
|
|||
|
position}
|
|||
|
function AppendStringToBuffer(const ASource, ADestination: PChar; ACount: Cardinal): PChar;
|
|||
|
begin
|
|||
|
System.Move(ASource^, ADestination^, ACount);
|
|||
|
Result := Pointer(Cardinal(ADestination) + ACount);
|
|||
|
end;
|
|||
|
|
|||
|
{Returns the class for a memory block. Returns nil if it is not a valid class}
|
|||
|
function GetObjectClass(APointer: Pointer): TClass;
|
|||
|
{$ifndef Linux}
|
|||
|
var
|
|||
|
LMemInfo: TMemoryBasicInformation;
|
|||
|
|
|||
|
function InternalIsValidClass(APossibleClass: Pointer; ADepth: Integer = 0): Boolean;
|
|||
|
var
|
|||
|
LParentClass: Pointer;
|
|||
|
begin
|
|||
|
{Do we need to recheck the VM?}
|
|||
|
if (Cardinal(LMemInfo.BaseAddress) > (Cardinal(APossibleClass) + Cardinal(vmtSelfPtr)))
|
|||
|
or ((Cardinal(LMemInfo.BaseAddress) + LMemInfo.RegionSize) < (Cardinal(APossibleClass) + Cardinal(vmtParent + 3))) then
|
|||
|
begin
|
|||
|
{Get the VM status for the pointer}
|
|||
|
VirtualQuery(Pointer(Cardinal(APossibleClass) + Cardinal(vmtSelfPtr)), LMemInfo,
|
|||
|
SizeOf(LMemInfo));
|
|||
|
end;
|
|||
|
{Get the result, while checking for recursion}
|
|||
|
Result := (ADepth < 1000)
|
|||
|
{The required info must fit inside the region}
|
|||
|
and ((Cardinal(LMemInfo.BaseAddress) + LMemInfo.RegionSize) > (Cardinal(APossibleClass) + Cardinal(vmtParent + 3)))
|
|||
|
{Memory must be committed}
|
|||
|
and (LMemInfo.State = MEM_COMMIT)
|
|||
|
{Memory must be readable}
|
|||
|
and (LMemInfo.Protect and
|
|||
|
(PAGE_READONLY or PAGE_READWRITE or PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY) <> 0)
|
|||
|
and (LMemInfo.Protect and PAGE_GUARD = 0)
|
|||
|
{All class fields must fit inside the block}
|
|||
|
{The self pointer must be valid}
|
|||
|
and (PPointer(Cardinal(APossibleClass) + Cardinal(vmtSelfPtr))^ = APossibleClass);
|
|||
|
{Check the parent class}
|
|||
|
if Result then
|
|||
|
begin
|
|||
|
LParentClass := PPointer(Cardinal(APossibleClass) + Cardinal(vmtParent))^;
|
|||
|
{The parent must also be a valid class}
|
|||
|
Result := (LParentClass = nil) or
|
|||
|
InternalIsValidClass(Pointer(Cardinal(LParentClass) - Cardinal(vmtSelfPtr)), ADepth + 1)
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
{Get the class pointer from the (suspected) object}
|
|||
|
Result := TClass(PCardinal(APointer)^);
|
|||
|
{No VM info yet}
|
|||
|
LMemInfo.RegionSize := 0;
|
|||
|
{Check the block}
|
|||
|
if (Cardinal(Result) < 65536)
|
|||
|
or (not InternalIsValidClass(Result, 0)) then
|
|||
|
begin
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$else}
|
|||
|
begin
|
|||
|
{Not currently supported under Linux}
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Fills a block of memory with the given dword. Always fills a multiple of 4 bytes}
|
|||
|
procedure FillDWord(var AAddress; AByteCount: integer; ADWordFillValue: Cardinal);
|
|||
|
asm
|
|||
|
{On Entry: eax = AAddress
|
|||
|
edx = AByteCount
|
|||
|
ecx = ADWordFillValue}
|
|||
|
add eax, edx
|
|||
|
neg edx
|
|||
|
jns @Done
|
|||
|
@FillLoop:
|
|||
|
mov [eax + edx], ecx
|
|||
|
add edx, 4
|
|||
|
js @FillLoop
|
|||
|
@Done:
|
|||
|
end;
|
|||
|
|
|||
|
{Gets the available size inside a block}
|
|||
|
function GetAvailableSpaceInBlock(APointer: Pointer): Cardinal;
|
|||
|
var
|
|||
|
LBlockHeader: Cardinal;
|
|||
|
LPSmallBlockPool: PSmallBlockPoolHeader;
|
|||
|
begin
|
|||
|
LBlockHeader := PCardinal(Cardinal(APointer) - 4)^;
|
|||
|
if LBlockHeader and (IsMediumBlockFlag or IsLargeBlockFlag) = 0 then
|
|||
|
begin
|
|||
|
LPSmallBlockPool := PSmallBlockPoolHeader(LBlockHeader and DropSmallFlagsMask);
|
|||
|
Result := LPSmallBlockPool.BlockType.BlockSize - BlockHeaderSize;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
Result := (LBlockHeader and DropMediumAndLargeFlagsMask) - BlockHeaderSize;
|
|||
|
if (LBlockHeader and IsMediumBlockFlag) = 0 then
|
|||
|
Dec(Result, LargeBlockHeaderSize);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{-----------------Small Block Management------------------}
|
|||
|
|
|||
|
{Locks all small block types}
|
|||
|
procedure LockAllSmallBlockTypes;
|
|||
|
var
|
|||
|
LInd: Cardinal;
|
|||
|
begin
|
|||
|
{Lock the medium blocks}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
if IsMultiThread then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
for LInd := 0 to NumSmallBlockTypes - 1 do
|
|||
|
begin
|
|||
|
while LockCmpxchg(0, 1, @SmallBlockTypes[LInd].BlockTypeLocked) <> 0 do
|
|||
|
begin
|
|||
|
Sleep(InitialSleepTime);
|
|||
|
if LockCmpxchg(0, 1, @SmallBlockTypes[LInd].BlockTypeLocked) = 0 then
|
|||
|
break;
|
|||
|
Sleep(AdditionalSleepTime);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Gets the first and last block pointer for a small block pool}
|
|||
|
procedure GetFirstAndLastSmallBlockInPool(APSmallBlockPool: PSmallBlockPoolHeader;
|
|||
|
var AFirstPtr, ALastPtr: Pointer);
|
|||
|
var
|
|||
|
LBlockSize: Cardinal;
|
|||
|
begin
|
|||
|
{Get the pointer to the first block}
|
|||
|
AFirstPtr := Pointer(Cardinal(APSmallBlockPool) + SmallBlockPoolHeaderSize);
|
|||
|
{Get a pointer to the last block}
|
|||
|
if (APSmallBlockPool.BlockType.CurrentSequentialFeedPool <> APSmallBlockPool)
|
|||
|
or (Cardinal(APSmallBlockPool.BlockType.NextSequentialFeedBlockAddress) > Cardinal(APSmallBlockPool.BlockType.MaxSequentialFeedBlockAddress)) then
|
|||
|
begin
|
|||
|
{Not the sequential feed - point to the end of the block}
|
|||
|
LBlockSize := PCardinal(Cardinal(APSmallBlockPool) - 4)^ and DropMediumAndLargeFlagsMask;
|
|||
|
ALastPtr := Pointer(Cardinal(APSmallBlockPool) + LBlockSize - APSmallBlockPool.BlockType.BlockSize);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{The sequential feed pool - point to before the next sequential feed block}
|
|||
|
ALastPtr := Pointer(Cardinal(APSmallBlockPool.BlockType.NextSequentialFeedBlockAddress) - 1);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{-----------------Medium Block Management------------------}
|
|||
|
|
|||
|
{Advances to the next medium block. Returns nil if the end of the medium block
|
|||
|
pool has been reached}
|
|||
|
function NextMediumBlock(APMediumBlock: Pointer): Pointer;
|
|||
|
var
|
|||
|
LBlockSize: Cardinal;
|
|||
|
begin
|
|||
|
{Get the size of this block}
|
|||
|
LBlockSize := PCardinal(Cardinal(APMediumBlock) - 4)^ and DropMediumAndLargeFlagsMask;
|
|||
|
{Advance the pointer}
|
|||
|
Result := Pointer(Cardinal(APMediumBlock) + LBlockSize);
|
|||
|
{Is the next block the end of medium pool marker?}
|
|||
|
LBlockSize := PCardinal(Cardinal(Result) - 4)^ and DropMediumAndLargeFlagsMask;
|
|||
|
if LBlockSize = 0 then
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
|
|||
|
{Gets the first medium block in the medium block pool}
|
|||
|
function GetFirstMediumBlockInPool(APMediumBlockPoolHeader: PMediumBlockPoolHeader): Pointer;
|
|||
|
begin
|
|||
|
if (MediumSequentialFeedBytesLeft = 0)
|
|||
|
or (Cardinal(LastSequentiallyFedMediumBlock) < Cardinal(APMediumBlockPoolHeader))
|
|||
|
or (Cardinal(LastSequentiallyFedMediumBlock) > Cardinal(APMediumBlockPoolHeader) + MediumBlockPoolSize) then
|
|||
|
begin
|
|||
|
Result := Pointer(Cardinal(APMediumBlockPoolHeader) + MediumBlockPoolHeaderSize);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Is the sequential feed pool empty?}
|
|||
|
if MediumSequentialFeedBytesLeft <> MediumBlockPoolSize - MediumBlockPoolHeaderSize then
|
|||
|
Result := LastSequentiallyFedMediumBlock
|
|||
|
else
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Locks the medium blocks}
|
|||
|
procedure LockMediumBlocks;
|
|||
|
begin
|
|||
|
{Lock the medium blocks}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
if IsMultiThread then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
while LockCmpxchg(0, 1, @MediumBlocksLocked) <> 0 do
|
|||
|
begin
|
|||
|
Sleep(InitialSleepTime);
|
|||
|
if LockCmpxchg(0, 1, @MediumBlocksLocked) = 0 then
|
|||
|
break;
|
|||
|
Sleep(AdditionalSleepTime);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{$ifndef AsmVersion}
|
|||
|
{Removes a medium block from the circular linked list of free blocks.
|
|||
|
Does not change any header flags. Medium blocks should be locked
|
|||
|
before calling this procedure.}
|
|||
|
procedure RemoveMediumFreeBlock(APMediumFreeBlock: PMediumFreeBlock);
|
|||
|
var
|
|||
|
LPreviousFreeBlock, LNextFreeBlock: PMediumFreeBlock;
|
|||
|
LBinNumber, LBinGroupNumber: Cardinal;
|
|||
|
begin
|
|||
|
{Get the current previous and next blocks}
|
|||
|
LNextFreeBlock := APMediumFreeBlock.NextFreeBlock;
|
|||
|
LPreviousFreeBlock := APMediumFreeBlock.PreviousFreeBlock;
|
|||
|
{Remove this block from the linked list}
|
|||
|
LPreviousFreeBlock.NextFreeBlock := LNextFreeBlock;
|
|||
|
LNextFreeBlock.PreviousFreeBlock := LPreviousFreeBlock;
|
|||
|
{Is this bin now empty? If the previous and next free block pointers are
|
|||
|
equal, they must point to the bin.}
|
|||
|
if LPreviousFreeBlock = LNextFreeBlock then
|
|||
|
begin
|
|||
|
{Get the bin number for this block size}
|
|||
|
LBinNumber := (Cardinal(LNextFreeBlock) - Cardinal(@MediumBlockBins)) div SizeOf(TMediumFreeBlock);
|
|||
|
LBinGroupNumber := LBinNumber div 32;
|
|||
|
{Flag this bin as empty}
|
|||
|
MediumBlockBinBitmaps[LBinGroupNumber] := MediumBlockBinBitmaps[LBinGroupNumber]
|
|||
|
and (not (1 shl (LBinNumber and 31)));
|
|||
|
{Is the group now entirely empty?}
|
|||
|
if MediumBlockBinBitmaps[LBinGroupNumber] = 0 then
|
|||
|
begin
|
|||
|
{Flag this group as empty}
|
|||
|
MediumBlockBinGroupBitmap := MediumBlockBinGroupBitmap
|
|||
|
and (not (1 shl LBinGroupNumber));
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$else}
|
|||
|
{Removes a medium block from the circular linked list of free blocks.
|
|||
|
Does not change any header flags. Medium blocks should be locked
|
|||
|
before calling this procedure.}
|
|||
|
procedure RemoveMediumFreeBlock(APMediumFreeBlock: PMediumFreeBlock);
|
|||
|
asm
|
|||
|
{On entry: eax = APMediumFreeBlock}
|
|||
|
{Get the current previous and next blocks}
|
|||
|
mov ecx, TMediumFreeBlock[eax].NextFreeBlock
|
|||
|
mov edx, TMediumFreeBlock[eax].PreviousFreeBlock
|
|||
|
{Is this bin now empty? If the previous and next free block pointers are
|
|||
|
equal, they must point to the bin.}
|
|||
|
cmp ecx, edx
|
|||
|
{Remove this block from the linked list}
|
|||
|
mov TMediumFreeBlock[ecx].PreviousFreeBlock, edx
|
|||
|
mov TMediumFreeBlock[edx].NextFreeBlock, ecx
|
|||
|
{Is this bin now empty? If the previous and next free block pointers are
|
|||
|
equal, they must point to the bin.}
|
|||
|
je @BinIsNowEmpty
|
|||
|
@Done:
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
@BinIsNowEmpty:
|
|||
|
{Get the bin number for this block size in ecx}
|
|||
|
sub ecx, offset MediumBlockBins
|
|||
|
mov edx, ecx
|
|||
|
shr ecx, 3
|
|||
|
{Get the group number in edx}
|
|||
|
movzx edx, dh
|
|||
|
{Flag this bin as empty}
|
|||
|
mov eax, -2
|
|||
|
rol eax, cl
|
|||
|
and dword ptr [MediumBlockBinBitmaps + edx * 4], eax
|
|||
|
jnz @Done
|
|||
|
{Flag this group as empty}
|
|||
|
mov eax, -2
|
|||
|
mov ecx, edx
|
|||
|
rol eax, cl
|
|||
|
and MediumBlockBinGroupBitmap, eax
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef AsmVersion}
|
|||
|
{Inserts a medium block into the appropriate medium block bin.}
|
|||
|
procedure InsertMediumBlockIntoBin(APMediumFreeBlock: PMediumFreeBlock; AMediumBlockSize: Cardinal);
|
|||
|
var
|
|||
|
LBinNumber, LBinGroupNumber: Cardinal;
|
|||
|
LPBin, LPFirstFreeBlock: PMediumFreeBlock;
|
|||
|
begin
|
|||
|
{Get the bin number for this block size. Get the bin that holds blocks of at
|
|||
|
least this size.}
|
|||
|
LBinNumber := (AMediumBlockSize - MinimumMediumBlockSize) div MediumBlockGranularity;
|
|||
|
if LBinNumber >= MediumBlockBinCount then
|
|||
|
LBinNumber := MediumBlockBinCount - 1;
|
|||
|
{Get the bin}
|
|||
|
LPBin := @MediumBlockBins[LBinNumber];
|
|||
|
{Bins are LIFO, se we insert this block as the first free block in the bin}
|
|||
|
LPFirstFreeBlock := LPBin.NextFreeBlock;
|
|||
|
APMediumFreeBlock.PreviousFreeBlock := LPBin;
|
|||
|
APMediumFreeBlock.NextFreeBlock := LPFirstFreeBlock;
|
|||
|
LPFirstFreeBlock.PreviousFreeBlock := APMediumFreeBlock;
|
|||
|
LPBin.NextFreeBlock := APMediumFreeBlock;
|
|||
|
{Was this bin empty?}
|
|||
|
if LPFirstFreeBlock = LPBin then
|
|||
|
begin
|
|||
|
{Get the group number}
|
|||
|
LBinGroupNumber := LBinNumber div 32;
|
|||
|
{Flag this bin as used}
|
|||
|
MediumBlockBinBitmaps[LBinGroupNumber] := MediumBlockBinBitmaps[LBinGroupNumber]
|
|||
|
or (1 shl (LBinNumber and 31));
|
|||
|
{Flag the group as used}
|
|||
|
MediumBlockBinGroupBitmap := MediumBlockBinGroupBitmap
|
|||
|
or (1 shl LBinGroupNumber);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$else}
|
|||
|
{Inserts a medium block into the appropriate medium block bin.}
|
|||
|
procedure InsertMediumBlockIntoBin(APMediumFreeBlock: PMediumFreeBlock; AMediumBlockSize: Cardinal);
|
|||
|
asm
|
|||
|
{On entry: eax = APMediumFreeBlock, edx = AMediumBlockSize}
|
|||
|
{Get the bin number for this block size. Get the bin that holds blocks of at
|
|||
|
least this size.}
|
|||
|
sub edx, MinimumMediumBlockSize
|
|||
|
shr edx, 8
|
|||
|
{Validate the bin number}
|
|||
|
sub edx, MediumBlockBinCount - 1
|
|||
|
sbb ecx, ecx
|
|||
|
and edx, ecx
|
|||
|
add edx, MediumBlockBinCount - 1
|
|||
|
{Get the bin in ecx}
|
|||
|
lea ecx, [MediumBlockBins + edx * 8]
|
|||
|
{Bins are LIFO, se we insert this block as the first free block in the bin}
|
|||
|
mov edx, TMediumFreeBlock[ecx].NextFreeBlock
|
|||
|
{Was this bin empty?}
|
|||
|
cmp edx, ecx
|
|||
|
mov TMediumFreeBlock[eax].PreviousFreeBlock, ecx
|
|||
|
mov TMediumFreeBlock[eax].NextFreeBlock, edx
|
|||
|
mov TMediumFreeBlock[edx].PreviousFreeBlock, eax
|
|||
|
mov TMediumFreeBlock[ecx].NextFreeBlock, eax
|
|||
|
{Was this bin empty?}
|
|||
|
je @BinWasEmpty
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@BinWasEmpty:
|
|||
|
{Get the bin number in ecx}
|
|||
|
sub ecx, offset MediumBlockBins
|
|||
|
mov edx, ecx
|
|||
|
shr ecx, 3
|
|||
|
{Get the group number in edx}
|
|||
|
movzx edx, dh
|
|||
|
{Flag this bin as not empty}
|
|||
|
mov eax, 1
|
|||
|
shl eax, cl
|
|||
|
or dword ptr [MediumBlockBinBitmaps + edx * 4], eax
|
|||
|
{Flag the group as not empty}
|
|||
|
mov eax, 1
|
|||
|
mov ecx, edx
|
|||
|
shl eax, cl
|
|||
|
or MediumBlockBinGroupBitmap, eax
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef AsmVersion}
|
|||
|
{Bins what remains in the current sequential feed medium block pool. Medium
|
|||
|
blocks must be locked.}
|
|||
|
procedure BinMediumSequentialFeedRemainder;
|
|||
|
var
|
|||
|
LSequentialFeedFreeSize, LNextBlockSizeAndFlags: Cardinal;
|
|||
|
LPRemainderBlock, LNextMediumBlock: Pointer;
|
|||
|
begin
|
|||
|
LSequentialFeedFreeSize := MediumSequentialFeedBytesLeft;
|
|||
|
if LSequentialFeedFreeSize > 0 then
|
|||
|
begin
|
|||
|
{Get the block after the open space}
|
|||
|
LNextMediumBlock := LastSequentiallyFedMediumBlock;
|
|||
|
LNextBlockSizeAndFlags := PCardinal(Cardinal(LNextMediumBlock) - BlockHeaderSize)^;
|
|||
|
{Point to the remainder}
|
|||
|
LPRemainderBlock := Pointer(Cardinal(LNextMediumBlock) - LSequentialFeedFreeSize);
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{Can the next block be combined with the remainder?}
|
|||
|
if (LNextBlockSizeAndFlags and IsFreeBlockFlag) <> 0 then
|
|||
|
begin
|
|||
|
{Increase the size of this block}
|
|||
|
Inc(LSequentialFeedFreeSize, LNextBlockSizeAndFlags and DropMediumAndLargeFlagsMask);
|
|||
|
{Remove the next block as well}
|
|||
|
if (LNextBlockSizeAndFlags and DropMediumAndLargeFlagsMask) >= MinimumMediumBlockSize then
|
|||
|
RemoveMediumFreeBlock(LNextMediumBlock);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
{Set the "previous block is free" flag of the next block}
|
|||
|
PCardinal(Cardinal(LNextMediumBlock) - BlockHeaderSize)^ := LNextBlockSizeAndFlags or PreviousMediumBlockIsFreeFlag;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Store the size of the block as well as the flags}
|
|||
|
PCardinal(Cardinal(LPRemainderBlock) - BlockHeaderSize)^ := LSequentialFeedFreeSize or IsMediumBlockFlag or IsFreeBlockFlag;
|
|||
|
{Store the trailing size marker}
|
|||
|
PCardinal(Cardinal(LPRemainderBlock) + LSequentialFeedFreeSize - 8)^ := LSequentialFeedFreeSize;
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{In full debug mode the sequential feed remainder will never be too small to
|
|||
|
fit a full debug header.}
|
|||
|
{Clear the user area of the block}
|
|||
|
FillDWord(Pointer(Cardinal(LPRemainderBlock) + SizeOf(TFullDebugBlockHeader) + 4)^,
|
|||
|
LSequentialFeedFreeSize - FullDebugBlockOverhead - 4,
|
|||
|
{$ifndef CatchUseOfFreedInterfaces}DebugFillDWord{$else}Cardinal(@VMTBadInterface){$endif});
|
|||
|
{We need to set a valid debug header and footer in the remainder}
|
|||
|
PFullDebugBlockHeader(LPRemainderBlock).HeaderCheckSum := Cardinal(LPRemainderBlock);
|
|||
|
PCardinal(Cardinal(LPRemainderBlock) + SizeOf(TFullDebugBlockHeader))^ := not Cardinal(LPRemainderBlock);
|
|||
|
{$endif}
|
|||
|
{Bin this medium block}
|
|||
|
if LSequentialFeedFreeSize >= MinimumMediumBlockSize then
|
|||
|
InsertMediumBlockIntoBin(LPRemainderBlock, LSequentialFeedFreeSize);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$else}
|
|||
|
{Bins what remains in the current sequential feed medium block pool. Medium
|
|||
|
blocks must be locked.}
|
|||
|
procedure BinMediumSequentialFeedRemainder;
|
|||
|
asm
|
|||
|
cmp MediumSequentialFeedBytesLeft, 0
|
|||
|
jne @MustBinMedium
|
|||
|
{Nothing to bin}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@MustBinMedium:
|
|||
|
{Get a pointer to the last sequentially allocated medium block}
|
|||
|
mov eax, LastSequentiallyFedMediumBlock
|
|||
|
{Is the block that was last fed sequentially free?}
|
|||
|
test byte ptr [eax - 4], IsFreeBlockFlag
|
|||
|
jnz @LastBlockFedIsFree
|
|||
|
{Set the "previous block is free" flag in the last block fed}
|
|||
|
or dword ptr [eax - 4], PreviousMediumBlockIsFreeFlag
|
|||
|
{Get the remainder in edx}
|
|||
|
mov edx, MediumSequentialFeedBytesLeft
|
|||
|
{Point eax to the start of the remainder}
|
|||
|
sub eax, edx
|
|||
|
@BinTheRemainder:
|
|||
|
{Status: eax = start of remainder, edx = size of remainder}
|
|||
|
{Store the size of the block as well as the flags}
|
|||
|
lea ecx, [edx + IsMediumBlockFlag + IsFreeBlockFlag]
|
|||
|
mov [eax - 4], ecx
|
|||
|
{Store the trailing size marker}
|
|||
|
mov [eax + edx - 8], edx
|
|||
|
{Bin this medium block}
|
|||
|
cmp edx, MinimumMediumBlockSize
|
|||
|
jnb InsertMediumBlockIntoBin
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@LastBlockFedIsFree:
|
|||
|
{Drop the flags}
|
|||
|
mov edx, DropMediumAndLargeFlagsMask
|
|||
|
and edx, [eax - 4]
|
|||
|
{Free the last block fed}
|
|||
|
cmp edx, MinimumMediumBlockSize
|
|||
|
jb @DontRemoveLastFed
|
|||
|
{Last fed block is free - remove it from its size bin}
|
|||
|
call RemoveMediumFreeBlock
|
|||
|
{Re-read eax and edx}
|
|||
|
mov eax, LastSequentiallyFedMediumBlock
|
|||
|
mov edx, DropMediumAndLargeFlagsMask
|
|||
|
and edx, [eax - 4]
|
|||
|
@DontRemoveLastFed:
|
|||
|
{Get the number of bytes left in ecx}
|
|||
|
mov ecx, MediumSequentialFeedBytesLeft
|
|||
|
{Point eax to the start of the remainder}
|
|||
|
sub eax, ecx
|
|||
|
{edx = total size of the remainder}
|
|||
|
add edx, ecx
|
|||
|
jmp @BinTheRemainder
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Allocates a new sequential feed medium block pool and immediately splits off a
|
|||
|
block of the requested size. The block size must be a multiple of 16 and
|
|||
|
medium blocks must be locked.}
|
|||
|
function AllocNewSequentialFeedMediumPool(AFirstBlockSize: Cardinal): Pointer;
|
|||
|
var
|
|||
|
LOldFirstMediumBlockPool: PMediumBlockPoolHeader;
|
|||
|
LNewPool: Pointer;
|
|||
|
begin
|
|||
|
{Bin the current sequential feed remainder}
|
|||
|
BinMediumSequentialFeedRemainder;
|
|||
|
{Allocate a new sequential feed block pool}
|
|||
|
LNewPool := VirtualAlloc(nil, MediumBlockPoolSize, MEM_COMMIT, PAGE_READWRITE);
|
|||
|
if LNewPool <> nil then
|
|||
|
begin
|
|||
|
{Insert this block pool into the list of block pools}
|
|||
|
LOldFirstMediumBlockPool := MediumBlockPoolsCircularList.NextMediumBlockPoolHeader;
|
|||
|
PMediumBlockPoolHeader(LNewPool).PreviousMediumBlockPoolHeader := @MediumBlockPoolsCircularList;
|
|||
|
MediumBlockPoolsCircularList.NextMediumBlockPoolHeader := LNewPool;
|
|||
|
PMediumBlockPoolHeader(LNewPool).NextMediumBlockPoolHeader := LOldFirstMediumBlockPool;
|
|||
|
LOldFirstMediumBlockPool.PreviousMediumBlockPoolHeader := LNewPool;
|
|||
|
{Store the sequential feed pool trailer}
|
|||
|
PCardinal(Cardinal(LNewPool) + MediumBlockPoolSize - BlockHeaderSize)^ := IsMediumBlockFlag;
|
|||
|
{Get the number of bytes still available}
|
|||
|
MediumSequentialFeedBytesLeft := (MediumBlockPoolSize - MediumBlockPoolHeaderSize) - AFirstBlockSize;
|
|||
|
{Get the result}
|
|||
|
Result := Pointer(Cardinal(LNewPool) + MediumBlockPoolSize - AFirstBlockSize);
|
|||
|
LastSequentiallyFedMediumBlock := Result;
|
|||
|
{Store the block header}
|
|||
|
PCardinal(Cardinal(Result) - BlockHeaderSize)^ := AFirstBlockSize or IsMediumBlockFlag;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Out of memory}
|
|||
|
MediumSequentialFeedBytesLeft := 0;
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Frees a medium block pool. Medium blocks must be locked on entry.}
|
|||
|
procedure FreeMediumBlockPool(AMediumBlockPool: PMediumBlockPoolHeader);
|
|||
|
var
|
|||
|
LPPreviousMediumBlockPoolHeader, LPNextMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
begin
|
|||
|
{Remove this medium block pool from the linked list}
|
|||
|
LPPreviousMediumBlockPoolHeader := AMediumBlockPool.PreviousMediumBlockPoolHeader;
|
|||
|
LPNextMediumBlockPoolHeader := AMediumBlockPool.NextMediumBlockPoolHeader;
|
|||
|
LPPreviousMediumBlockPoolHeader.NextMediumBlockPoolHeader := LPNextMediumBlockPoolHeader;
|
|||
|
LPNextMediumBlockPoolHeader.PreviousMediumBlockPoolHeader := LPPreviousMediumBlockPoolHeader;
|
|||
|
{Free the medium block pool}
|
|||
|
VirtualFree(AMediumBlockPool, 0, MEM_RELEASE);
|
|||
|
end;
|
|||
|
|
|||
|
{-----------------Large Block Management------------------}
|
|||
|
|
|||
|
{Locks the large blocks}
|
|||
|
procedure LockLargeBlocks;
|
|||
|
begin
|
|||
|
{Lock the large blocks}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
if IsMultiThread then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
while LockCmpxchg(0, 1, @LargeBlocksLocked) <> 0 do
|
|||
|
begin
|
|||
|
Sleep(InitialSleepTime);
|
|||
|
if LockCmpxchg(0, 1, @LargeBlocksLocked) = 0 then
|
|||
|
break;
|
|||
|
Sleep(AdditionalSleepTime);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Allocates a Large block of at least ASize (actual size may be larger to
|
|||
|
allow for alignment etc.). ASize must be the actual user requested size. This
|
|||
|
procedure will pad it to the appropriate page boundary and also add the space
|
|||
|
required by the header.}
|
|||
|
function AllocateLargeBlock(ASize: Cardinal): Pointer;
|
|||
|
var
|
|||
|
LLargeUsedBlockSize: Cardinal;
|
|||
|
LOldFirstLargeBlock: PLargeBlockHeader;
|
|||
|
begin
|
|||
|
{Pad the block size to include the header and granularity. We also add a
|
|||
|
4-byte overhead so a huge block size is a multiple of 16 bytes less 4 (so we
|
|||
|
can use a single move function for reallocating all block types)}
|
|||
|
LLargeUsedBlockSize := (ASize + LargeBlockHeaderSize + LargeBlockGranularity - 1 + BlockHeaderSize)
|
|||
|
and -LargeBlockGranularity;
|
|||
|
{Get the Large block}
|
|||
|
Result := VirtualAlloc(nil, LLargeUsedBlockSize, MEM_COMMIT or MEM_TOP_DOWN,
|
|||
|
PAGE_READWRITE);
|
|||
|
{Set the Large block fields}
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
{Set the large block size and flags}
|
|||
|
PLargeBlockHeader(Result).UserAllocatedSize := ASize;
|
|||
|
PLargeBlockHeader(Result).BlockSizeAndFlags := LLargeUsedBlockSize or IsLargeBlockFlag;
|
|||
|
{Insert the large block into the linked list of large blocks}
|
|||
|
LockLargeBlocks;
|
|||
|
LOldFirstLargeBlock := LargeBlocksCircularList.NextLargeBlockHeader;
|
|||
|
PLargeBlockHeader(Result).PreviousLargeBlockHeader := @LargeBlocksCircularList;
|
|||
|
LargeBlocksCircularList.NextLargeBlockHeader := Result;
|
|||
|
PLargeBlockHeader(Result).NextLargeBlockHeader := LOldFirstLargeBlock;
|
|||
|
LOldFirstLargeBlock.PreviousLargeBlockHeader := Result;
|
|||
|
LargeBlocksLocked := False;
|
|||
|
{Add the size of the header}
|
|||
|
Inc(Cardinal(Result), LargeBlockHeaderSize);
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{Clear the user area of the block}
|
|||
|
FillDWord(Pointer(Cardinal(Result) + SizeOf(TFullDebugBlockHeader) + 4)^,
|
|||
|
LLargeUsedBlockSize - LargeBlockHeaderSize - FullDebugBlockOverhead - 4,
|
|||
|
{$ifndef CatchUseOfFreedInterfaces}DebugFillDWord{$else}Cardinal(@VMTBadInterface){$endif});
|
|||
|
{Set the debug header and footer}
|
|||
|
PFullDebugBlockHeader(Result).HeaderCheckSum := Cardinal(Result);
|
|||
|
PCardinal(Cardinal(Result) + SizeOf(TFullDebugBlockHeader))^ := not Cardinal(Result);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Frees a large block, returning 0 on success, -1 otherwise}
|
|||
|
function FreeLargeBlock(APointer: Pointer): Integer;
|
|||
|
var
|
|||
|
LPreviousLargeBlockHeader, LNextLargeBlockHeader: PLargeBlockHeader;
|
|||
|
{$ifndef Linux}
|
|||
|
LRemainingSize: Cardinal;
|
|||
|
LCurrentSegment: Pointer;
|
|||
|
LMemInfo: TMemoryBasicInformation;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{Point to the start of the large block}
|
|||
|
APointer := Pointer(Cardinal(APointer) - LargeBlockHeaderSize);
|
|||
|
{Get the previous and next large blocks}
|
|||
|
LockLargeBlocks;
|
|||
|
LPreviousLargeBlockHeader := PLargeBlockHeader(APointer).PreviousLargeBlockHeader;
|
|||
|
LNextLargeBlockHeader := PLargeBlockHeader(APointer).NextLargeBlockHeader;
|
|||
|
{$ifndef Linux}
|
|||
|
{Is the large block segmented?}
|
|||
|
if PLargeBlockHeader(APointer).BlockSizeAndFlags and LargeBlockIsSegmented = 0 then
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
{Single segment large block: Try to free it}
|
|||
|
if VirtualFree(APointer, 0, MEM_RELEASE) then
|
|||
|
Result := 0
|
|||
|
else
|
|||
|
Result := -1;
|
|||
|
{$ifndef Linux}
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{The large block is segmented - free all segments}
|
|||
|
LCurrentSegment := APointer;
|
|||
|
LRemainingSize := PLargeBlockHeader(APointer).BlockSizeAndFlags and DropMediumAndLargeFlagsMask;
|
|||
|
Result := 0;
|
|||
|
while True do
|
|||
|
begin
|
|||
|
{Free the current segment}
|
|||
|
if not VirtualFree(LCurrentSegment, 0, MEM_RELEASE) then
|
|||
|
begin
|
|||
|
Result := -1;
|
|||
|
break;
|
|||
|
end;
|
|||
|
{Get the size of the segment that was freed}
|
|||
|
VirtualQuery(LCurrentSegment, LMemInfo, SizeOf(LMemInfo));
|
|||
|
{Done?}
|
|||
|
if LMemInfo.RegionSize >= LRemainingSize then
|
|||
|
Break;
|
|||
|
{Decrement the remaining size}
|
|||
|
Dec(LRemainingSize, LMemInfo.RegionSize);
|
|||
|
Inc(Cardinal(LCurrentSegment), LMemInfo.RegionSize);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Success?}
|
|||
|
if Result = 0 then
|
|||
|
begin
|
|||
|
{Remove the large block from the linked list}
|
|||
|
LNextLargeBlockHeader.PreviousLargeBlockHeader := LPreviousLargeBlockHeader;
|
|||
|
LPreviousLargeBlockHeader.NextLargeBlockHeader := LNextLargeBlockHeader;
|
|||
|
end;
|
|||
|
{Unlock the large blocks}
|
|||
|
LargeBlocksLocked := False;
|
|||
|
end;
|
|||
|
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{Reallocates a large block to at least the requested size. Returns the new
|
|||
|
pointer, or nil on error}
|
|||
|
function ReallocateLargeBlock(APointer: Pointer; ANewSize: Cardinal): Pointer;
|
|||
|
var
|
|||
|
LOldAvailableSize, LBlockHeader, LOldUserSize, LMinimumUpsize,
|
|||
|
LNewAllocSize: Cardinal;
|
|||
|
{$ifndef Linux}
|
|||
|
LNewSegmentSize: Cardinal;
|
|||
|
LNextSegmentPointer: Pointer;
|
|||
|
LMemInfo: TMemoryBasicInformation;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{Get the block header}
|
|||
|
LBlockHeader := PCardinal(Cardinal(APointer) - BlockHeaderSize)^;
|
|||
|
{Large block - size is (16 + 4) less than the allocated size}
|
|||
|
LOldAvailableSize := (LBlockHeader and DropMediumAndLargeFlagsMask) - (LargeBlockHeaderSize + BlockHeaderSize);
|
|||
|
{Is it an upsize or a downsize?}
|
|||
|
if Cardinal(ANewSize) > LOldAvailableSize then
|
|||
|
begin
|
|||
|
{This pointer is being reallocated to a larger block and therefore it is
|
|||
|
logical to assume that it may be enlarged again. Since reallocations are
|
|||
|
expensive, there is a minimum upsize percentage to avoid unnecessary
|
|||
|
future move operations.}
|
|||
|
{Add 25% for large block upsizes}
|
|||
|
LMinimumUpsize := Cardinal(LOldAvailableSize)
|
|||
|
+ (Cardinal(LOldAvailableSize) shr 2);
|
|||
|
if Cardinal(ANewSize) < LMinimumUpsize then
|
|||
|
LNewAllocSize := LMinimumUpsize
|
|||
|
else
|
|||
|
LNewAllocSize := ANewSize;
|
|||
|
{$ifndef Linux}
|
|||
|
{Can another large block segment be allocated directly after this segment,
|
|||
|
thus negating the need to move the data?}
|
|||
|
LNextSegmentPointer := Pointer(Cardinal(APointer) - LargeBlockHeaderSize + (LBlockHeader and DropMediumAndLargeFlagsMask));
|
|||
|
VirtualQuery(LNextSegmentPointer, LMemInfo, SizeOf(LMemInfo));
|
|||
|
if (LMemInfo.State = MEM_FREE) then
|
|||
|
begin
|
|||
|
{Round the region size to the previous 64K}
|
|||
|
LMemInfo.RegionSize := LMemInfo.RegionSize and -LargeBlockGranularity;
|
|||
|
{Enough space to grow in place?}
|
|||
|
if (LMemInfo.RegionSize > (ANewSize - LOldAvailableSize)) then
|
|||
|
begin
|
|||
|
{There is enough space after the block to extend it - determine by how
|
|||
|
much}
|
|||
|
LNewSegmentSize := (LNewAllocSize - LOldAvailableSize + LargeBlockGranularity - 1) and -LargeBlockGranularity;
|
|||
|
if LNewSegmentSize > LMemInfo.RegionSize then
|
|||
|
LNewSegmentSize := LMemInfo.RegionSize;
|
|||
|
if (VirtualAlloc(LNextSegmentPointer, LNewSegmentSize, MEM_RESERVE, PAGE_READWRITE) <> nil)
|
|||
|
and (VirtualAlloc(LNextSegmentPointer, LNewSegmentSize, MEM_COMMIT, PAGE_READWRITE) <> nil) then
|
|||
|
begin
|
|||
|
{Update the requested size}
|
|||
|
PLargeBlockHeader(Cardinal(APointer) - LargeBlockHeaderSize).UserAllocatedSize := ANewSize;
|
|||
|
PLargeBlockHeader(Cardinal(APointer) - LargeBlockHeaderSize).BlockSizeAndFlags :=
|
|||
|
(PLargeBlockHeader(Cardinal(APointer) - LargeBlockHeaderSize).BlockSizeAndFlags + LNewSegmentSize)
|
|||
|
or LargeBlockIsSegmented;
|
|||
|
{Success}
|
|||
|
Result := APointer;
|
|||
|
exit;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Could not resize in place: Allocate the new block}
|
|||
|
Result := FastGetMem(LNewAllocSize);
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
{If its a large block - store the actual user requested size (it may
|
|||
|
not be if the block that is being reallocated from was previously
|
|||
|
downsized)}
|
|||
|
if LNewAllocSize > (MaximumMediumBlockSize - BlockHeaderSize) then
|
|||
|
PLargeBlockHeader(Cardinal(Result) - LargeBlockHeaderSize).UserAllocatedSize := ANewSize;
|
|||
|
{The user allocated size is stored for large blocks}
|
|||
|
LOldUserSize := PLargeBlockHeader(Cardinal(APointer) - LargeBlockHeaderSize).UserAllocatedSize;
|
|||
|
{The number of bytes to move is the old user size.}
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
MoveX16L4(APointer^, Result^, LOldUserSize);
|
|||
|
{$else}
|
|||
|
System.Move(APointer^, Result^, LOldUserSize);
|
|||
|
{$endif}
|
|||
|
{Free the old block}
|
|||
|
FastFreeMem(APointer);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{It's a downsize: do we need to reallocate? Only if the new size is less
|
|||
|
than half the old size}
|
|||
|
if Cardinal(ANewSize) >= (LOldAvailableSize shr 1) then
|
|||
|
begin
|
|||
|
{No need to reallocate}
|
|||
|
Result := APointer;
|
|||
|
{Update the requested size}
|
|||
|
PLargeBlockHeader(Cardinal(APointer) - LargeBlockHeaderSize).UserAllocatedSize := ANewSize;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{The block is less than half the old size, and the current size is
|
|||
|
greater than the minimum block size allowing a downsize: reallocate}
|
|||
|
Result := FastGetMem(ANewSize);
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
{Still a large block? -> Set the user size}
|
|||
|
if ANewSize > (MaximumMediumBlockSize - BlockHeaderSize) then
|
|||
|
PLargeBlockHeader(Cardinal(APointer) - LargeBlockHeaderSize).UserAllocatedSize := ANewSize;
|
|||
|
{Move the data across}
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
{$ifdef Align16Bytes}
|
|||
|
MoveX16L4(APointer^, Result^, ANewSize);
|
|||
|
{$else}
|
|||
|
MoveX8L4(APointer^, Result^, ANewSize);
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
System.Move(APointer^, Result^, ANewSize);
|
|||
|
{$endif}
|
|||
|
{Free the old block}
|
|||
|
FastFreeMem(APointer);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{---------------------Replacement Memory Manager Interface---------------------}
|
|||
|
|
|||
|
{$ifndef ASMVersion}
|
|||
|
{Replacement for SysGetMem (pascal version)}
|
|||
|
function FastGetMem(ASize: Integer): Pointer;
|
|||
|
var
|
|||
|
LMediumBlock{$ifndef FullDebugMode}, LNextFreeBlock, LSecondSplit{$endif}: PMediumFreeBlock;
|
|||
|
LNextMediumBlockHeader: PCardinal;
|
|||
|
LBlockSize, LAvailableBlockSize{$ifndef FullDebugMode}, LSecondSplitSize{$endif}: Cardinal;
|
|||
|
LPSmallBlockType: PSmallBlockType;
|
|||
|
LPSmallBlockPool, LPNewFirstPool: PSmallBlockPoolHeader;
|
|||
|
LBinNumber: Cardinal;
|
|||
|
LNewFirstFreeBlock: Pointer;
|
|||
|
LPMediumBin: PMediumFreeBlock;
|
|||
|
LSequentialFeedFreeSize: Cardinal;
|
|||
|
{$ifndef FullDebugMode}LBinGroupsMasked, {$endif}LBinGroupMasked, LBinGroupNumber: Cardinal;
|
|||
|
begin
|
|||
|
{Is it a small block? -> Take the header size into account when
|
|||
|
determining the required block size}
|
|||
|
if Cardinal(ASize) <= (MaximumSmallBlockSize - BlockHeaderSize) then
|
|||
|
begin
|
|||
|
{-------------------------Allocate a small block---------------------------}
|
|||
|
{Get the block type from the size}
|
|||
|
LPSmallBlockType := PSmallBlockType(AllocSize2SmallBlockTypeIndX4[
|
|||
|
(Cardinal(ASize) + (BlockHeaderSize - 1)) div SmallBlockGranularity] * 8
|
|||
|
+ Cardinal(@SmallBlockTypes));
|
|||
|
{Lock the block type}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
if IsMultiThread then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
while True do
|
|||
|
begin
|
|||
|
{Try to lock the small block type}
|
|||
|
if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
|
|||
|
break;
|
|||
|
{Try the next block type}
|
|||
|
Inc(Cardinal(LPSmallBlockType), SizeOf(TSmallBlockType));
|
|||
|
if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
|
|||
|
break;
|
|||
|
{Try up to two sizes past the requested size}
|
|||
|
Inc(Cardinal(LPSmallBlockType), SizeOf(TSmallBlockType));
|
|||
|
if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
|
|||
|
break;
|
|||
|
{All three sizes locked - given up and sleep}
|
|||
|
Dec(Cardinal(LPSmallBlockType), 2 * SizeOf(TSmallBlockType));
|
|||
|
{Both this block type and the next is in use: sleep}
|
|||
|
Sleep(InitialSleepTime);
|
|||
|
{Try the lock again}
|
|||
|
if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
|
|||
|
break;
|
|||
|
{Sleep longer}
|
|||
|
Sleep(AdditionalSleepTime);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Get the first pool with free blocks}
|
|||
|
LPSmallBlockPool := LPSmallBlockType.NextPartiallyFreePool;
|
|||
|
{Is the pool valid?}
|
|||
|
if Cardinal(LPSmallBlockPool) <> Cardinal(LPSmallBlockType) then
|
|||
|
begin
|
|||
|
{Get the first free offset}
|
|||
|
Result := LPSmallBlockPool.FirstFreeBlock;
|
|||
|
{Get the new first free block}
|
|||
|
LNewFirstFreeBlock := PPointer(Cardinal(Result) - 4)^;
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{The block should be free}
|
|||
|
if (Cardinal(LNewFirstFreeBlock) and ExtractSmallFlagsMask) <> IsFreeBlockFlag then
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
LNewFirstFreeBlock := Pointer(Cardinal(LNewFirstFreeBlock) and DropSmallFlagsMask);
|
|||
|
{Increment the number of used blocks}
|
|||
|
Inc(LPSmallBlockPool.BlocksInUse);
|
|||
|
{Set the new first free block}
|
|||
|
LPSmallBlockPool.FirstFreeBlock := LNewFirstFreeBlock;
|
|||
|
{Is the pool now full?}
|
|||
|
if LNewFirstFreeBlock = nil then
|
|||
|
begin
|
|||
|
{Pool is full - remove it from the partially free list}
|
|||
|
LPNewFirstPool := LPSmallBlockPool.NextPartiallyFreePool;
|
|||
|
LPSmallBlockType.NextPartiallyFreePool := LPNewFirstPool;
|
|||
|
LPNewFirstPool.PreviousPartiallyFreePool := PSmallBlockPoolHeader(LPSmallBlockType);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Try to feed a small block sequentially}
|
|||
|
Result := LPSmallBlockType.NextSequentialFeedBlockAddress;
|
|||
|
{Can another block fit?}
|
|||
|
if Cardinal(Result) <= Cardinal(LPSmallBlockType.MaxSequentialFeedBlockAddress) then
|
|||
|
begin
|
|||
|
{Get the sequential feed block pool}
|
|||
|
LPSmallBlockPool := LPSmallBlockType.CurrentSequentialFeedPool;
|
|||
|
{Increment the number of used blocks in the sequential feed pool}
|
|||
|
Inc(LPSmallBlockPool.BlocksInUse);
|
|||
|
{Store the next sequential feed block address}
|
|||
|
LPSmallBlockType.NextSequentialFeedBlockAddress := Pointer(Cardinal(Result) + LPSmallBlockType.BlockSize);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Need to allocate a pool: Lock the medium blocks}
|
|||
|
LockMediumBlocks;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{Are there any available blocks of a suitable size?}
|
|||
|
LBinGroupsMasked := MediumBlockBinGroupBitmap and ($ffffff00 or LPSmallBlockType.AllowedGroupsForBlockPoolBitmap);
|
|||
|
if LBinGroupsMasked <> 0 then
|
|||
|
begin
|
|||
|
{Get the bin group with free blocks}
|
|||
|
LBinGroupNumber := FindFirstSetBit(LBinGroupsMasked);
|
|||
|
{Get the bin in the group with free blocks}
|
|||
|
LBinNumber := FindFirstSetBit(MediumBlockBinBitmaps[LBinGroupNumber])
|
|||
|
+ LBinGroupNumber * 32;
|
|||
|
LPMediumBin := @MediumBlockBins[LBinNumber];
|
|||
|
{Get the first block in the bin}
|
|||
|
LMediumBlock := LPMediumBin.NextFreeBlock;
|
|||
|
{Remove the first block from the linked list (LIFO)}
|
|||
|
LNextFreeBlock := LMediumBlock.NextFreeBlock;
|
|||
|
LPMediumBin.NextFreeBlock := LNextFreeBlock;
|
|||
|
LNextFreeBlock.PreviousFreeBlock := LPMediumBin;
|
|||
|
{Is this bin now empty?}
|
|||
|
if LNextFreeBlock = LPMediumBin then
|
|||
|
begin
|
|||
|
{Flag this bin as empty}
|
|||
|
MediumBlockBinBitmaps[LBinGroupNumber] := MediumBlockBinBitmaps[LBinGroupNumber]
|
|||
|
and (not (1 shl (LBinNumber and 31)));
|
|||
|
{Is the group now entirely empty?}
|
|||
|
if MediumBlockBinBitmaps[LBinGroupNumber] = 0 then
|
|||
|
begin
|
|||
|
{Flag this group as empty}
|
|||
|
MediumBlockBinGroupBitmap := MediumBlockBinGroupBitmap
|
|||
|
and (not (1 shl LBinGroupNumber));
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Get the size of the available medium block}
|
|||
|
LBlockSize := PCardinal(Cardinal(LMediumBlock) - BlockHeaderSize)^ and DropMediumAndLargeFlagsMask;
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{Check that this block is actually free and the next and previous blocks
|
|||
|
are both in use.}
|
|||
|
if ((PCardinal(Cardinal(LMediumBlock) - BlockHeaderSize)^ and ExtractMediumAndLargeFlagsMask) <> (IsMediumBlockFlag or IsFreeBlockFlag))
|
|||
|
or ((PCardinal(Cardinal(LMediumBlock) + (PCardinal(Cardinal(LMediumBlock) - BlockHeaderSize)^ and DropMediumAndLargeFlagsMask) - BlockHeaderSize)^ and IsFreeBlockFlag) <> 0)
|
|||
|
then
|
|||
|
begin
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Should the block be split?}
|
|||
|
if LBlockSize >= MaximumSmallBlockPoolSize then
|
|||
|
begin
|
|||
|
{Get the size of the second split}
|
|||
|
LSecondSplitSize := LBlockSize - LPSmallBlockType.OptimalBlockPoolSize;
|
|||
|
{Adjust the block size}
|
|||
|
LBlockSize := LPSmallBlockType.OptimalBlockPoolSize;
|
|||
|
{Split the block in two}
|
|||
|
LSecondSplit := PMediumFreeBlock(Cardinal(LMediumBlock) + LBlockSize);
|
|||
|
PCardinal(Cardinal(LSecondSplit) - BlockHeaderSize)^ := LSecondSplitSize or (IsMediumBlockFlag or IsFreeBlockFlag);
|
|||
|
{Store the size of the second split as the second last dword}
|
|||
|
PCardinal(Cardinal(LSecondSplit) + LSecondSplitSize - 8)^ := LSecondSplitSize;
|
|||
|
{Put the remainder in a bin (it will be big enough)}
|
|||
|
InsertMediumBlockIntoBin(LSecondSplit, LSecondSplitSize);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Mark this block as used in the block following it}
|
|||
|
LNextMediumBlockHeader := PCardinal(Cardinal(LMediumBlock) + LBlockSize - BlockHeaderSize);
|
|||
|
LNextMediumBlockHeader^ := LNextMediumBlockHeader^ and (not PreviousMediumBlockIsFreeFlag);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
{Check the sequential feed medium block pool for space}
|
|||
|
LSequentialFeedFreeSize := MediumSequentialFeedBytesLeft;
|
|||
|
if LSequentialFeedFreeSize >= LPSmallBlockType.MinimumBlockPoolSize then
|
|||
|
begin
|
|||
|
{Enough sequential feed space: Will the remainder be usable?}
|
|||
|
if LSequentialFeedFreeSize >= (LPSmallBlockType.OptimalBlockPoolSize + MinimumMediumBlockSize) then
|
|||
|
begin
|
|||
|
LBlockSize := LPSmallBlockType.OptimalBlockPoolSize;
|
|||
|
end
|
|||
|
else
|
|||
|
LBlockSize := LSequentialFeedFreeSize;
|
|||
|
{Get the block}
|
|||
|
LMediumBlock := Pointer(Cardinal(LastSequentiallyFedMediumBlock) - LBlockSize);
|
|||
|
{Update the sequential feed parameters}
|
|||
|
LastSequentiallyFedMediumBlock := LMediumBlock;
|
|||
|
MediumSequentialFeedBytesLeft := LSequentialFeedFreeSize - LBlockSize;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Need to allocate a new sequential feed medium block pool: use the
|
|||
|
optimal size for this small block pool}
|
|||
|
LBlockSize := LPSmallBlockType.OptimalBlockPoolSize;
|
|||
|
{Allocate the medium block pool}
|
|||
|
LMediumBlock := AllocNewSequentialFeedMediumPool(LBlockSize);
|
|||
|
if LMediumBlock = nil then
|
|||
|
begin
|
|||
|
{Out of memory}
|
|||
|
{Unlock the medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{Unlock the block type}
|
|||
|
LPSmallBlockType.BlockTypeLocked := False;
|
|||
|
{Failed}
|
|||
|
Result := nil;
|
|||
|
{done}
|
|||
|
exit;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Mark this block as in use}
|
|||
|
{Set the size and flags for this block}
|
|||
|
PCardinal(Cardinal(LMediumBlock) - BlockHeaderSize)^ := LBlockSize or IsMediumBlockFlag or IsSmallBlockPoolInUseFlag;
|
|||
|
{Unlock medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{Set up the block pool}
|
|||
|
LPSmallBlockPool := PSmallBlockPoolHeader(LMediumBlock);
|
|||
|
LPSmallBlockPool.BlockType := LPSmallBlockType;
|
|||
|
LPSmallBlockPool.FirstFreeBlock := nil;
|
|||
|
LPSmallBlockPool.BlocksInUse := 1;
|
|||
|
{Set it up for sequential block serving}
|
|||
|
LPSmallBlockType.CurrentSequentialFeedPool := LPSmallBlockPool;
|
|||
|
Result := Pointer(Cardinal(LPSmallBlockPool) + SmallBlockPoolHeaderSize);
|
|||
|
LPSmallBlockType.NextSequentialFeedBlockAddress := Pointer(Cardinal(Result) + LPSmallBlockType.BlockSize);
|
|||
|
LPSmallBlockType.MaxSequentialFeedBlockAddress := Pointer(Cardinal(LPSmallBlockPool) + LBlockSize - LPSmallBlockType.BlockSize);
|
|||
|
end;
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{Clear the user area of the block}
|
|||
|
FillDWord(Pointer(Cardinal(Result) + (SizeOf(TFullDebugBlockHeader) + 4))^,
|
|||
|
LPSmallBlockType.BlockSize - FullDebugBlockOverhead - 4,
|
|||
|
{$ifndef CatchUseOfFreedInterfaces}DebugFillDWord{$else}Cardinal(@VMTBadInterface){$endif});
|
|||
|
{Block was fed sequentially - we need to set a valid debug header}
|
|||
|
PFullDebugBlockHeader(Result).HeaderCheckSum := Cardinal(Result);
|
|||
|
PCardinal(Cardinal(Result) + SizeOf(TFullDebugBlockHeader))^ := not Cardinal(Result);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{Unlock the block type}
|
|||
|
LPSmallBlockType.BlockTypeLocked := False;
|
|||
|
{Set the block header}
|
|||
|
PCardinal(Cardinal(Result) - BlockHeaderSize)^ := Cardinal(LPSmallBlockPool);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Medium block or Large block?}
|
|||
|
if Cardinal(ASize) <= (MaximumMediumBlockSize - BlockHeaderSize) then
|
|||
|
begin
|
|||
|
{------------------------Allocate a medium block--------------------------}
|
|||
|
{Get the block size and bin number for this block size. Block sizes are
|
|||
|
rounded up to the next bin size.}
|
|||
|
LBlockSize := ((Cardinal(ASize) + (MediumBlockGranularity - 1 + BlockHeaderSize - MediumBlockSizeOffset))
|
|||
|
and -MediumBlockGranularity) + MediumBlockSizeOffset;
|
|||
|
{Get the bin number}
|
|||
|
LBinNumber := (LBlockSize - MinimumMediumBlockSize) div MediumBlockGranularity;
|
|||
|
{Lock the medium blocks}
|
|||
|
LockMediumBlocks;
|
|||
|
{Calculate the bin group}
|
|||
|
LBinGroupNumber := LBinNumber div 32;
|
|||
|
{Is there a suitable block inside this group?}
|
|||
|
LBinGroupMasked := MediumBlockBinBitmaps[LBinGroupNumber] and -(1 shl (LBinNumber and 31));
|
|||
|
if LBinGroupMasked <> 0 then
|
|||
|
begin
|
|||
|
{Get the actual bin number}
|
|||
|
LBinNumber := FindFirstSetBit(LBinGroupMasked) + LBinGroupNumber * 32;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{Try all groups greater than this group}
|
|||
|
LBinGroupsMasked := MediumBlockBinGroupBitmap and -(2 shl LBinGroupNumber);
|
|||
|
if LBinGroupsMasked <> 0 then
|
|||
|
begin
|
|||
|
{There is a suitable group with space: get the bin number}
|
|||
|
LBinGroupNumber := FindFirstSetBit(LBinGroupsMasked);
|
|||
|
{Get the bin in the group with free blocks}
|
|||
|
LBinNumber := FindFirstSetBit(MediumBlockBinBitmaps[LBinGroupNumber])
|
|||
|
+ LBinGroupNumber * 32;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
{There are no bins with a suitable block: Sequentially feed the required block}
|
|||
|
LSequentialFeedFreeSize := MediumSequentialFeedBytesLeft;
|
|||
|
if LSequentialFeedFreeSize >= LBlockSize then
|
|||
|
begin
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{In full debug mode a medium block must have enough bytes to fit
|
|||
|
all the debug info, so we must make sure there are no tiny medium
|
|||
|
blocks at the start of the pool.}
|
|||
|
if LSequentialFeedFreeSize - LBlockSize < (FullDebugBlockOverhead + BlockHeaderSize) then
|
|||
|
LBlockSize := LSequentialFeedFreeSize;
|
|||
|
{$endif}
|
|||
|
{Block can be fed sequentially}
|
|||
|
Result := Pointer(Cardinal(LastSequentiallyFedMediumBlock) - LBlockSize);
|
|||
|
{Store the last sequentially fed block}
|
|||
|
LastSequentiallyFedMediumBlock := Result;
|
|||
|
{Store the remaining bytes}
|
|||
|
MediumSequentialFeedBytesLeft := LSequentialFeedFreeSize - LBlockSize;
|
|||
|
{Set the flags for the block}
|
|||
|
PCardinal(Cardinal(Result) - BlockHeaderSize)^ := LBlockSize or IsMediumBlockFlag;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Need to allocate a new sequential feed block}
|
|||
|
Result := AllocNewSequentialFeedMediumPool(LBlockSize);
|
|||
|
end;
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{Block was fed sequentially - we need to set a valid debug header}
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
PFullDebugBlockHeader(Result).HeaderCheckSum := Cardinal(Result);
|
|||
|
PCardinal(Cardinal(Result) + SizeOf(TFullDebugBlockHeader))^ := not Cardinal(Result);
|
|||
|
{Clear the user area of the block}
|
|||
|
FillDWord(Pointer(Cardinal(Result) + SizeOf(TFullDebugBlockHeader) + 4)^,
|
|||
|
LBlockSize - FullDebugBlockOverhead - 4,
|
|||
|
{$ifndef CatchUseOfFreedInterfaces}DebugFillDWord{$else}Cardinal(@VMTBadInterface){$endif});
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Done}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
exit;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{If we get here we have a valid LBinGroupNumber and LBinNumber:
|
|||
|
Use the first block in the bin, splitting it if necessary}
|
|||
|
{Get a pointer to the bin}
|
|||
|
LPMediumBin := @MediumBlockBins[LBinNumber];
|
|||
|
{Get the result}
|
|||
|
Result := LPMediumBin.NextFreeBlock;
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{Check that this block is actually free and the next and previous blocks
|
|||
|
are both in use (except in full debug mode).}
|
|||
|
if ((PCardinal(Cardinal(Result) - BlockHeaderSize)^ and {$ifndef FullDebugMode}ExtractMediumAndLargeFlagsMask{$else}(IsMediumBlockFlag or IsFreeBlockFlag){$endif}) <> (IsFreeBlockFlag or IsMediumBlockFlag))
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
or ((PCardinal(Cardinal(Result) + (PCardinal(Cardinal(Result) - BlockHeaderSize)^ and DropMediumAndLargeFlagsMask) - BlockHeaderSize)^ and (ExtractMediumAndLargeFlagsMask - IsSmallBlockPoolInUseFlag)) <> (IsMediumBlockFlag or PreviousMediumBlockIsFreeFlag))
|
|||
|
{$endif}
|
|||
|
then
|
|||
|
begin
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Remove the block from the bin containing it}
|
|||
|
RemoveMediumFreeBlock(Result);
|
|||
|
{Get the block size}
|
|||
|
LAvailableBlockSize := PCardinal(Cardinal(Result) - BlockHeaderSize)^ and DropMediumAndLargeFlagsMask;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{Is it an exact fit or not?}
|
|||
|
LSecondSplitSize := LAvailableBlockSize - LBlockSize;
|
|||
|
if LSecondSplitSize <> 0 then
|
|||
|
begin
|
|||
|
{Split the block in two}
|
|||
|
LSecondSplit := PMediumFreeBlock(Cardinal(Result) + LBlockSize);
|
|||
|
{Set the size of the second split}
|
|||
|
PCardinal(Cardinal(LSecondSplit) - BlockHeaderSize)^ := LSecondSplitSize or (IsMediumBlockFlag or IsFreeBlockFlag);
|
|||
|
{Store the size of the second split as the second last dword}
|
|||
|
PCardinal(Cardinal(LSecondSplit) + LSecondSplitSize - 8)^ := LSecondSplitSize;
|
|||
|
{Put the remainder in a bin if it is big enough}
|
|||
|
if LSecondSplitSize >= MinimumMediumBlockSize then
|
|||
|
InsertMediumBlockIntoBin(LSecondSplit, LSecondSplitSize);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$else}
|
|||
|
{In full debug mode blocks are never split or coalesced}
|
|||
|
LBlockSize := LAvailableBlockSize;
|
|||
|
{$endif}
|
|||
|
{Mark this block as used in the block following it}
|
|||
|
LNextMediumBlockHeader := Pointer(Cardinal(Result) + LBlockSize - BlockHeaderSize);
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{The next block must be in use}
|
|||
|
if (LNextMediumBlockHeader^ and (ExtractMediumAndLargeFlagsMask - IsSmallBlockPoolInUseFlag)) <> (IsMediumBlockFlag or PreviousMediumBlockIsFreeFlag) then
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
LNextMediumBlockHeader^ :=
|
|||
|
LNextMediumBlockHeader^ and (not PreviousMediumBlockIsFreeFlag);
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
end;
|
|||
|
{Set the size and flags for this block}
|
|||
|
PCardinal(Cardinal(Result) - BlockHeaderSize)^ := LBlockSize or IsMediumBlockFlag;
|
|||
|
{$else}
|
|||
|
{In full debug mode blocks are never split or coalesced}
|
|||
|
Dec(PCardinal(Cardinal(Result) - BlockHeaderSize)^, IsFreeBlockFlag);
|
|||
|
{$endif}
|
|||
|
{Unlock the medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Allocate a Large block}
|
|||
|
if ASize > 0 then
|
|||
|
Result := AllocateLargeBlock(ASize)
|
|||
|
else
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$else}
|
|||
|
{Replacement for SysGetMem (asm version)}
|
|||
|
function FastGetMem(ASize: Integer): Pointer;
|
|||
|
asm
|
|||
|
{On entry:
|
|||
|
eax = ASize}
|
|||
|
{Since most allocations are for small blocks, determine the small block type
|
|||
|
index so long}
|
|||
|
lea edx, [eax + BlockHeaderSize - 1]
|
|||
|
{$ifdef Align16Bytes}
|
|||
|
shr edx, 4
|
|||
|
{$else}
|
|||
|
shr edx, 3
|
|||
|
{$endif}
|
|||
|
{Is it a small block?}
|
|||
|
cmp eax, (MaximumSmallBlockSize - BlockHeaderSize)
|
|||
|
{Save ebx}
|
|||
|
push ebx
|
|||
|
{Get the IsMultiThread variable so long}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
mov cl, IsMultiThread
|
|||
|
{$endif}
|
|||
|
{Is it a small block?}
|
|||
|
ja @NotASmallBlock
|
|||
|
{Do we need to lock the block type?}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
test cl, cl
|
|||
|
{$endif}
|
|||
|
{Get the small block type in ebx}
|
|||
|
movzx eax, byte ptr [AllocSize2SmallBlockTypeIndX4 + edx]
|
|||
|
lea ebx, [SmallBlockTypes + eax * 8]
|
|||
|
{Do we need to lock the block type?}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
jnz @LockBlockTypeLoop
|
|||
|
{$else}
|
|||
|
jmp @LockBlockTypeLoop
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
@GotLockOnSmallBlockType:
|
|||
|
{Find the next free block: Get the first pool with free blocks in edx}
|
|||
|
mov edx, TSmallBlockType[ebx].NextPartiallyFreePool
|
|||
|
{Get the first free block (or the next sequential feed address if edx = ebx)}
|
|||
|
mov eax, TSmallBlockPoolHeader[edx].FirstFreeBlock
|
|||
|
{Get the drop flags mask in ecx so long}
|
|||
|
mov ecx, DropSmallFlagsMask
|
|||
|
{Is there a pool with free blocks?}
|
|||
|
cmp edx, ebx
|
|||
|
je @TrySmallSequentialFeed
|
|||
|
{Increment the number of used blocks}
|
|||
|
add TSmallBlockPoolHeader[edx].BlocksInUse, 1
|
|||
|
{Get the new first free block}
|
|||
|
and ecx, [eax - 4]
|
|||
|
{Set the new first free block}
|
|||
|
mov TSmallBlockPoolHeader[edx].FirstFreeBlock, ecx
|
|||
|
{Set the block header}
|
|||
|
mov [eax - 4], edx
|
|||
|
{Is the chunk now full?}
|
|||
|
jz @RemoveSmallPool
|
|||
|
{Unlock the block type}
|
|||
|
mov TSmallBlockType[ebx].BlockTypeLocked, False
|
|||
|
{Restore ebx}
|
|||
|
pop ebx
|
|||
|
{All done}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
nop
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
nop
|
|||
|
@TrySmallSequentialFeed:
|
|||
|
{Try to feed a small block sequentially: Get the sequential feed block pool}
|
|||
|
mov edx, TSmallBlockType[ebx].CurrentSequentialFeedPool
|
|||
|
{Get the next sequential feed address so long}
|
|||
|
movzx ecx, TSmallBlockType[ebx].BlockSize
|
|||
|
add ecx, eax
|
|||
|
{Can another block fit?}
|
|||
|
cmp eax, TSmallBlockType[ebx].MaxSequentialFeedBlockAddress
|
|||
|
{Can another block fit?}
|
|||
|
ja @AllocateSmallBlockPool
|
|||
|
{Increment the number of used blocks in the sequential feed pool}
|
|||
|
add TSmallBlockPoolHeader[edx].BlocksInUse, 1
|
|||
|
{Store the next sequential feed block address}
|
|||
|
mov TSmallBlockType[ebx].NextSequentialFeedBlockAddress, ecx
|
|||
|
{Unlock the block type}
|
|||
|
mov TSmallBlockType[ebx].BlockTypeLocked, False
|
|||
|
{Set the block header}
|
|||
|
mov [eax - 4], edx
|
|||
|
{Restore ebx}
|
|||
|
pop ebx
|
|||
|
{All done}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
nop
|
|||
|
@RemoveSmallPool:
|
|||
|
{Pool is full - remove it from the partially free list}
|
|||
|
mov ecx, TSmallBlockPoolHeader[edx].NextPartiallyFreePool
|
|||
|
mov TSmallBlockPoolHeader[ecx].PreviousPartiallyFreePool, ebx
|
|||
|
mov TSmallBlockType[ebx].NextPartiallyFreePool, ecx
|
|||
|
{Unlock the block type}
|
|||
|
mov TSmallBlockType[ebx].BlockTypeLocked, False
|
|||
|
{Restore ebx}
|
|||
|
pop ebx
|
|||
|
{All done}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@LockBlockTypeLoop:
|
|||
|
mov eax, $100
|
|||
|
{Attempt to grab the block type}
|
|||
|
lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
|
|||
|
je @GotLockOnSmallBlockType
|
|||
|
{Try the next size}
|
|||
|
add ebx, Type(TSmallBlockType)
|
|||
|
mov eax, $100
|
|||
|
lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
|
|||
|
je @GotLockOnSmallBlockType
|
|||
|
{Try the next size (up to two sizes larger)}
|
|||
|
add ebx, Type(TSmallBlockType)
|
|||
|
mov eax, $100
|
|||
|
lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
|
|||
|
je @GotLockOnSmallBlockType
|
|||
|
{Block type and two sizes larger are all locked - give up and sleep}
|
|||
|
sub ebx, 2 * Type(TSmallBlockType)
|
|||
|
{Couldn't grab the block type - sleep and try again}
|
|||
|
push InitialSleepTime
|
|||
|
call Sleep
|
|||
|
{Try again}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to grab the block type}
|
|||
|
lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
|
|||
|
je @GotLockOnSmallBlockType
|
|||
|
{Couldn't grab the block type - sleep and try again}
|
|||
|
push AdditionalSleepTime
|
|||
|
call Sleep
|
|||
|
{Try again}
|
|||
|
jmp @LockBlockTypeLoop
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
nop
|
|||
|
@AllocateSmallBlockPool:
|
|||
|
{save additional registers}
|
|||
|
push esi
|
|||
|
push edi
|
|||
|
{Do we need to lock the medium blocks?}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
cmp IsMultiThread, False
|
|||
|
je @MediumBlocksLockedForPool
|
|||
|
{$endif}
|
|||
|
@LockMediumBlocksForPool:
|
|||
|
mov eax, $100
|
|||
|
{Attempt to lock the medium blocks}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @MediumBlocksLockedForPool
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push InitialSleepTime
|
|||
|
call Sleep
|
|||
|
{Try again}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to grab the block type}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @MediumBlocksLockedForPool
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push AdditionalSleepTime
|
|||
|
call Sleep
|
|||
|
{Try again}
|
|||
|
jmp @LockMediumBlocksForPool
|
|||
|
{Align branch target}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
nop
|
|||
|
nop
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
@MediumBlocksLockedForPool:
|
|||
|
{Are there any available blocks of a suitable size?}
|
|||
|
movsx esi, TSmallBlockType[ebx].AllowedGroupsForBlockPoolBitmap
|
|||
|
and esi, MediumBlockBinGroupBitmap
|
|||
|
jz @NoSuitableMediumBlocks
|
|||
|
{Get the bin group number with free blocks in eax}
|
|||
|
bsf eax, esi
|
|||
|
{Get the bin number in ecx}
|
|||
|
lea esi, [eax * 8]
|
|||
|
mov ecx, dword ptr [MediumBlockBinBitmaps + eax * 4]
|
|||
|
bsf ecx, ecx
|
|||
|
lea ecx, [ecx + esi * 4]
|
|||
|
{Get a pointer to the bin in edi}
|
|||
|
lea edi, [MediumBlockBins + ecx * 8]
|
|||
|
{Get the free block in esi}
|
|||
|
mov esi, TMediumFreeBlock[edi].NextFreeBlock
|
|||
|
{Remove the first block from the linked list (LIFO)}
|
|||
|
mov edx, TMediumFreeBlock[esi].NextFreeBlock
|
|||
|
mov TMediumFreeBlock[edi].NextFreeBlock, edx
|
|||
|
mov TMediumFreeBlock[edx].PreviousFreeBlock, edi
|
|||
|
{Is this bin now empty?}
|
|||
|
cmp edi, edx
|
|||
|
jne @MediumBinNotEmpty
|
|||
|
{eax = bin group number, ecx = bin number, edi = @bin, esi = free block, ebx = block type}
|
|||
|
{Flag this bin as empty}
|
|||
|
mov edx, -2
|
|||
|
rol edx, cl
|
|||
|
and dword ptr [MediumBlockBinBitmaps + eax * 4], edx
|
|||
|
jnz @MediumBinNotEmpty
|
|||
|
{Flag the group as empty}
|
|||
|
btr MediumBlockBinGroupBitmap, eax
|
|||
|
@MediumBinNotEmpty:
|
|||
|
{esi = free block, ebx = block type}
|
|||
|
{Get the size of the available medium block in edi}
|
|||
|
mov edi, DropMediumAndLargeFlagsMask
|
|||
|
and edi, [esi - 4]
|
|||
|
cmp edi, MaximumSmallBlockPoolSize
|
|||
|
jb @UseWholeBlock
|
|||
|
{Split the block: get the size of the second part, new block size is the
|
|||
|
optimal size}
|
|||
|
mov edx, edi
|
|||
|
movzx edi, TSmallBlockType[ebx].OptimalBlockPoolSize
|
|||
|
sub edx, edi
|
|||
|
{Split the block in two}
|
|||
|
lea eax, [esi + edi]
|
|||
|
lea ecx, [edx + IsMediumBlockFlag + IsFreeBlockFlag]
|
|||
|
mov [eax - 4], ecx
|
|||
|
{Store the size of the second split as the second last dword}
|
|||
|
mov [eax + edx - 8], edx
|
|||
|
{Put the remainder in a bin (it will be big enough)}
|
|||
|
call InsertMediumBlockIntoBin
|
|||
|
jmp @GotMediumBlock
|
|||
|
{Align branch target}
|
|||
|
@NoSuitableMediumBlocks:
|
|||
|
{Check the sequential feed medium block pool for space}
|
|||
|
movzx ecx, TSmallBlockType[ebx].MinimumBlockPoolSize
|
|||
|
mov edi, MediumSequentialFeedBytesLeft
|
|||
|
cmp edi, ecx
|
|||
|
jb @AllocateNewSequentialFeed
|
|||
|
{Get the address of the last block that was fed}
|
|||
|
mov esi, LastSequentiallyFedMediumBlock
|
|||
|
{Enough sequential feed space: Will the remainder be usable?}
|
|||
|
movzx ecx, TSmallBlockType[ebx].OptimalBlockPoolSize
|
|||
|
lea edx, [ecx + MinimumMediumBlockSize]
|
|||
|
cmp edi, edx
|
|||
|
jb @NotMuchSpace
|
|||
|
mov edi, ecx
|
|||
|
@NotMuchSpace:
|
|||
|
sub esi, edi
|
|||
|
{Update the sequential feed parameters}
|
|||
|
sub MediumSequentialFeedBytesLeft, edi
|
|||
|
mov LastSequentiallyFedMediumBlock, esi
|
|||
|
{Get the block pointer}
|
|||
|
jmp @GotMediumBlock
|
|||
|
{Align branch target}
|
|||
|
@AllocateNewSequentialFeed:
|
|||
|
{Need to allocate a new sequential feed medium block pool: use the
|
|||
|
optimal size for this small block pool}
|
|||
|
movzx eax, TSmallBlockType[ebx].OptimalBlockPoolSize
|
|||
|
mov edi, eax
|
|||
|
{Allocate the medium block pool}
|
|||
|
call AllocNewSequentialFeedMediumPool
|
|||
|
mov esi, eax
|
|||
|
test eax, eax
|
|||
|
jnz @GotMediumBlock
|
|||
|
mov MediumBlocksLocked, al
|
|||
|
mov TSmallBlockType[ebx].BlockTypeLocked, al
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
@UseWholeBlock:
|
|||
|
{esi = free block, ebx = block type, edi = block size}
|
|||
|
{Mark this block as used in the block following it}
|
|||
|
and byte ptr [esi + edi - 4], not PreviousMediumBlockIsFreeFlag
|
|||
|
@GotMediumBlock:
|
|||
|
{esi = free block, ebx = block type, edi = block size}
|
|||
|
{Set the size and flags for this block}
|
|||
|
lea ecx, [edi + IsMediumBlockFlag + IsSmallBlockPoolInUseFlag]
|
|||
|
mov [esi - 4], ecx
|
|||
|
{Unlock medium blocks}
|
|||
|
xor eax, eax
|
|||
|
mov MediumBlocksLocked, al
|
|||
|
{Set up the block pool}
|
|||
|
mov TSmallBlockPoolHeader[esi].BlockType, ebx
|
|||
|
mov TSmallBlockPoolHeader[esi].FirstFreeBlock, eax
|
|||
|
mov TSmallBlockPoolHeader[esi].BlocksInUse, 1
|
|||
|
{Set it up for sequential block serving}
|
|||
|
mov TSmallBlockType[ebx].CurrentSequentialFeedPool, esi
|
|||
|
{Return the pointer to the first block}
|
|||
|
lea eax, [esi + SmallBlockPoolHeaderSize]
|
|||
|
movzx ecx, TSmallBlockType[ebx].BlockSize
|
|||
|
lea edx, [eax + ecx]
|
|||
|
mov TSmallBlockType[ebx].NextSequentialFeedBlockAddress, edx
|
|||
|
add edi, esi
|
|||
|
sub edi, ecx
|
|||
|
mov TSmallBlockType[ebx].MaxSequentialFeedBlockAddress, edi
|
|||
|
{Unlock the small block type}
|
|||
|
mov TSmallBlockType[ebx].BlockTypeLocked, False
|
|||
|
{Set the small block header}
|
|||
|
mov [eax - 4], esi
|
|||
|
{Restore registers}
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
{Done}
|
|||
|
ret
|
|||
|
{-------------------Medium block allocation-------------------}
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
@LockMediumBlocks:
|
|||
|
mov eax, $100
|
|||
|
{Attempt to lock the medium blocks}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @MediumBlocksLocked
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push InitialSleepTime
|
|||
|
call Sleep
|
|||
|
{Try again}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to lock the medium blocks}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @MediumBlocksLocked
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push AdditionalSleepTime
|
|||
|
call Sleep
|
|||
|
{Try again}
|
|||
|
jmp @LockMediumBlocks
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@NotASmallBlock:
|
|||
|
cmp eax, (MaximumMediumBlockSize - BlockHeaderSize)
|
|||
|
ja @IsALargeBlockRequest
|
|||
|
{Get the bin size for this block size. Block sizes are
|
|||
|
rounded up to the next bin size.}
|
|||
|
lea ebx, [eax + MediumBlockGranularity - 1 + BlockHeaderSize - MediumBlockSizeOffset]
|
|||
|
and ebx, -MediumBlockGranularity
|
|||
|
add ebx, MediumBlockSizeOffset
|
|||
|
{Do we need to lock the medium blocks?}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
test cl, cl
|
|||
|
jnz @LockMediumBlocks
|
|||
|
{$else}
|
|||
|
jmp @LockMediumBlocks
|
|||
|
{Align branch target}
|
|||
|
{$endif}
|
|||
|
@MediumBlocksLocked:
|
|||
|
{Get the bin number in ecx and the group number in edx}
|
|||
|
lea edx, [ebx - MinimumMediumBlockSize]
|
|||
|
mov ecx, edx
|
|||
|
shr edx, 8 + 5
|
|||
|
shr ecx, 8
|
|||
|
{Is there a suitable block inside this group?}
|
|||
|
mov eax, -1
|
|||
|
shl eax, cl
|
|||
|
and eax, dword ptr [MediumBlockBinBitmaps + edx * 4]
|
|||
|
jz @GroupIsEmpty
|
|||
|
{Get the actual bin number}
|
|||
|
and ecx, -32
|
|||
|
bsf eax, eax
|
|||
|
or ecx, eax
|
|||
|
jmp @GotBinAndGroup
|
|||
|
{Align branch target}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
nop
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
@GroupIsEmpty:
|
|||
|
{Try all groups greater than this group}
|
|||
|
mov eax, -2
|
|||
|
mov ecx, edx
|
|||
|
shl eax, cl
|
|||
|
and eax, MediumBlockBinGroupBitmap
|
|||
|
jz @TrySequentialFeedMedium
|
|||
|
{There is a suitable group with space: get the bin number}
|
|||
|
bsf edx, eax
|
|||
|
{Get the bin in the group with free blocks}
|
|||
|
mov eax, dword ptr [MediumBlockBinBitmaps + edx * 4]
|
|||
|
bsf ecx, eax
|
|||
|
mov eax, edx
|
|||
|
shl eax, 5
|
|||
|
or ecx, eax
|
|||
|
jmp @GotBinAndGroup
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
@TrySequentialFeedMedium:
|
|||
|
mov ecx, MediumSequentialFeedBytesLeft
|
|||
|
{Block can be fed sequentially?}
|
|||
|
sub ecx, ebx
|
|||
|
jc @AllocateNewSequentialFeedForMedium
|
|||
|
{Get the block address}
|
|||
|
mov eax, LastSequentiallyFedMediumBlock
|
|||
|
sub eax, ebx
|
|||
|
mov LastSequentiallyFedMediumBlock, eax
|
|||
|
{Store the remaining bytes}
|
|||
|
mov MediumSequentialFeedBytesLeft, ecx
|
|||
|
{Set the flags for the block}
|
|||
|
or ebx, IsMediumBlockFlag
|
|||
|
mov [eax - 4], ebx
|
|||
|
jmp @MediumBlockGetDone
|
|||
|
{Align branch target}
|
|||
|
@AllocateNewSequentialFeedForMedium:
|
|||
|
mov eax, ebx
|
|||
|
call AllocNewSequentialFeedMediumPool
|
|||
|
@MediumBlockGetDone:
|
|||
|
mov MediumBlocksLocked, False
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
@GotBinAndGroup:
|
|||
|
{ebx = block size, ecx = bin number, edx = group number}
|
|||
|
push esi
|
|||
|
push edi
|
|||
|
{Get a pointer to the bin in edi}
|
|||
|
lea edi, [MediumBlockBins + ecx * 8]
|
|||
|
{Get the free block in esi}
|
|||
|
mov esi, TMediumFreeBlock[edi].NextFreeBlock
|
|||
|
{Remove the first block from the linked list (LIFO)}
|
|||
|
mov eax, TMediumFreeBlock[esi].NextFreeBlock
|
|||
|
mov TMediumFreeBlock[edi].NextFreeBlock, eax
|
|||
|
mov TMediumFreeBlock[eax].PreviousFreeBlock, edi
|
|||
|
{Is this bin now empty?}
|
|||
|
cmp edi, eax
|
|||
|
jne @MediumBinNotEmptyForMedium
|
|||
|
{eax = bin group number, ecx = bin number, edi = @bin, esi = free block, ebx = block size}
|
|||
|
{Flag this bin as empty}
|
|||
|
mov eax, -2
|
|||
|
rol eax, cl
|
|||
|
and dword ptr [MediumBlockBinBitmaps + edx * 4], eax
|
|||
|
jnz @MediumBinNotEmptyForMedium
|
|||
|
{Flag the group as empty}
|
|||
|
btr MediumBlockBinGroupBitmap, edx
|
|||
|
@MediumBinNotEmptyForMedium:
|
|||
|
{esi = free block, ebx = block size}
|
|||
|
{Get the size of the available medium block in edi}
|
|||
|
mov edi, DropMediumAndLargeFlagsMask
|
|||
|
and edi, [esi - 4]
|
|||
|
{Get the size of the second split in edx}
|
|||
|
mov edx, edi
|
|||
|
sub edx, ebx
|
|||
|
jz @UseWholeBlockForMedium
|
|||
|
{Split the block in two}
|
|||
|
lea eax, [esi + ebx]
|
|||
|
lea ecx, [edx + IsMediumBlockFlag + IsFreeBlockFlag]
|
|||
|
mov [eax - 4], ecx
|
|||
|
{Store the size of the second split as the second last dword}
|
|||
|
mov [eax + edx - 8], edx
|
|||
|
{Put the remainder in a bin}
|
|||
|
cmp edx, MinimumMediumBlockSize
|
|||
|
jb @GotMediumBlockForMedium
|
|||
|
call InsertMediumBlockIntoBin
|
|||
|
jmp @GotMediumBlockForMedium
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
nop
|
|||
|
@UseWholeBlockForMedium:
|
|||
|
{Mark this block as used in the block following it}
|
|||
|
and byte ptr [esi + edi - 4], not PreviousMediumBlockIsFreeFlag
|
|||
|
@GotMediumBlockForMedium:
|
|||
|
{Set the size and flags for this block}
|
|||
|
lea ecx, [ebx + IsMediumBlockFlag]
|
|||
|
mov [esi - 4], ecx
|
|||
|
{Unlock medium blocks}
|
|||
|
mov MediumBlocksLocked, False
|
|||
|
mov eax, esi
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{-------------------Large block allocation-------------------}
|
|||
|
{Align branch target}
|
|||
|
@IsALargeBlockRequest:
|
|||
|
pop ebx
|
|||
|
test eax, eax
|
|||
|
jns AllocateLargeBlock
|
|||
|
xor eax, eax
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef ASMVersion}
|
|||
|
{Replacement for SysFreeMem (pascal version)}
|
|||
|
function FastFreeMem(APointer: Pointer): Integer;
|
|||
|
var
|
|||
|
LNextMediumBlock{$ifndef FullDebugMode}, LPreviousMediumBlock{$endif}: PMediumFreeBlock;
|
|||
|
LNextMediumBlockSizeAndFlags: Cardinal;
|
|||
|
LBlockSize{$ifndef FullDebugMode}, LPreviousMediumBlockSize{$endif}: Cardinal;
|
|||
|
LPSmallBlockPool{$ifndef FullDebugMode}, LPPreviousPool, LPNextPool{$endif},
|
|||
|
LPOldFirstPool: PSmallBlockPoolHeader;
|
|||
|
LPSmallBlockType: PSmallBlockType;
|
|||
|
LOldFirstFreeBlock: Pointer;
|
|||
|
LBlockHeader: Cardinal;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
LPPreviousMediumBlockPoolHeader, LPNextMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{Get the small block header: Is it actually a small block?}
|
|||
|
LBlockHeader := PCardinal(Cardinal(APointer) - BlockHeaderSize)^;
|
|||
|
{Is it a small block that is in use?}
|
|||
|
if LBlockHeader and (IsFreeBlockFlag or IsMediumBlockFlag or IsLargeBlockFlag) = 0 then
|
|||
|
begin
|
|||
|
{Get a pointer to the block pool}
|
|||
|
LPSmallBlockPool := PSmallBlockPoolHeader(LBlockHeader);
|
|||
|
{Get the block type}
|
|||
|
LPSmallBlockType := LPSmallBlockPool.BlockType;
|
|||
|
{Lock the block type}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
if IsMultiThread then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
while (LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) <> 0) do
|
|||
|
begin
|
|||
|
Sleep(InitialSleepTime);
|
|||
|
if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
|
|||
|
break;
|
|||
|
Sleep(AdditionalSleepTime);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Get the old first free block}
|
|||
|
LOldFirstFreeBlock := LPSmallBlockPool.FirstFreeBlock;
|
|||
|
{Was the pool manager previously full?}
|
|||
|
if LOldFirstFreeBlock = nil then
|
|||
|
begin
|
|||
|
{Insert this as the first partially free pool for the block size}
|
|||
|
LPOldFirstPool := LPSmallBlockType.NextPartiallyFreePool;
|
|||
|
LPSmallBlockPool.NextPartiallyFreePool := LPOldFirstPool;
|
|||
|
LPOldFirstPool.PreviousPartiallyFreePool := LPSmallBlockPool;
|
|||
|
LPSmallBlockPool.PreviousPartiallyFreePool := PSmallBlockPoolHeader(LPSmallBlockType);
|
|||
|
LPSmallBlockType.NextPartiallyFreePool := LPSmallBlockPool;
|
|||
|
end;
|
|||
|
{Store the old first free block}
|
|||
|
PCardinal(Cardinal(APointer) - BlockHeaderSize)^ := Cardinal(LOldFirstFreeBlock) or IsFreeBlockFlag;
|
|||
|
{Store this as the new first free block}
|
|||
|
LPSmallBlockPool.FirstFreeBlock := APointer;
|
|||
|
{Decrement the number of allocated blocks}
|
|||
|
Dec(LPSmallBlockPool.BlocksInUse);
|
|||
|
{Small block pools are never freed in full debug mode. This increases the
|
|||
|
likehood of success in catching objects still being used after being
|
|||
|
destroyed.}
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{Is the entire pool now free? -> Free it.}
|
|||
|
if LPSmallBlockPool.BlocksInUse = 0 then
|
|||
|
begin
|
|||
|
{Get the previous and next chunk managers}
|
|||
|
LPPreviousPool := LPSmallBlockPool.PreviousPartiallyFreePool;
|
|||
|
LPNextPool := LPSmallBlockPool.NextPartiallyFreePool;
|
|||
|
{Remove this manager}
|
|||
|
LPPreviousPool.NextPartiallyFreePool := LPNextPool;
|
|||
|
LPNextPool.PreviousPartiallyFreePool := LPPreviousPool;
|
|||
|
{Is this the sequential feed pool? If so, stop sequential feeding}
|
|||
|
if (LPSmallBlockType.CurrentSequentialFeedPool = LPSmallBlockPool) then
|
|||
|
LPSmallBlockType.MaxSequentialFeedBlockAddress := nil;
|
|||
|
{Unlock this block type}
|
|||
|
LPSmallBlockType.BlockTypeLocked := False;
|
|||
|
{No longer a small block pool in use (the flag must be reset in the
|
|||
|
pascal version, since IsSmallBlockPoolInUseFlag = IsLargeBlockFlag)}
|
|||
|
PCardinal(Cardinal(LPSmallBlockPool) - 4)^ := PCardinal(Cardinal(LPSmallBlockPool) - 4)^ and (not IsSmallBlockPoolInUseFlag);
|
|||
|
{Release this pool}
|
|||
|
FastFreeMem(LPSmallBlockPool);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
{Unlock this block type}
|
|||
|
LPSmallBlockType.BlockTypeLocked := False;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{No error}
|
|||
|
Result := 0;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Is this a medium block or a large block?}
|
|||
|
if LBlockHeader and (IsFreeBlockFlag or IsLargeBlockFlag) = 0 then
|
|||
|
begin
|
|||
|
{Get the medium block size}
|
|||
|
LBlockSize := LBlockHeader and DropMediumAndLargeFlagsMask;
|
|||
|
{Lock the medium blocks}
|
|||
|
LockMediumBlocks;
|
|||
|
{Can we combine this block with the next free block?}
|
|||
|
LNextMediumBlock := PMediumFreeBlock(Cardinal(APointer) + LBlockSize);
|
|||
|
LNextMediumBlockSizeAndFlags := PCardinal(Cardinal(LNextMediumBlock) - BlockHeaderSize)^;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{Check that this block was flagged as in use in the next block}
|
|||
|
if (LNextMediumBlockSizeAndFlags and PreviousMediumBlockIsFreeFlag) <> 0 then
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
if (LNextMediumBlockSizeAndFlags and IsFreeBlockFlag) <> 0 then
|
|||
|
begin
|
|||
|
{Increase the size of this block}
|
|||
|
Inc(LBlockSize, LNextMediumBlockSizeAndFlags and DropMediumAndLargeFlagsMask);
|
|||
|
{Remove the next block as well}
|
|||
|
if LNextMediumBlockSizeAndFlags >= MinimumMediumBlockSize then
|
|||
|
RemoveMediumFreeBlock(LNextMediumBlock);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
{Reset the "previous in use" flag of the next block}
|
|||
|
PCardinal(Cardinal(LNextMediumBlock) - BlockHeaderSize)^ := LNextMediumBlockSizeAndFlags or PreviousMediumBlockIsFreeFlag;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
end;
|
|||
|
{Can we combine this block with the previous free block? We need to
|
|||
|
re-read the flags since it could have changed before we could lock the
|
|||
|
medium blocks.}
|
|||
|
if (PCardinal(Cardinal(APointer) - BlockHeaderSize)^ and PreviousMediumBlockIsFreeFlag) <> 0 then
|
|||
|
begin
|
|||
|
{Get the size of the free block just before this one}
|
|||
|
LPreviousMediumBlockSize := PCardinal(Cardinal(APointer) - 8)^;
|
|||
|
{Get the start of the previous block}
|
|||
|
LPreviousMediumBlock := PMediumFreeBlock(Cardinal(APointer) - LPreviousMediumBlockSize);
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{Check that the previous block is actually free}
|
|||
|
if (PCardinal(Cardinal(LPreviousMediumBlock) - BlockHeaderSize)^ and ExtractMediumAndLargeFlagsMask) <> (IsMediumBlockFlag or IsFreeBlockFlag) then
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{Set the new block size}
|
|||
|
Inc(LBlockSize, LPreviousMediumBlockSize);
|
|||
|
{This is the new current block}
|
|||
|
APointer := LPreviousMediumBlock;
|
|||
|
{Remove the previous block from the linked list}
|
|||
|
if LPreviousMediumBlockSize >= MinimumMediumBlockSize then
|
|||
|
RemoveMediumFreeBlock(LPreviousMediumBlock);
|
|||
|
end;
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{Check that the previous block is currently flagged as in use}
|
|||
|
if (PCardinal(Cardinal(APointer) - BlockHeaderSize)^ and PreviousMediumBlockIsFreeFlag) <> 0 then
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{Is the entire medium block pool free, and there are other free blocks
|
|||
|
that can fit the largest possible medium block? -> free it. (Except in
|
|||
|
full debug mode where medium pools are never freed.)}
|
|||
|
if (LBlockSize <> (MediumBlockPoolSize - MediumBlockPoolHeaderSize)) then
|
|||
|
begin
|
|||
|
{Store the size of the block as well as the flags}
|
|||
|
PCardinal(Cardinal(APointer) - BlockHeaderSize)^ := LBlockSize or (IsMediumBlockFlag or IsFreeBlockFlag);
|
|||
|
{$else}
|
|||
|
{Mark the block as free}
|
|||
|
Inc(PCardinal(Cardinal(APointer) - BlockHeaderSize)^, IsFreeBlockFlag);
|
|||
|
{$endif}
|
|||
|
{Store the trailing size marker}
|
|||
|
PCardinal(Cardinal(APointer) + LBlockSize - 8)^ := LBlockSize;
|
|||
|
{Insert this block back into the bins: Size check not required here,
|
|||
|
since medium blocks that are in use are not allowed to be
|
|||
|
shrunk smaller than MinimumMediumBlockSize}
|
|||
|
InsertMediumBlockIntoBin(APointer, LBlockSize);
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{Check that this block is actually free and the next and previous blocks are both in use.}
|
|||
|
if ((PCardinal(Cardinal(APointer) - BlockHeaderSize)^ and ExtractMediumAndLargeFlagsMask) <> (IsMediumBlockFlag or IsFreeBlockFlag))
|
|||
|
or ((PCardinal(Cardinal(APointer) + (PCardinal(Cardinal(APointer) - BlockHeaderSize)^ and DropMediumAndLargeFlagsMask) - BlockHeaderSize)^ and IsFreeBlockFlag) <> 0) then
|
|||
|
begin
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{Unlock medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{All OK}
|
|||
|
Result := 0;
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Should this become the new sequential feed?}
|
|||
|
if MediumSequentialFeedBytesLeft <> MediumBlockPoolSize - MediumBlockPoolHeaderSize then
|
|||
|
begin
|
|||
|
{Bin the current sequential feed}
|
|||
|
BinMediumSequentialFeedRemainder;
|
|||
|
{Set this medium pool up as the new sequential feed pool:
|
|||
|
Store the sequential feed pool trailer}
|
|||
|
PCardinal(Cardinal(APointer) + LBlockSize - BlockHeaderSize)^ := IsMediumBlockFlag;
|
|||
|
{Store the number of bytes available in the sequential feed chunk}
|
|||
|
MediumSequentialFeedBytesLeft := MediumBlockPoolSize - MediumBlockPoolHeaderSize;
|
|||
|
{Set the last sequentially fed block}
|
|||
|
LastSequentiallyFedMediumBlock := Pointer(Cardinal(APointer) + LBlockSize);
|
|||
|
{Unlock medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{Success}
|
|||
|
Result := 0;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Remove this medium block pool from the linked list}
|
|||
|
Dec(Cardinal(APointer), MediumBlockPoolHeaderSize);
|
|||
|
LPPreviousMediumBlockPoolHeader := PMediumBlockPoolHeader(APointer).PreviousMediumBlockPoolHeader;
|
|||
|
LPNextMediumBlockPoolHeader := PMediumBlockPoolHeader(APointer).NextMediumBlockPoolHeader;
|
|||
|
LPPreviousMediumBlockPoolHeader.NextMediumBlockPoolHeader := LPNextMediumBlockPoolHeader;
|
|||
|
LPNextMediumBlockPoolHeader.PreviousMediumBlockPoolHeader := LPPreviousMediumBlockPoolHeader;
|
|||
|
{Unlock medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{Free the medium block pool}
|
|||
|
if VirtualFree(APointer, 0, MEM_RELEASE) then
|
|||
|
Result := 0
|
|||
|
else
|
|||
|
Result := -1;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Validate: Is this actually a Large block, or is it an attempt to free an
|
|||
|
already freed small block?}
|
|||
|
if LBlockHeader and (IsFreeBlockFlag or IsMediumBlockFlag) = 0 then
|
|||
|
Result := FreeLargeBlock(APointer)
|
|||
|
else
|
|||
|
Result := -1;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$else}
|
|||
|
{Replacement for SysFreeMem (pascal version)}
|
|||
|
function FastFreeMem(APointer: Pointer): Integer;
|
|||
|
asm
|
|||
|
{Get the block header in edx}
|
|||
|
mov edx, [eax - 4]
|
|||
|
{Is it a small block in use?}
|
|||
|
test dl, IsFreeBlockFlag + IsMediumBlockFlag + IsLargeBlockFlag
|
|||
|
{Save the pointer in ecx}
|
|||
|
mov ecx, eax
|
|||
|
{Save ebx}
|
|||
|
push ebx
|
|||
|
{Get the IsMultiThread variable in bl}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
mov bl, IsMultiThread
|
|||
|
{$endif}
|
|||
|
{Is it a small block that is in use?}
|
|||
|
jnz @NotSmallBlockInUse
|
|||
|
{Do we need to lock the block type?}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
test bl, bl
|
|||
|
{$endif}
|
|||
|
{Get the small block type in ebx}
|
|||
|
mov ebx, TSmallBlockPoolHeader[edx].BlockType
|
|||
|
{Do we need to lock the block type?}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
jnz @LockBlockTypeLoop
|
|||
|
{$else}
|
|||
|
jmp @LockBlockTypeLoop
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
@GotLockOnSmallBlockType:
|
|||
|
{Current state: edx = @SmallBlockPoolHeader, ecx = APointer, ebx = @SmallBlockType}
|
|||
|
{Decrement the number of blocks in use}
|
|||
|
sub TSmallBlockPoolHeader[edx].BlocksInUse, 1
|
|||
|
{Get the old first free block}
|
|||
|
mov eax, TSmallBlockPoolHeader[edx].FirstFreeBlock
|
|||
|
{Is the pool now empty?}
|
|||
|
jz @PoolIsNowEmpty
|
|||
|
{Was the pool full?}
|
|||
|
test eax, eax
|
|||
|
{Store this as the new first free block}
|
|||
|
mov TSmallBlockPoolHeader[edx].FirstFreeBlock, ecx
|
|||
|
{Store the previous first free block as the block header}
|
|||
|
lea eax, [eax + IsFreeBlockFlag]
|
|||
|
mov [ecx - 4], eax
|
|||
|
{Insert the pool back into the linked list if it was full}
|
|||
|
jz @SmallPoolWasFull
|
|||
|
{All ok}
|
|||
|
xor eax, eax
|
|||
|
{Unlock the block type}
|
|||
|
mov TSmallBlockType[ebx].BlockTypeLocked, al
|
|||
|
{Restore registers}
|
|||
|
pop ebx
|
|||
|
{Done}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
@SmallPoolWasFull:
|
|||
|
{Insert this as the first partially free pool for the block size}
|
|||
|
mov ecx, TSmallBlockType[ebx].NextPartiallyFreePool
|
|||
|
mov TSmallBlockPoolHeader[edx].PreviousPartiallyFreePool, ebx
|
|||
|
mov TSmallBlockPoolHeader[edx].NextPartiallyFreePool, ecx
|
|||
|
mov TSmallBlockPoolHeader[ecx].PreviousPartiallyFreePool, edx
|
|||
|
mov TSmallBlockType[ebx].NextPartiallyFreePool, edx
|
|||
|
{Unlock the block type}
|
|||
|
mov TSmallBlockType[ebx].BlockTypeLocked, False
|
|||
|
{All ok}
|
|||
|
xor eax, eax
|
|||
|
{Restore registers}
|
|||
|
pop ebx
|
|||
|
{Done}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@PoolIsNowEmpty:
|
|||
|
{Was this pool actually in the linked list of pools with space? If not, it
|
|||
|
can only be the sequential feed pool (it is the only pool that may contain
|
|||
|
only one block, i.e. other blocks have not been split off yet)}
|
|||
|
test eax, eax
|
|||
|
jz @IsSequentialFeedPool
|
|||
|
{Pool is now empty: Remove it from the linked list and free it}
|
|||
|
mov eax, TSmallBlockPoolHeader[edx].PreviousPartiallyFreePool
|
|||
|
mov ecx, TSmallBlockPoolHeader[edx].NextPartiallyFreePool
|
|||
|
{Remove this manager}
|
|||
|
mov TSmallBlockPoolHeader[eax].NextPartiallyFreePool, ecx
|
|||
|
mov TSmallBlockPoolHeader[ecx].PreviousPartiallyFreePool, eax
|
|||
|
{Zero out eax}
|
|||
|
xor eax, eax
|
|||
|
{Is this the sequential feed pool? If so, stop sequential feeding}
|
|||
|
cmp TSmallBlockType[ebx].CurrentSequentialFeedPool, edx
|
|||
|
jne @NotSequentialFeedPool
|
|||
|
@IsSequentialFeedPool:
|
|||
|
mov TSmallBlockType[ebx].MaxSequentialFeedBlockAddress, eax
|
|||
|
@NotSequentialFeedPool:
|
|||
|
{Unlock the block type}
|
|||
|
mov TSmallBlockType[ebx].BlockTypeLocked, al
|
|||
|
{Release this pool}
|
|||
|
mov eax, edx
|
|||
|
mov edx, [edx - 4]
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
mov bl, IsMultiThread
|
|||
|
{$endif}
|
|||
|
jmp @FreeMediumBlock
|
|||
|
{Align branch target}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
nop
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
nop
|
|||
|
@LockBlockTypeLoop:
|
|||
|
mov eax, $100
|
|||
|
{Attempt to grab the block type}
|
|||
|
lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
|
|||
|
je @GotLockOnSmallBlockType
|
|||
|
{Couldn't grab the block type - sleep and try again}
|
|||
|
push ecx
|
|||
|
push edx
|
|||
|
push InitialSleepTime
|
|||
|
call Sleep
|
|||
|
pop edx
|
|||
|
pop ecx
|
|||
|
{Try again}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to grab the block type}
|
|||
|
lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
|
|||
|
je @GotLockOnSmallBlockType
|
|||
|
{Couldn't grab the block type - sleep and try again}
|
|||
|
push ecx
|
|||
|
push edx
|
|||
|
push AdditionalSleepTime
|
|||
|
call Sleep
|
|||
|
pop edx
|
|||
|
pop ecx
|
|||
|
{Try again}
|
|||
|
jmp @LockBlockTypeLoop
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
{---------------------Medium blocks------------------------------}
|
|||
|
@LockMediumBlocks:
|
|||
|
mov eax, $100
|
|||
|
{Attempt to lock the medium blocks}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @MediumBlocksLocked
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push InitialSleepTime
|
|||
|
call Sleep
|
|||
|
{Try again}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to lock the medium blocks}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @MediumBlocksLocked
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push AdditionalSleepTime
|
|||
|
call Sleep
|
|||
|
{Try again}
|
|||
|
jmp @LockMediumBlocks
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@NotSmallBlockInUse:
|
|||
|
{Not a small block in use: is it a medium or large block?}
|
|||
|
test dl, IsFreeBlockFlag + IsLargeBlockFlag
|
|||
|
jnz @NotASmallOrMediumBlock
|
|||
|
@FreeMediumBlock:
|
|||
|
{Drop the flags}
|
|||
|
and edx, DropMediumAndLargeFlagsMask
|
|||
|
{Free the large block pointed to by eax, header in edx, bl = IsMultiThread}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
{Do we need to lock the medium blocks?}
|
|||
|
test bl, bl
|
|||
|
{$endif}
|
|||
|
{Block size in ebx}
|
|||
|
mov ebx, edx
|
|||
|
{Save registers}
|
|||
|
push esi
|
|||
|
{Pointer in esi}
|
|||
|
mov esi, eax
|
|||
|
{Do we need to lock the medium blocks?}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
jnz @LockMediumBlocks
|
|||
|
{$else}
|
|||
|
jmp @LockMediumBlocks
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
@MediumBlocksLocked:
|
|||
|
{Can we combine this block with the next free block?}
|
|||
|
test dword ptr [esi + ebx - 4], IsFreeBlockFlag
|
|||
|
{Get the next block size and flags in ecx}
|
|||
|
mov ecx, [esi + ebx - 4]
|
|||
|
jnz @NextBlockIsFree
|
|||
|
{Set the "PreviousIsFree" flag in the next block}
|
|||
|
or ecx, PreviousMediumBlockIsFreeFlag
|
|||
|
mov [esi + ebx - 4], ecx
|
|||
|
@NextBlockChecked:
|
|||
|
{Can we combine this block with the previous free block? We need to
|
|||
|
re-read the flags since it could have changed before we could lock the
|
|||
|
medium blocks.}
|
|||
|
test byte ptr [esi - 4], PreviousMediumBlockIsFreeFlag
|
|||
|
jnz @PreviousBlockIsFree
|
|||
|
@PreviousBlockChecked:
|
|||
|
{Is the entire medium block pool free, and there are other free blocks
|
|||
|
that can fit the largest possible medium block -> free it.}
|
|||
|
cmp ebx, (MediumBlockPoolSize - MediumBlockPoolHeaderSize)
|
|||
|
je @EntireMediumPoolFree
|
|||
|
@BinFreeMediumBlock:
|
|||
|
{Store the size of the block as well as the flags}
|
|||
|
lea eax, [ebx + IsMediumBlockFlag + IsFreeBlockFlag]
|
|||
|
mov [esi - 4], eax
|
|||
|
{Store the trailing size marker}
|
|||
|
mov [esi + ebx - 8], ebx
|
|||
|
{Insert this block back into the bins: Size check not required here,
|
|||
|
since medium blocks that are in use are not allowed to be
|
|||
|
shrunk smaller than MinimumMediumBlockSize}
|
|||
|
mov eax, esi
|
|||
|
mov edx, ebx
|
|||
|
{Insert into bin}
|
|||
|
call InsertMediumBlockIntoBin
|
|||
|
{Unlock medium blocks}
|
|||
|
mov MediumBlocksLocked, False;
|
|||
|
{All OK}
|
|||
|
xor eax, eax
|
|||
|
{Restore registers}
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
{Return}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
{$ifdef AssumeMultiThreaded}
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
nop
|
|||
|
@NextBlockIsFree:
|
|||
|
{Get the next block address in eax}
|
|||
|
lea eax, [esi + ebx]
|
|||
|
{Increase the size of this block}
|
|||
|
and ecx, DropMediumAndLargeFlagsMask
|
|||
|
add ebx, ecx
|
|||
|
{Was the block binned?}
|
|||
|
cmp ecx, MinimumMediumBlockSize
|
|||
|
jb @NextBlockChecked
|
|||
|
call RemoveMediumFreeBlock
|
|||
|
jmp @NextBlockChecked
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
@PreviousBlockIsFree:
|
|||
|
{Get the size of the free block just before this one}
|
|||
|
mov ecx, [esi - 8]
|
|||
|
{Include the previous block}
|
|||
|
sub esi, ecx
|
|||
|
{Set the new block size}
|
|||
|
add ebx, ecx
|
|||
|
{Remove the previous block from the linked list}
|
|||
|
cmp ecx, MinimumMediumBlockSize
|
|||
|
jb @PreviousBlockChecked
|
|||
|
mov eax, esi
|
|||
|
call RemoveMediumFreeBlock
|
|||
|
jmp @PreviousBlockChecked
|
|||
|
{Align branch target}
|
|||
|
@EntireMediumPoolFree:
|
|||
|
{Should we make this the new sequential feed medium block pool? If the
|
|||
|
current sequential feed pool is not entirely free, we make this the new
|
|||
|
sequential feed pool.}
|
|||
|
cmp MediumSequentialFeedBytesLeft, MediumBlockPoolSize - MediumBlockPoolHeaderSize
|
|||
|
jne @MakeEmptyMediumPoolSequentialFeed
|
|||
|
{Point esi to the medium block pool header}
|
|||
|
sub esi, MediumBlockPoolHeaderSize
|
|||
|
{Remove this medium block pool from the linked list}
|
|||
|
mov eax, TMediumBlockPoolHeader[esi].PreviousMediumBlockPoolHeader
|
|||
|
mov edx, TMediumBlockPoolHeader[esi].NextMediumBlockPoolHeader
|
|||
|
mov TMediumBlockPoolHeader[eax].NextMediumBlockPoolHeader, edx
|
|||
|
mov TMediumBlockPoolHeader[edx].PreviousMediumBlockPoolHeader, eax
|
|||
|
{Unlock medium blocks}
|
|||
|
mov MediumBlocksLocked, False;
|
|||
|
{Free the medium block pool}
|
|||
|
push MEM_RELEASE
|
|||
|
push 0
|
|||
|
push esi
|
|||
|
call VirtualFree
|
|||
|
{VirtualFree returns >0 if all is ok}
|
|||
|
cmp eax, 1
|
|||
|
{Return 0 on all ok}
|
|||
|
sbb eax, eax
|
|||
|
{Restore registers}
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
nop
|
|||
|
@MakeEmptyMediumPoolSequentialFeed:
|
|||
|
{Get a pointer to the end-marker block}
|
|||
|
lea ebx, [esi + MediumBlockPoolSize - MediumBlockPoolHeaderSize]
|
|||
|
{Bin the current sequential feed pool}
|
|||
|
call BinMediumSequentialFeedRemainder
|
|||
|
{Set this medium pool up as the new sequential feed pool:
|
|||
|
Store the sequential feed pool trailer}
|
|||
|
mov dword ptr [ebx - BlockHeaderSize], IsMediumBlockFlag
|
|||
|
{Store the number of bytes available in the sequential feed chunk}
|
|||
|
mov MediumSequentialFeedBytesLeft, MediumBlockPoolSize - MediumBlockPoolHeaderSize
|
|||
|
{Set the last sequentially fed block}
|
|||
|
mov LastSequentiallyFedMediumBlock, ebx
|
|||
|
{Unlock medium blocks}
|
|||
|
mov MediumBlocksLocked, False;
|
|||
|
{Success}
|
|||
|
xor eax, eax
|
|||
|
{Restore registers}
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@NotASmallOrMediumBlock:
|
|||
|
{Restore ebx}
|
|||
|
pop ebx
|
|||
|
{Is it in fact a large block?}
|
|||
|
test dl, IsFreeBlockFlag + IsMediumBlockFlag
|
|||
|
jz FreeLargeBlock
|
|||
|
{Attempt to free an already free block}
|
|||
|
mov eax, -1
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
{$ifndef ASMVersion}
|
|||
|
{Replacement for SysReallocMem (pascal version)}
|
|||
|
function FastReallocMem(APointer: Pointer; ANewSize: Integer): Pointer;
|
|||
|
var
|
|||
|
LBlockHeader, LBlockFlags, LOldAvailableSize, LNewAllocSize,
|
|||
|
LNextBlockSizeAndFlags, LNextBlockSize, LNewAvailableSize, LMinimumUpsize,
|
|||
|
LSecondSPlitSize, LNewBlockSize: Cardinal;
|
|||
|
LPSmallBlockType: PSmallBlockType;
|
|||
|
LPNextBlock, LPNextBlockHeader: Pointer;
|
|||
|
|
|||
|
{Upsizes a large block in-place. The following variables are assumed correct:
|
|||
|
LBlockFlags, LOldAvailableSize, LPNextBlock, LNextBlockSizeAndFlags,
|
|||
|
LNextBlockSize, LNewAvailableSize. Medium blocks must be locked on entry if
|
|||
|
required.}
|
|||
|
procedure MediumBlockInPlaceUpsize;
|
|||
|
begin
|
|||
|
{Remove the next block}
|
|||
|
if LNextBlockSizeAndFlags >= MinimumMediumBlockSize then
|
|||
|
RemoveMediumFreeBlock(LPNextBlock);
|
|||
|
{Add 25% for medium block in-place upsizes}
|
|||
|
LMinimumUpsize := LOldAvailableSize + (LOldAvailableSize shr 2);
|
|||
|
if Cardinal(ANewSize) < LMinimumUpsize then
|
|||
|
LNewAllocSize := LMinimumUpsize
|
|||
|
else
|
|||
|
LNewAllocSize := ANewSize;
|
|||
|
{Round up to the nearest block size granularity}
|
|||
|
LNewBlockSize := ((LNewAllocSize + (BlockHeaderSize + MediumBlockGranularity - 1 - MediumBlockSizeOffset)) and -MediumBlockGranularity) + MediumBlockSizeOffset;
|
|||
|
{Calculate the size of the second split}
|
|||
|
LSecondSplitSize := LNewAvailableSize + BlockHeaderSize - LNewBlockSize;
|
|||
|
{Does it fit?}
|
|||
|
if Integer(LSecondSplitSize) <= 0 then
|
|||
|
begin
|
|||
|
{The block size is the full available size plus header}
|
|||
|
LNewBlockSize := LNewAvailableSize + BlockHeaderSize;
|
|||
|
{Grab the whole block: Mark it as used in the block following it}
|
|||
|
LPNextBlockHeader := Pointer(Cardinal(APointer) + LNewAvailableSize);
|
|||
|
PCardinal(LPNextBlockHeader)^ :=
|
|||
|
PCardinal(LPNextBlockHeader)^ and (not PreviousMediumBlockIsFreeFlag);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Split the block in two}
|
|||
|
LPNextBlock := PMediumFreeBlock(Cardinal(APointer) + LNewBlockSize);
|
|||
|
{Set the size of the second split}
|
|||
|
PCardinal(Cardinal(LPNextBlock) - BlockHeaderSize)^ := LSecondSplitSize or (IsMediumBlockFlag or IsFreeBlockFlag);
|
|||
|
{Store the size of the second split as the second last dword}
|
|||
|
PCardinal(Cardinal(LPNextBlock) + LSecondSplitSize - 8)^ := LSecondSplitSize;
|
|||
|
{Put the remainder in a bin if it is big enough}
|
|||
|
if LSecondSplitSize >= MinimumMediumBlockSize then
|
|||
|
InsertMediumBlockIntoBin(LPNextBlock, LSecondSplitSize);
|
|||
|
end;
|
|||
|
{Set the size and flags for this block}
|
|||
|
PCardinal(Cardinal(APointer) - BlockHeaderSize)^ := LNewBlockSize or LBlockFlags;
|
|||
|
end;
|
|||
|
|
|||
|
{In-place downsize of a medium block. On entry ANewSize must be less than half
|
|||
|
of LOldAvailableSize.}
|
|||
|
procedure MediumBlockInPlaceDownsize;
|
|||
|
begin
|
|||
|
{Round up to the next medium block size}
|
|||
|
LNewBlockSize := ((ANewSize + (BlockHeaderSize + MediumBlockGranularity - 1 - MediumBlockSizeOffset)) and -MediumBlockGranularity) + MediumBlockSizeOffset;
|
|||
|
{Get the size of the second split}
|
|||
|
LSecondSplitSize := (LOldAvailableSize + BlockHeaderSize) - LNewBlockSize;
|
|||
|
{Lock the medium blocks}
|
|||
|
LockMediumBlocks;
|
|||
|
{Set the new size}
|
|||
|
PCardinal(Cardinal(APointer) - BlockHeaderSize)^ :=
|
|||
|
(PCardinal(Cardinal(APointer) - BlockHeaderSize)^ and ExtractMediumAndLargeFlagsMask)
|
|||
|
or LNewBlockSize;
|
|||
|
{Is the next block in use?}
|
|||
|
LPNextBlock := PCardinal(Cardinal(APointer) + LOldAvailableSize + BlockHeaderSize);
|
|||
|
LNextBlockSizeAndFlags := PCardinal(Cardinal(LPNextBlock) - BlockHeaderSize)^;
|
|||
|
if LNextBlockSizeAndFlags and IsFreeBlockFlag = 0 then
|
|||
|
begin
|
|||
|
{The next block is in use: flag its previous block as free}
|
|||
|
PCardinal(Cardinal(LPNextBlock) - BlockHeaderSize)^ :=
|
|||
|
LNextBlockSizeAndFlags or PreviousMediumBlockIsFreeFlag;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{The next block is free: combine it}
|
|||
|
LNextBlockSizeAndFlags := LNextBlockSizeAndFlags and DropMediumAndLargeFlagsMask;
|
|||
|
Inc(LSecondSplitSize, LNextBlockSizeAndFlags);
|
|||
|
if LNextBlockSizeAndFlags >= MinimumMediumBlockSize then
|
|||
|
RemoveMediumFreeBlock(LPNextBlock);
|
|||
|
end;
|
|||
|
{Set the split}
|
|||
|
LPNextBlock := PCardinal(Cardinal(APointer) + LNewBlockSize);
|
|||
|
{Store the free part's header}
|
|||
|
PCardinal(Cardinal(LPNextBlock) - BlockHeaderSize)^ := LSecondSplitSize or (IsMediumBlockFlag or IsFreeBlockFlag);
|
|||
|
{Store the trailing size field}
|
|||
|
PCardinal(Cardinal(LPNextBlock) + LSecondSplitSize - 8)^ := LSecondSplitSize;
|
|||
|
{Bin this free block}
|
|||
|
if LSecondSplitSize >= MinimumMediumBlockSize then
|
|||
|
InsertMediumBlockIntoBin(LPNextBlock, LSecondSplitSize);
|
|||
|
{Unlock the medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
{Get the block header: Is it actually a small block?}
|
|||
|
LBlockHeader := PCardinal(Cardinal(APointer) - BlockHeaderSize)^;
|
|||
|
{Is it a small block that is in use?}
|
|||
|
if LBlockHeader and (IsFreeBlockFlag or IsMediumBlockFlag or IsLargeBlockFlag) = 0 then
|
|||
|
begin
|
|||
|
{-----------------------------------Small block-------------------------------------}
|
|||
|
{The block header is a pointer to the block pool: Get the block type}
|
|||
|
LPSmallBlockType := PSmallBlockPoolHeader(LBlockHeader).BlockType;
|
|||
|
{Get the available size inside blocks of this type.}
|
|||
|
LOldAvailableSize := LPSmallBlockType.BlockSize - BlockHeaderSize;
|
|||
|
{Is it an upsize or a downsize?}
|
|||
|
if LOldAvailableSize >= Cardinal(ANewSize) then
|
|||
|
begin
|
|||
|
{It's a downsize. Do we need to allocate a smaller block? Only if the new
|
|||
|
block size is less than a quarter of the available size less
|
|||
|
SmallBlockDownsizeCheckAdder bytes}
|
|||
|
if (Cardinal(ANewSize) * 4 + SmallBlockDownsizeCheckAdder) >= LOldAvailableSize then
|
|||
|
begin
|
|||
|
{In-place downsize - return the pointer}
|
|||
|
Result := APointer;
|
|||
|
exit;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Allocate a smaller block}
|
|||
|
Result := FastGetMem(ANewSize);
|
|||
|
{Allocated OK?}
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
{Move the data across}
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
{$ifdef Align16Bytes}
|
|||
|
MoveX16L4(APointer^, Result^, ANewSize);
|
|||
|
{$else}
|
|||
|
MoveX8L4(APointer^, Result^, ANewSize);
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
System.Move(APointer^, Result^, ANewSize);
|
|||
|
{$endif}
|
|||
|
{Free the old pointer}
|
|||
|
FastFreeMem(APointer);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{This pointer is being reallocated to a larger block and therefore it is
|
|||
|
logical to assume that it may be enlarged again. Since reallocations are
|
|||
|
expensive, there is a minimum upsize percentage to avoid unnecessary
|
|||
|
future move operations.}
|
|||
|
{Must grow with at least 100% + x bytes}
|
|||
|
LNewAllocSize := LOldAvailableSize * 2 + SmallBlockUpsizeAdder;
|
|||
|
{Still not large enough?}
|
|||
|
if LNewAllocSize < Cardinal(ANewSize) then
|
|||
|
LNewAllocSize := ANewSize;
|
|||
|
{Allocate the new block}
|
|||
|
Result := FastGetMem(LNewAllocSize);
|
|||
|
{Allocated OK?}
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
{Do we need to store the requested size? Only large blocks store the
|
|||
|
requested size.}
|
|||
|
if LNewAllocSize > (MaximumMediumBlockSize - BlockHeaderSize) then
|
|||
|
PLargeBlockHeader(Cardinal(Result) - LargeBlockHeaderSize).UserAllocatedSize := ANewSize;
|
|||
|
{Move the data across}
|
|||
|
{$ifdef UseCustomFixedSizeMoveRoutines}
|
|||
|
LPSmallBlockType.UpsizeMoveProcedure(APointer^, Result^, LOldAvailableSize);
|
|||
|
{$else}
|
|||
|
System.Move(APointer^, Result^, LOldAvailableSize);
|
|||
|
{$endif}
|
|||
|
{Free the old pointer}
|
|||
|
FastFreeMem(APointer);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Is this a medium block or a large block?}
|
|||
|
if LBlockHeader and (IsFreeBlockFlag or IsLargeBlockFlag) = 0 then
|
|||
|
begin
|
|||
|
{-------------------------------Medium block--------------------------------------}
|
|||
|
{What is the available size in the block being reallocated?}
|
|||
|
LOldAvailableSize := (LBlockHeader and DropMediumAndLargeFlagsMask);
|
|||
|
{Get a pointer to the next block}
|
|||
|
LPNextBlock := PCardinal(Cardinal(APointer) + LOldAvailableSize);
|
|||
|
{Subtract the block header size from the old available size}
|
|||
|
Dec(LOldAvailableSize, BlockHeaderSize);
|
|||
|
{Is it an upsize or a downsize?}
|
|||
|
if Cardinal(ANewSize) > LOldAvailableSize then
|
|||
|
begin
|
|||
|
{Can we do an in-place upsize?}
|
|||
|
LNextBlockSizeAndFlags := PCardinal(Cardinal(LPNextBlock) - BlockHeaderSize)^;
|
|||
|
{Is the next block free?}
|
|||
|
if LNextBlockSizeAndFlags and IsFreeBlockFlag <> 0 then
|
|||
|
begin
|
|||
|
LNextBlockSize := LNextBlockSizeAndFlags and DropMediumAndLargeFlagsMask;
|
|||
|
{The available size including the next block}
|
|||
|
LNewAvailableSize := LOldAvailableSize + LNextBlockSize;
|
|||
|
{Can the block fit?}
|
|||
|
if Cardinal(ANewSize) <= LNewAvailableSize then
|
|||
|
begin
|
|||
|
{The next block is free and there is enough space to grow this
|
|||
|
block in place.}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
if IsMultiThread then
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
{Multi-threaded application - lock medium blocks and re-read the
|
|||
|
information on the blocks.}
|
|||
|
LockMediumBlocks;
|
|||
|
{Re-read the info for this block}
|
|||
|
LBlockFlags := PCardinal(Cardinal(APointer) - BlockHeaderSize)^ and ExtractMediumAndLargeFlagsMask;
|
|||
|
{Re-read the info for the next block}
|
|||
|
LNextBlockSizeAndFlags := PCardinal(Cardinal(LPNextBlock) - BlockHeaderSize)^;
|
|||
|
{Recalculate the next block size}
|
|||
|
LNextBlockSize := LNextBlockSizeAndFlags and DropMediumAndLargeFlagsMask;
|
|||
|
{The available size including the next block}
|
|||
|
LNewAvailableSize := LOldAvailableSize + LNextBlockSize;
|
|||
|
{Is the next block still free and the size still sufficient?}
|
|||
|
if (LNextBlockSizeAndFlags and IsFreeBlockFlag <> 0)
|
|||
|
and (Cardinal(ANewSize) <= LNewAvailableSize) then
|
|||
|
begin
|
|||
|
{Upsize the block in-place}
|
|||
|
MediumBlockInPlaceUpsize;
|
|||
|
{Unlock the medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{Return the result}
|
|||
|
Result := APointer;
|
|||
|
{Done}
|
|||
|
exit;
|
|||
|
end;
|
|||
|
{Couldn't use the block: Unlock the medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Extract the block flags}
|
|||
|
LBlockFlags := ExtractMediumAndLargeFlagsMask and LBlockHeader;
|
|||
|
{Upsize the block in-place}
|
|||
|
MediumBlockInPlaceUpsize;
|
|||
|
{Return the result}
|
|||
|
Result := APointer;
|
|||
|
{Done}
|
|||
|
exit;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Couldn't upsize in place. Grab a new block and move the data across:
|
|||
|
If we have to reallocate and move medium blocks, we grow by at
|
|||
|
least 25%}
|
|||
|
LMinimumUpsize := LOldAvailableSize + (LOldAvailableSize shr 2);
|
|||
|
if Cardinal(ANewSize) < LMinimumUpsize then
|
|||
|
LNewAllocSize := LMinimumUpsize
|
|||
|
else
|
|||
|
LNewAllocSize := ANewSize;
|
|||
|
{Allocate the new block}
|
|||
|
Result := FastGetMem(LNewAllocSize);
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
{If its a Large block - store the actual user requested size}
|
|||
|
if LNewAllocSize > (MaximumMediumBlockSize - BlockHeaderSize) then
|
|||
|
PLargeBlockHeader(Cardinal(Result) - LargeBlockHeaderSize).UserAllocatedSize := ANewSize;
|
|||
|
{Move the data across}
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
MoveX16L4(APointer^, Result^, LOldAvailableSize);
|
|||
|
{$else}
|
|||
|
System.Move(APointer^, Result^, LOldAvailableSize);
|
|||
|
{$endif}
|
|||
|
{Free the old block}
|
|||
|
FastFreeMem(APointer);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Must be less than half the current size or we don't bother resizing.}
|
|||
|
if Cardinal(ANewSize * 2) >= LOldAvailableSize then
|
|||
|
begin
|
|||
|
Result := APointer;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{In-place downsize? Balance the cost of moving the data vs. the cost
|
|||
|
of fragmenting the memory pool. Medium blocks in use may never be
|
|||
|
smaller than MinimumMediumBlockSize.}
|
|||
|
if ANewSize >= (MinimumMediumBlockSize - BlockHeaderSize) then
|
|||
|
begin
|
|||
|
MediumBlockInPlaceDownsize;
|
|||
|
Result := APointer;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{The requested size is less than the minimum medium block size. If
|
|||
|
the requested size is less than the threshold value (currently a
|
|||
|
quarter of the minimum medium block size), move the data to a small
|
|||
|
block, otherwise shrink the medium block to the minimum allowable
|
|||
|
medium block size.}
|
|||
|
if Cardinal(ANewSize) >= MediumInPlaceDownsizeLimit then
|
|||
|
begin
|
|||
|
{The request is for a size smaller than the minimum medium block
|
|||
|
size, but not small enough to justify moving data: Reduce the
|
|||
|
block size to the minimum medium block size}
|
|||
|
ANewSize := MinimumMediumBlockSize - BlockHeaderSize;
|
|||
|
{Is it already at the minimum medium block size?}
|
|||
|
if LOldAvailableSize > Cardinal(ANewSize) then
|
|||
|
MediumBlockInPlaceDownsize;
|
|||
|
Result := APointer;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Allocate the new block}
|
|||
|
Result := FastGetMem(ANewSize);
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
{Move the data across}
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
{$ifdef Align16Bytes}
|
|||
|
MoveX16L4(APointer^, Result^, ANewSize);
|
|||
|
{$else}
|
|||
|
MoveX8L4(APointer^, Result^, ANewSize);
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
System.Move(APointer^, Result^, ANewSize);
|
|||
|
{$endif}
|
|||
|
{Free the old block}
|
|||
|
FastFreeMem(APointer);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Is this a valid large block?}
|
|||
|
if LBlockHeader and (IsFreeBlockFlag or IsMediumBlockFlag) = 0 then
|
|||
|
begin
|
|||
|
{-----------------------Large block------------------------------}
|
|||
|
Result := ReallocateLargeBlock(APointer, ANewSize);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{-----------------------Invalid block------------------------------}
|
|||
|
{Bad pointer: probable attempt to reallocate a free memory block.}
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$else}
|
|||
|
{Replacement for SysReallocMem (asm version)}
|
|||
|
function FastReallocMem(APointer: Pointer; ANewSize: Integer): Pointer;
|
|||
|
asm
|
|||
|
{On entry: eax = APointer; edx = ANewSize}
|
|||
|
{Get the block header: Is it actually a small block?}
|
|||
|
mov ecx, [eax - 4]
|
|||
|
{Is it a small block?}
|
|||
|
test cl, IsFreeBlockFlag + IsMediumBlockFlag + IsLargeBlockFlag
|
|||
|
{Save ebx}
|
|||
|
push ebx
|
|||
|
{Save esi}
|
|||
|
push esi
|
|||
|
{Save the original pointer in esi}
|
|||
|
mov esi, eax
|
|||
|
{Is it a small block?}
|
|||
|
jnz @NotASmallBlock
|
|||
|
{-----------------------------------Small block-------------------------------------}
|
|||
|
{Get the block type in ebx}
|
|||
|
mov ebx, TSmallBlockPoolHeader[ecx].BlockType
|
|||
|
{Get the available size inside blocks of this type.}
|
|||
|
movzx ecx, TSmallBlockType[ebx].BlockSize
|
|||
|
sub ecx, 4
|
|||
|
{Is it an upsize or a downsize?}
|
|||
|
cmp ecx, edx
|
|||
|
jb @SmallUpsize
|
|||
|
{It's a downsize. Do we need to allocate a smaller block? Only if the new
|
|||
|
size is less than a quarter of the available size less
|
|||
|
SmallBlockDownsizeCheckAdder bytes}
|
|||
|
lea ebx, [edx * 4 + SmallBlockDownsizeCheckAdder]
|
|||
|
cmp ebx, ecx
|
|||
|
jb @NotSmallInPlaceDownsize
|
|||
|
{In-place downsize - return the original pointer}
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
@NotSmallInPlaceDownsize:
|
|||
|
{Save the requested size}
|
|||
|
mov ebx, edx
|
|||
|
{Allocate a smaller block}
|
|||
|
mov eax, edx
|
|||
|
call FastGetMem
|
|||
|
{Allocated OK?}
|
|||
|
test eax, eax
|
|||
|
jz @SmallDownsizeDone
|
|||
|
{Move data across: count in ecx}
|
|||
|
mov ecx, ebx
|
|||
|
{Destination in edx}
|
|||
|
mov edx, eax
|
|||
|
{Save the result in ebx}
|
|||
|
mov ebx, eax
|
|||
|
{Original pointer in eax}
|
|||
|
mov eax, esi
|
|||
|
{Move the data across}
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
{$ifdef Align16Bytes}
|
|||
|
call MoveX16L4
|
|||
|
{$else}
|
|||
|
call MoveX8L4
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
call System.Move
|
|||
|
{$endif}
|
|||
|
{Free the original pointer}
|
|||
|
mov eax, esi
|
|||
|
call FastFreeMem
|
|||
|
{Return the pointer}
|
|||
|
mov eax, ebx
|
|||
|
@SmallDownsizeDone:
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@SmallUpsize:
|
|||
|
{State: esi = APointer, edx = ANewSize, ecx = Current Block Size, ebx = Current Block Type}
|
|||
|
{This pointer is being reallocated to a larger block and therefore it is
|
|||
|
logical to assume that it may be enlarged again. Since reallocations are
|
|||
|
expensive, there is a minimum upsize percentage to avoid unnecessary
|
|||
|
future move operations.}
|
|||
|
{Small blocks always grow with at least 100% + SmallBlockUpsizeAdder bytes}
|
|||
|
lea ecx, [ecx + ecx + SmallBlockUpsizeAdder]
|
|||
|
{save edi}
|
|||
|
push edi
|
|||
|
{Save the requested size in edi}
|
|||
|
mov edi, edx
|
|||
|
{New allocated size is the maximum of the requested size and the minimum
|
|||
|
upsize}
|
|||
|
xor eax, eax
|
|||
|
sub ecx, edx
|
|||
|
adc eax, -1
|
|||
|
and eax, ecx
|
|||
|
add eax, edx
|
|||
|
{Allocate the new block}
|
|||
|
call FastGetMem
|
|||
|
{Allocated OK?}
|
|||
|
test eax, eax
|
|||
|
jz @SmallUpsizeDone
|
|||
|
{Do we need to store the requested size? Only large blocks store the
|
|||
|
requested size.}
|
|||
|
cmp edi, MaximumMediumBlockSize - BlockHeaderSize
|
|||
|
jbe @NotSmallUpsizeToLargeBlock
|
|||
|
{Store the user requested size}
|
|||
|
mov [eax - 8], edi
|
|||
|
@NotSmallUpsizeToLargeBlock:
|
|||
|
{Get the size to move across}
|
|||
|
movzx ecx, TSmallBlockType[ebx].BlockSize
|
|||
|
sub ecx, BlockHeaderSize
|
|||
|
{Move to the new block}
|
|||
|
mov edx, eax
|
|||
|
{Save the result in edi}
|
|||
|
mov edi, eax
|
|||
|
{Move from the old block}
|
|||
|
mov eax, esi
|
|||
|
{Move the data across}
|
|||
|
{$ifdef UseCustomFixedSizeMoveRoutines}
|
|||
|
call TSmallBlockType[ebx].UpsizeMoveProcedure
|
|||
|
{$else}
|
|||
|
call System.Move
|
|||
|
{$endif}
|
|||
|
{Free the old pointer}
|
|||
|
mov eax, esi
|
|||
|
call FastFreeMem
|
|||
|
{Done}
|
|||
|
mov eax, edi
|
|||
|
@SmallUpsizeDone:
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
@NotASmallBlock:
|
|||
|
{Is this a medium block or a large block?}
|
|||
|
test cl, IsFreeBlockFlag + IsLargeBlockFlag
|
|||
|
jnz @PossibleLargeBlock
|
|||
|
{-------------------------------Medium block--------------------------------------}
|
|||
|
{Status: ecx = Current Block Size + Flags, eax/esi = APointer,
|
|||
|
edx = Requested Size}
|
|||
|
mov ebx, ecx
|
|||
|
{Drop the flags from the header}
|
|||
|
and ecx, DropMediumAndLargeFlagsMask
|
|||
|
{Save edi}
|
|||
|
push edi
|
|||
|
{Get a pointer to the next block in edi}
|
|||
|
lea edi, [eax + ecx]
|
|||
|
{Subtract the block header size from the old available size}
|
|||
|
sub ecx, BlockHeaderSize
|
|||
|
{Get the complete flags in ebx}
|
|||
|
and ebx, ExtractMediumAndLargeFlagsMask
|
|||
|
{Is it an upsize or a downsize?}
|
|||
|
cmp edx, ecx
|
|||
|
{Save ebp}
|
|||
|
push ebp
|
|||
|
{Is it an upsize or a downsize?}
|
|||
|
ja @MediumBlockUpsize
|
|||
|
{Status: ecx = Current Block Size - 4, bl = Current Block Flags,
|
|||
|
edi = @Next Block, eax/esi = APointer, edx = Requested Size}
|
|||
|
{Must be less than half the current size or we don't bother resizing.}
|
|||
|
lea ebp, [edx + edx]
|
|||
|
cmp ebp, ecx
|
|||
|
jb @MediumMustDownsize
|
|||
|
@MediumNoResize:
|
|||
|
{Restore registers}
|
|||
|
pop ebp
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
{Return}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
nop
|
|||
|
@MediumMustDownsize:
|
|||
|
{In-place downsize? Balance the cost of moving the data vs. the cost of
|
|||
|
fragmenting the memory pool. Medium blocks in use may never be smaller
|
|||
|
than MinimumMediumBlockSize.}
|
|||
|
cmp edx, MinimumMediumBlockSize - BlockHeaderSize
|
|||
|
jae @MediumBlockInPlaceDownsize
|
|||
|
{The requested size is less than the minimum medium block size. If the
|
|||
|
requested size is less than the threshold value (currently a quarter of the
|
|||
|
minimum medium block size), move the data to a small block, otherwise shrink
|
|||
|
the medium block to the minimum allowable medium block size.}
|
|||
|
cmp edx, MediumInPlaceDownsizeLimit
|
|||
|
jb @MediumDownsizeRealloc
|
|||
|
{The request is for a size smaller than the minimum medium block size, but
|
|||
|
not small enough to justify moving data: Reduce the block size to the
|
|||
|
minimum medium block size}
|
|||
|
mov edx, MinimumMediumBlockSize - BlockHeaderSize
|
|||
|
{Is it already at the minimum medium block size?}
|
|||
|
cmp ecx, edx
|
|||
|
jna @MediumNoResize
|
|||
|
@MediumBlockInPlaceDownsize:
|
|||
|
{Round up to the next medium block size}
|
|||
|
lea ebp, [edx + BlockHeaderSize + MediumBlockGranularity - 1 - MediumBlockSizeOffset]
|
|||
|
and ebp, -MediumBlockGranularity;
|
|||
|
add ebp, MediumBlockSizeOffset
|
|||
|
{Get the size of the second split}
|
|||
|
add ecx, BlockHeaderSize
|
|||
|
sub ecx, ebp
|
|||
|
{Lock the medium blocks}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
cmp IsMultiThread, False
|
|||
|
je @DoMediumInPlaceDownsize
|
|||
|
{$endif}
|
|||
|
{We have to re-read the flags}
|
|||
|
@DoMediumLockForDownsize:
|
|||
|
{Lock the medium blocks}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to lock the medium blocks}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @MediumDownsizeRereadFlags
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push ecx
|
|||
|
push InitialSleepTime
|
|||
|
call Sleep
|
|||
|
pop ecx
|
|||
|
{Try again}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to grab the block type}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @MediumDownsizeRereadFlags
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push ecx
|
|||
|
push AdditionalSleepTime
|
|||
|
call Sleep
|
|||
|
pop ecx
|
|||
|
{Try again}
|
|||
|
jmp @DoMediumLockForDownsize
|
|||
|
{Align branch target}
|
|||
|
{$ifdef AssumeMultiThreaded}
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
@MediumDownsizeRereadFlags:
|
|||
|
mov ebx, ExtractMediumAndLargeFlagsMask
|
|||
|
and ebx, [esi - 4]
|
|||
|
@DoMediumInPlaceDownsize:
|
|||
|
{Set the new size}
|
|||
|
or ebx, ebp
|
|||
|
mov [esi - 4], ebx
|
|||
|
{Get the second split size in ebx}
|
|||
|
mov ebx, ecx
|
|||
|
{Is the next block in use?}
|
|||
|
mov edx, [edi - 4]
|
|||
|
test dl, IsFreeBlockFlag
|
|||
|
jnz @MediumDownsizeNextBlockFree
|
|||
|
{The next block is in use: flag its previous block as free}
|
|||
|
or edx, PreviousMediumBlockIsFreeFlag
|
|||
|
mov [edi - 4], edx
|
|||
|
jmp @MediumDownsizeDoSplit
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
@MediumDownsizeNextBlockFree:
|
|||
|
{The next block is free: combine it}
|
|||
|
mov eax, edi
|
|||
|
and edx, DropMediumAndLargeFlagsMask
|
|||
|
add ebx, edx
|
|||
|
add edi, edx
|
|||
|
cmp edx, MinimumMediumBlockSize
|
|||
|
jb @MediumDownsizeDoSplit
|
|||
|
call RemoveMediumFreeBlock
|
|||
|
@MediumDownsizeDoSplit:
|
|||
|
{Store the trailing size field}
|
|||
|
mov [edi - 8], ebx
|
|||
|
{Store the free part's header}
|
|||
|
lea eax, [ebx + IsMediumBlockFlag + IsFreeBlockFlag];
|
|||
|
mov [esi + ebp - 4], eax
|
|||
|
{Bin this free block}
|
|||
|
cmp ebx, MinimumMediumBlockSize
|
|||
|
jb @MediumBlockDownsizeDone
|
|||
|
lea eax, [esi + ebp]
|
|||
|
mov edx, ebx
|
|||
|
call InsertMediumBlockIntoBin
|
|||
|
@MediumBlockDownsizeDone:
|
|||
|
{Unlock the medium blocks}
|
|||
|
mov MediumBlocksLocked, False
|
|||
|
{Result = old pointer}
|
|||
|
mov eax, esi
|
|||
|
{Restore registers}
|
|||
|
pop ebp
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
{Return}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
@MediumDownsizeRealloc:
|
|||
|
{Save the requested size}
|
|||
|
mov edi, edx
|
|||
|
mov eax, edx
|
|||
|
{Allocate the new block}
|
|||
|
call FastGetMem
|
|||
|
test eax, eax
|
|||
|
jz @MediumBlockDownsizeExit
|
|||
|
{Save the result}
|
|||
|
mov ebp, eax
|
|||
|
mov edx, eax
|
|||
|
mov eax, esi
|
|||
|
mov ecx, edi
|
|||
|
{Move the data across}
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
{$ifdef Align16Bytes}
|
|||
|
call MoveX16L4
|
|||
|
{$else}
|
|||
|
call MoveX8L4
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
call System.Move
|
|||
|
{$endif}
|
|||
|
mov eax, esi
|
|||
|
call FastFreeMem
|
|||
|
{Return the result}
|
|||
|
mov eax, ebp
|
|||
|
@MediumBlockDownsizeExit:
|
|||
|
pop ebp
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
@MediumBlockUpsize:
|
|||
|
{Status: ecx = Current Block Size - 4, bl = Current Block Flags,
|
|||
|
edi = @Next Block, eax/esi = APointer, edx = Requested Size}
|
|||
|
{Can we do an in-place upsize?}
|
|||
|
mov eax, [edi - 4]
|
|||
|
test al, IsFreeBlockFlag
|
|||
|
jz @CannotUpsizeMediumBlockInPlace
|
|||
|
{Get the total available size including the next block}
|
|||
|
and eax, DropMediumAndLargeFlagsMask
|
|||
|
{ebp = total available size including the next block (excluding the header)}
|
|||
|
lea ebp, [eax + ecx]
|
|||
|
{Can the block fit?}
|
|||
|
cmp edx, ebp
|
|||
|
ja @CannotUpsizeMediumBlockInPlace
|
|||
|
{The next block is free and there is enough space to grow this
|
|||
|
block in place.}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
cmp IsMultiThread, False
|
|||
|
je @DoMediumInPlaceUpsize
|
|||
|
{$endif}
|
|||
|
@DoMediumLockForUpsize:
|
|||
|
{Lock the medium blocks}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to lock the medium blocks}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @RecheckMediumInPlaceUpsize
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push ecx
|
|||
|
push edx
|
|||
|
push InitialSleepTime
|
|||
|
call Sleep
|
|||
|
pop edx
|
|||
|
pop ecx
|
|||
|
{Try again}
|
|||
|
mov eax, $100
|
|||
|
{Attempt to grab the block type}
|
|||
|
lock cmpxchg MediumBlocksLocked, ah
|
|||
|
je @RecheckMediumInPlaceUpsize
|
|||
|
{Couldn't lock the medium blocks - sleep and try again}
|
|||
|
push ecx
|
|||
|
push edx
|
|||
|
push AdditionalSleepTime
|
|||
|
call Sleep
|
|||
|
pop edx
|
|||
|
pop ecx
|
|||
|
{Try again}
|
|||
|
jmp @DoMediumLockForUpsize
|
|||
|
{Align branch target}
|
|||
|
{$ifdef AssumeMultiThreaded}
|
|||
|
nop
|
|||
|
{$endif}
|
|||
|
@RecheckMediumInPlaceUpsize:
|
|||
|
{Re-read the info for this block}
|
|||
|
mov ebx, ExtractMediumAndLargeFlagsMask
|
|||
|
and ebx, [esi - 4]
|
|||
|
{Re-read the info for the next block}
|
|||
|
mov eax, [edi - 4]
|
|||
|
{Next block still free?}
|
|||
|
test al, IsFreeBlockFlag
|
|||
|
jz @NextMediumBlockChanged
|
|||
|
{Recalculate the next block size}
|
|||
|
and eax, DropMediumAndLargeFlagsMask
|
|||
|
{The available size including the next block}
|
|||
|
lea ebp, [eax + ecx]
|
|||
|
{Can the block still fit?}
|
|||
|
cmp edx, ebp
|
|||
|
ja @NextMediumBlockChanged
|
|||
|
@DoMediumInPlaceUpsize:
|
|||
|
{Is the next block binnable?}
|
|||
|
cmp eax, MinimumMediumBlockSize
|
|||
|
{Remove the next block}
|
|||
|
jb @MediumInPlaceNoNextRemove
|
|||
|
mov eax, edi
|
|||
|
push ecx
|
|||
|
push edx
|
|||
|
call RemoveMediumFreeBlock
|
|||
|
pop edx
|
|||
|
pop ecx
|
|||
|
@MediumInPlaceNoNextRemove:
|
|||
|
{Medium blocks grow a minimum of 25% in in-place upsizes}
|
|||
|
mov eax, ecx
|
|||
|
shr eax, 2
|
|||
|
add eax, ecx
|
|||
|
{Get the maximum of the requested size and the minimum growth size}
|
|||
|
xor edi, edi
|
|||
|
sub eax, edx
|
|||
|
adc edi, -1
|
|||
|
and eax, edi
|
|||
|
{Round up to the nearest block size granularity}
|
|||
|
lea eax, [eax + edx + BlockHeaderSize + MediumBlockGranularity - 1 - MediumBlockSizeOffset]
|
|||
|
and eax, -MediumBlockGranularity
|
|||
|
add eax, MediumBlockSizeOffset
|
|||
|
{Calculate the size of the second split}
|
|||
|
lea edx, [ebp + BlockHeaderSize]
|
|||
|
sub edx, eax
|
|||
|
{Does it fit?}
|
|||
|
ja @MediumInPlaceUpsizeSplit
|
|||
|
{Grab the whole block: Mark it as used in the block following it}
|
|||
|
and dword ptr [esi + ebp], not PreviousMediumBlockIsFreeFlag
|
|||
|
{The block size is the full available size plus header}
|
|||
|
add ebp, 4
|
|||
|
{Upsize done}
|
|||
|
jmp @MediumUpsizeInPlaceDone
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@MediumInPlaceUpsizeSplit:
|
|||
|
{Store the size of the second split as the second last dword}
|
|||
|
mov [esi + ebp - 4], edx
|
|||
|
{Set the second split header}
|
|||
|
lea edi, [edx + IsMediumBlockFlag + IsFreeBlockFlag]
|
|||
|
mov [esi + eax - 4], edi
|
|||
|
mov ebp, eax
|
|||
|
cmp edx, MinimumMediumBlockSize
|
|||
|
jb @MediumUpsizeInPlaceDone
|
|||
|
add eax, esi
|
|||
|
call InsertMediumBlockIntoBin
|
|||
|
@MediumUpsizeInPlaceDone:
|
|||
|
{Set the size and flags for this block}
|
|||
|
or ebp, ebx
|
|||
|
mov [esi - 4], ebp
|
|||
|
{Unlock the medium blocks}
|
|||
|
mov MediumBlocksLocked, False
|
|||
|
{Result = old pointer}
|
|||
|
mov eax, esi
|
|||
|
@MediumBlockResizeDone2:
|
|||
|
{Restore registers}
|
|||
|
pop ebp
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
{Return}
|
|||
|
ret
|
|||
|
{Align branch target for "@CannotUpsizeMediumBlockInPlace"}
|
|||
|
nop
|
|||
|
nop
|
|||
|
@NextMediumBlockChanged:
|
|||
|
{The next medium block changed while the medium blocks were being locked}
|
|||
|
mov MediumBlocksLocked, False
|
|||
|
@CannotUpsizeMediumBlockInPlace:
|
|||
|
{Couldn't upsize in place. Grab a new block and move the data across:
|
|||
|
If we have to reallocate and move medium blocks, we grow by at
|
|||
|
least 25%}
|
|||
|
mov eax, ecx
|
|||
|
shr eax, 2
|
|||
|
add eax, ecx
|
|||
|
{Get the maximum of the requested size and the minimum growth size}
|
|||
|
xor edi, edi
|
|||
|
sub eax, edx
|
|||
|
adc edi, -1
|
|||
|
and eax, edi
|
|||
|
add eax, edx
|
|||
|
{Save the size to allocate}
|
|||
|
mov ebp, eax
|
|||
|
{Save the size to move across}
|
|||
|
mov edi, ecx
|
|||
|
{Get the block}
|
|||
|
push edx
|
|||
|
call FastGetMem
|
|||
|
pop edx
|
|||
|
{Success?}
|
|||
|
test eax, eax
|
|||
|
jz @MediumBlockResizeDone2
|
|||
|
{If it's a Large block - store the actual user requested size}
|
|||
|
cmp ebp, MaximumMediumBlockSize - BlockHeaderSize
|
|||
|
jbe @MediumUpsizeNotLarge
|
|||
|
mov [eax - 8], edx
|
|||
|
@MediumUpsizeNotLarge:
|
|||
|
{Save the result}
|
|||
|
mov ebp, eax
|
|||
|
{Move the data across}
|
|||
|
mov edx, eax
|
|||
|
mov eax, esi
|
|||
|
mov ecx, edi
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
call MoveX16L4
|
|||
|
{$else}
|
|||
|
call System.Move
|
|||
|
{$endif}
|
|||
|
{Free the old block}
|
|||
|
mov eax, esi
|
|||
|
call FastFreeMem
|
|||
|
{Restore the result}
|
|||
|
mov eax, ebp
|
|||
|
{Restore registers}
|
|||
|
pop ebp
|
|||
|
pop edi
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
{Return}
|
|||
|
ret
|
|||
|
{Align branch target}
|
|||
|
nop
|
|||
|
@PossibleLargeBlock:
|
|||
|
{-----------------------Large block------------------------------}
|
|||
|
{Restore registers}
|
|||
|
pop esi
|
|||
|
pop ebx
|
|||
|
{Is this a valid large block?}
|
|||
|
test cl, IsFreeBlockFlag + IsMediumBlockFlag
|
|||
|
jz ReallocateLargeBlock
|
|||
|
{-----------------------Invalid block------------------------------}
|
|||
|
xor eax, eax
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Allocates a block and fills it with zeroes}
|
|||
|
{$ifndef ASMVersion}
|
|||
|
function FastAllocMem(ASize: Cardinal): Pointer;
|
|||
|
begin
|
|||
|
Result := FastGetMem(ASize);
|
|||
|
{Large blocks are already zero filled}
|
|||
|
if (Result <> nil) and (ASize <= (MaximumMediumBlockSize - BlockHeaderSize)) then
|
|||
|
FillChar(Result^, ASize, 0);
|
|||
|
end;
|
|||
|
{$else}
|
|||
|
function FastAllocMem(ASize: Cardinal): Pointer;
|
|||
|
asm
|
|||
|
push ebx
|
|||
|
{Get the size rounded down to the previous multiple of 4 into ebx}
|
|||
|
lea ebx, [eax - 1]
|
|||
|
and ebx, -4
|
|||
|
{Get the block}
|
|||
|
call FastGetMem
|
|||
|
{Could a block be allocated? ecx = 0 if yes, $ffffffff if no}
|
|||
|
cmp eax, 1
|
|||
|
sbb ecx, ecx
|
|||
|
{Point edx to the last dword}
|
|||
|
lea edx, [eax + ebx]
|
|||
|
{ebx = $ffffffff if no block could be allocated, otherwise size rounded down
|
|||
|
to previous multiple of 4}
|
|||
|
or ebx, ecx
|
|||
|
{Large blocks are already zero filled}
|
|||
|
cmp ebx, MaximumMediumBlockSize - BlockHeaderSize
|
|||
|
jae @Done
|
|||
|
{Make the counter negative based}
|
|||
|
neg ebx
|
|||
|
{Load zero into st(0)}
|
|||
|
fldz
|
|||
|
{Clear groups of 8 bytes. Block sizes are always four less than a multiple
|
|||
|
of 8, with a minimum of 12 bytes}
|
|||
|
@FillLoop:
|
|||
|
fst qword ptr [edx + ebx]
|
|||
|
add ebx, 8
|
|||
|
js @FillLoop
|
|||
|
{Clear the last four bytes}
|
|||
|
mov [edx], ecx
|
|||
|
{Clear st(0)}
|
|||
|
ffree st(0)
|
|||
|
@Done:
|
|||
|
pop ebx
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-----------------Post Uninstall GetMem/FreeMem/ReallocMem-------------------}
|
|||
|
|
|||
|
{$ifdef DetectMMOperationsAfterUninstall}
|
|||
|
|
|||
|
function InvalidGetMem(ASize: Integer): Pointer;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
var
|
|||
|
LErrorMessageTitle: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(InvalidGetMemMsg);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
AppendStringToModuleName(InvalidOperationTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, InvalidGetMemMsg, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
|
|||
|
function InvalidFreeMem(APointer: Pointer): Integer;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
var
|
|||
|
LErrorMessageTitle: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(InvalidFreeMemMsg);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
AppendStringToModuleName(InvalidOperationTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, InvalidFreeMemMsg, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
Result := -1;
|
|||
|
end;
|
|||
|
|
|||
|
function InvalidReallocMem(APointer: Pointer; ANewSize: Integer): Pointer;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
var
|
|||
|
LErrorMessageTitle: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(InvalidReallocMemMsg);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
AppendStringToModuleName(InvalidOperationTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, InvalidReallocMemMsg, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
|
|||
|
function InvalidAllocMem(ASize: Cardinal): Pointer;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
var
|
|||
|
LErrorMessageTitle: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(InvalidAllocMemMsg);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
AppendStringToModuleName(InvalidOperationTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, InvalidAllocMemMsg, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
|
|||
|
function InvalidRegisterAndUnRegisterMemoryLeak(APointer: Pointer): Boolean;
|
|||
|
begin
|
|||
|
Result := False;
|
|||
|
end;
|
|||
|
|
|||
|
{$endif}
|
|||
|
|
|||
|
{-----------------Full Debug Mode Memory Manager Interface--------------------}
|
|||
|
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
|
|||
|
procedure DeleteEventLog;
|
|||
|
begin
|
|||
|
{Delete the file}
|
|||
|
DeleteFile(MMLogFileName);
|
|||
|
end;
|
|||
|
|
|||
|
procedure AppendEventLog(ABuffer: Pointer; ACount: Cardinal);
|
|||
|
var
|
|||
|
LFileHandle, LBytesWritten: Cardinal;
|
|||
|
LEventHeader: array[0..1023] of char;
|
|||
|
LMsgPtr: PChar;
|
|||
|
LSystemTime: TSystemTime;
|
|||
|
begin
|
|||
|
{Append the file}
|
|||
|
LFileHandle := CreateFile(MMLogFileName, GENERIC_READ or GENERIC_WRITE,
|
|||
|
0, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
|||
|
if LFileHandle <> 0 then
|
|||
|
begin
|
|||
|
{Seek to the end of the file}
|
|||
|
SetFilePointer(LFileHandle, 0, nil, FILE_END);
|
|||
|
{Set the separator}
|
|||
|
LMsgPtr := AppendStringToBuffer(CRLF, @LEventHeader[0], length(CRLF));
|
|||
|
LMsgPtr := AppendStringToBuffer(EventSeparator, LMsgPtr, length(EventSeparator));
|
|||
|
{Set the date & time}
|
|||
|
GetLocalTime(LSystemTime);
|
|||
|
LMsgPtr := CardinalToStrBuf(LSystemTime.wYear, LMsgPtr);
|
|||
|
LMsgPtr^ := '/';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr := CardinalToStrBuf(LSystemTime.wMonth, LMsgPtr);
|
|||
|
LMsgPtr^ := '/';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr := CardinalToStrBuf(LSystemTime.wDay, LMsgPtr);
|
|||
|
LMsgPtr^ := ' ';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr := CardinalToStrBuf(LSystemTime.wHour, LMsgPtr);
|
|||
|
LMsgPtr^ := ':';
|
|||
|
Inc(LMsgPtr);
|
|||
|
if LSystemTime.wMinute < 10 then
|
|||
|
begin
|
|||
|
LMsgPtr^ := '0';
|
|||
|
Inc(LMsgPtr);
|
|||
|
end;
|
|||
|
LMsgPtr := CardinalToStrBuf(LSystemTime.wMinute, LMsgPtr);
|
|||
|
LMsgPtr^ := ':';
|
|||
|
Inc(LMsgPtr);
|
|||
|
if LSystemTime.wSecond < 10 then
|
|||
|
begin
|
|||
|
LMsgPtr^ := '0';
|
|||
|
Inc(LMsgPtr);
|
|||
|
end;
|
|||
|
LMsgPtr := CardinalToStrBuf(LSystemTime.WSecond, LMsgPtr);
|
|||
|
{Write the header}
|
|||
|
LMsgPtr := AppendStringToBuffer(EventSeparator, LMsgPtr, length(EventSeparator));
|
|||
|
LMsgPtr := AppendStringToBuffer(CRLF, LMsgPtr, length(CRLF));
|
|||
|
WriteFile(LFileHandle, LEventHeader[0], Cardinal(LMsgPtr) - Cardinal(@LEventHeader[0]), LBytesWritten, nil);
|
|||
|
{Write the data}
|
|||
|
WriteFile(LFileHandle, ABuffer^, ACount, LBytesWritten, nil);
|
|||
|
{Close the file}
|
|||
|
CloseHandle(LFileHandle);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Sets the default log filename}
|
|||
|
procedure SetDefaultMMLogFileName;
|
|||
|
var
|
|||
|
LModuleNameLength: Cardinal;
|
|||
|
begin
|
|||
|
{Get the name of the application}
|
|||
|
LModuleNameLength := GetModuleFileName(0, @MMLogFileName[0], length(MMLogFileName) - 100);
|
|||
|
{Replace the last few characters}
|
|||
|
if LModuleNameLength > 0 then
|
|||
|
begin
|
|||
|
{Change the filename}
|
|||
|
System.Move(LogFileExtension, MMLogFileName[LModuleNameLength - 4], Length(LogFileExtension));
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Specify the full path and name for the filename to be used for logging memory
|
|||
|
errors, etc. If ALogFileName is nil or points to an empty string it will
|
|||
|
revert to the default log file name.}
|
|||
|
procedure SetMMLogFileName(ALogFileName: PChar = nil);
|
|||
|
var
|
|||
|
i: integer;
|
|||
|
begin
|
|||
|
if (ALogFileName <> nil) and (ALogFileName^ <> #0) then
|
|||
|
begin
|
|||
|
for i := 0 to length(MMLogFileName) - 2 do
|
|||
|
begin
|
|||
|
MMLogFileName[i] := ALogFileName^;
|
|||
|
if MMlogFileName[i] = #0 then
|
|||
|
break;
|
|||
|
Inc(ALogFileName);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
SetDefaultMMLogFileName;
|
|||
|
end;
|
|||
|
|
|||
|
{Returns the current "allocation group". Whenever a GetMem request is serviced
|
|||
|
in FullDebugMode, the current "allocation group" is stored in the block header.
|
|||
|
This may help with debugging. Note that if a block is subsequently reallocated
|
|||
|
that it keeps its original "allocation group" and "allocation number" (all
|
|||
|
allocations are also numbered sequentially).}
|
|||
|
function GetCurrentAllocationGroup: Cardinal;
|
|||
|
begin
|
|||
|
Result := AllocationGroupStack[AllocationGroupStackTop];
|
|||
|
end;
|
|||
|
|
|||
|
{Allocation groups work in a stack like fashion. Group numbers are pushed onto
|
|||
|
and popped off the stack. Note that the stack size is limited, so every push
|
|||
|
should have a matching pop.}
|
|||
|
procedure PushAllocationGroup(ANewCurrentAllocationGroup: Cardinal);
|
|||
|
begin
|
|||
|
if AllocationGroupStackTop < AllocationGroupStackSize - 1 then
|
|||
|
begin
|
|||
|
Inc(AllocationGroupStackTop);
|
|||
|
AllocationGroupStack[AllocationGroupStackTop] := ANewCurrentAllocationGroup;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Raise a runtime error if the stack overflows}
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure PopAllocationGroup;
|
|||
|
begin
|
|||
|
if AllocationGroupStackTop > 0 then
|
|||
|
begin
|
|||
|
Dec(AllocationGroupStackTop);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Raise a runtime error if the stack underflows}
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Sums all the dwords starting at the given address.}
|
|||
|
function SumCardinals(AStartValue: Cardinal; APointer: PCardinal; ACount: Cardinal): Cardinal;
|
|||
|
asm
|
|||
|
{On entry: eax = AStartValue, edx = APointer; ecx = ACount}
|
|||
|
add edx, ecx
|
|||
|
neg ecx
|
|||
|
@AddLoop:
|
|||
|
add eax, [edx + ecx]
|
|||
|
add edx, 4
|
|||
|
js @AddLoop
|
|||
|
end;
|
|||
|
|
|||
|
{Sums all the dwords starting at the given address for the fill pattern.
|
|||
|
Returns true if they are all valid}
|
|||
|
function CheckFillPattern(APointer: PCardinal; ACount: Cardinal): boolean;
|
|||
|
asm
|
|||
|
{On entry: eax = APointer; edx = ACount}
|
|||
|
add eax, edx
|
|||
|
neg edx
|
|||
|
@CheckLoop:
|
|||
|
cmp dword ptr [eax + edx], DebugFillDWord
|
|||
|
jne @Done
|
|||
|
add edx, 4
|
|||
|
js @CheckLoop
|
|||
|
@Done:
|
|||
|
sete al
|
|||
|
end;
|
|||
|
|
|||
|
{Calculates the checksum for the debug header. Adds all dwords in the debug
|
|||
|
header to the start address of the block.}
|
|||
|
function CalculateHeaderCheckSum(APointer: PFullDebugBlockHeader): Cardinal;
|
|||
|
begin
|
|||
|
Result := SumCardinals(Cardinal(APointer),
|
|||
|
PCardinal(Cardinal(APointer) + 8),
|
|||
|
SizeOf(TFullDebugBlockHeader) - 8 - 4);
|
|||
|
end;
|
|||
|
|
|||
|
procedure UpdateHeaderAndFooterCheckSums(APointer: PFullDebugBlockHeader);
|
|||
|
var
|
|||
|
LHeaderCheckSum: Cardinal;
|
|||
|
begin
|
|||
|
LHeaderCheckSum := CalculateHeaderCheckSum(APointer);
|
|||
|
APointer.HeaderCheckSum := LHeaderCheckSum;
|
|||
|
PCardinal(Cardinal(APointer) + SizeOf(TFullDebugBlockHeader) + APointer.UserSize)^ := not LHeaderCheckSum;
|
|||
|
end;
|
|||
|
|
|||
|
function LogCurrentStackTrace(ASkipFrames: Cardinal; ABuffer: PChar): PChar;
|
|||
|
var
|
|||
|
LCurrentStackTrace: TStackTrace;
|
|||
|
begin
|
|||
|
{Get the current call stack}
|
|||
|
GetStackTrace(@LCurrentStackTrace[0], StackTraceDepth, ASkipFrames);
|
|||
|
{List it}
|
|||
|
Result := AppendStringToBuffer(CurrentStackTraceMsg, ABuffer, length(CurrentStackTraceMsg));
|
|||
|
Result := LogStackTrace(@LCurrentStackTrace, StackTraceDepth, Result);
|
|||
|
end;
|
|||
|
|
|||
|
function LogMemoryDump(APointer: PFullDebugBlockHeader; ABuffer: PChar): PChar;
|
|||
|
var
|
|||
|
LByteNum, LVal: Cardinal;
|
|||
|
LDataPtr: PByte;
|
|||
|
begin
|
|||
|
Result := AppendStringToBuffer(MemoryDumpMsg, ABuffer, Length(MemoryDumpMsg));
|
|||
|
Result := CardinalToHexBuf(Cardinal(APointer) + SizeOf(TFullDebugBlockHeader), Result);
|
|||
|
Result^ := ':';
|
|||
|
Inc(Result);
|
|||
|
{Add the bytes}
|
|||
|
LDataPtr := PByte(Cardinal(APointer) + SizeOf(TFullDebugBlockHeader));
|
|||
|
for LByteNum := 0 to 255 do
|
|||
|
begin
|
|||
|
if LByteNum and 31 = 0 then
|
|||
|
begin
|
|||
|
Result^ := #13;
|
|||
|
Inc(Result);
|
|||
|
Result^ := #10;
|
|||
|
Inc(Result);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
Result^ := ' ';
|
|||
|
Inc(Result);
|
|||
|
end;
|
|||
|
{Set the hex data}
|
|||
|
LVal := LDataPtr^;
|
|||
|
Result^ := HexTable[LVal shr 4];
|
|||
|
Inc(Result);
|
|||
|
Result^ := HexTable[LVal and $f];
|
|||
|
Inc(Result);
|
|||
|
{Next byte}
|
|||
|
Inc(LDataPtr);
|
|||
|
end;
|
|||
|
{Dump ASCII}
|
|||
|
LDataPtr := PByte(Cardinal(APointer) + SizeOf(TFullDebugBlockHeader));
|
|||
|
for LByteNum := 0 to 255 do
|
|||
|
begin
|
|||
|
if LByteNum and 31 = 0 then
|
|||
|
begin
|
|||
|
Result^ := #13;
|
|||
|
Inc(Result);
|
|||
|
Result^ := #10;
|
|||
|
Inc(Result);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
Result^ := ' ';
|
|||
|
Inc(Result);
|
|||
|
Result^ := ' ';
|
|||
|
Inc(Result);
|
|||
|
end;
|
|||
|
{Set the hex data}
|
|||
|
LVal := LDataPtr^;
|
|||
|
if LVal < 32 then
|
|||
|
Result^ := '.'
|
|||
|
else
|
|||
|
Result^ := Char(LVal);
|
|||
|
Inc(Result);
|
|||
|
{Next byte}
|
|||
|
Inc(LDataPtr);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure LogBlockError(APointer: PFullDebugBlockHeader; AOperation: TBlockOperation; LHeaderValid, LFooterValid: Boolean);
|
|||
|
var
|
|||
|
LMsgPtr: PChar;
|
|||
|
LErrorMessage: array[0..32767] of char;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
LErrorMessageTitle: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
LClass: TClass;
|
|||
|
LClassName: ShortString;
|
|||
|
begin
|
|||
|
{Display the error header and the operation type.}
|
|||
|
LMsgPtr := AppendStringToBuffer(ErrorMsgHeader, @LErrorMessage[0], Length(ErrorMsgHeader));
|
|||
|
case AOperation of
|
|||
|
boGetMem: LMsgPtr := AppendStringToBuffer(GetMemMsg, LMsgPtr, Length(GetMemMsg));
|
|||
|
boFreeMem: LMsgPtr := AppendStringToBuffer(FreeMemMsg, LMsgPtr, Length(FreeMemMsg));
|
|||
|
boReallocMem: LMsgPtr := AppendStringToBuffer(ReallocMemMsg, LMsgPtr, Length(ReallocMemMsg));
|
|||
|
boBlockCheck: LMsgPtr := AppendStringToBuffer(BlockCheckMsg, LMsgPtr, Length(BlockCheckMsg));
|
|||
|
end;
|
|||
|
LMsgPtr := AppendStringToBuffer(OperationMsg, LMsgPtr, Length(OperationMsg));
|
|||
|
{Is the header still intact?}
|
|||
|
if LHeaderValid then
|
|||
|
begin
|
|||
|
{Is the footer still valid?}
|
|||
|
if LFooterValid then
|
|||
|
begin
|
|||
|
{A freed block has been modified, or a double free has occurred}
|
|||
|
if AOperation <= boGetMem then
|
|||
|
LMsgPtr := AppendStringToBuffer(FreeModifiedErrorMsg, LMsgPtr, Length(FreeModifiedErrorMsg))
|
|||
|
else
|
|||
|
LMsgPtr := AppendStringToBuffer(DoubleFreeErrorMsg, LMsgPtr, Length(DoubleFreeErrorMsg));
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(BlockFooterCorruptedMsg, LMsgPtr, Length(BlockFooterCorruptedMsg))
|
|||
|
end;
|
|||
|
{Set the block size message}
|
|||
|
if AOperation <= boGetMem then
|
|||
|
LMsgPtr := AppendStringToBuffer(PreviousBlockSizeMsg, LMsgPtr, Length(PreviousBlockSizeMsg))
|
|||
|
else
|
|||
|
LMsgPtr := AppendStringToBuffer(CurrentBlockSizeMsg, LMsgPtr, Length(CurrentBlockSizeMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(APointer.UserSize, LMsgPtr);
|
|||
|
{The header is still intact - display info about the this/previous allocation}
|
|||
|
if APointer.AllocationStackTrace[0] <> 0 then
|
|||
|
begin
|
|||
|
if AOperation <= boGetMem then
|
|||
|
LMsgPtr := AppendStringToBuffer(StackTraceAtPrevAllocMsg, LMsgPtr, Length(StackTraceAtPrevAllocMsg))
|
|||
|
else
|
|||
|
LMsgPtr := AppendStringToBuffer(StackTraceAtAllocMsg, LMsgPtr, Length(StackTraceAtAllocMsg));
|
|||
|
LMsgPtr := LogStackTrace(@APointer.AllocationStackTrace, StackTraceDepth, LMsgPtr);
|
|||
|
end;
|
|||
|
{Get the class this block was used for previously}
|
|||
|
LClass := GetObjectClass(@APointer.PreviouslyUsedByClass);
|
|||
|
if (LClass <> nil) and (Cardinal(LClass) <> Cardinal(@FreedObjectVMT.VMTMethods[0])) then
|
|||
|
begin
|
|||
|
LClassName := LClass.ClassName;
|
|||
|
LMsgPtr := AppendStringToBuffer(PreviousObjectClassMsg, LMsgPtr, Length(PreviousObjectClassMsg));
|
|||
|
LMsgPtr := AppendStringToBuffer(@LClassName[1], LMsgPtr, Length(LClassName));
|
|||
|
end;
|
|||
|
{Get the current class for this block}
|
|||
|
if (AOperation > boGetMem) and (not LFooterValid) then
|
|||
|
begin
|
|||
|
LClass := GetObjectClass(Pointer(Cardinal(APointer) + SizeOf(TFullDebugBlockHeader)));
|
|||
|
if (LClass <> nil) and (Cardinal(LClass) <> Cardinal(@FreedObjectVMT.VMTMethods[0])) then
|
|||
|
LClassName := LClass.ClassName
|
|||
|
else
|
|||
|
LClassName := UnknownClassNameMsg;
|
|||
|
LMsgPtr := AppendStringToBuffer(CurrentObjectClassMsg, LMsgPtr, Length(CurrentObjectClassMsg));
|
|||
|
LMsgPtr := AppendStringToBuffer(@LClassName[1], LMsgPtr, Length(LClassName));
|
|||
|
{Log the allocation group}
|
|||
|
if APointer.AllocationGroup > 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(CurrentAllocationGroupMsg, LMsgPtr, Length(CurrentAllocationGroupMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(APointer.AllocationGroup, LMsgPtr);
|
|||
|
end;
|
|||
|
{Log the allocation number}
|
|||
|
LMsgPtr := AppendStringToBuffer(CurrentAllocationNumberMsg, LMsgPtr, Length(CurrentAllocationNumberMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(APointer.AllocationNumber, LMsgPtr);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Log the allocation group}
|
|||
|
if APointer.AllocationGroup > 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(PreviousAllocationGroupMsg, LMsgPtr, Length(PreviousAllocationGroupMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(APointer.AllocationGroup, LMsgPtr);
|
|||
|
end;
|
|||
|
{Log the allocation number}
|
|||
|
LMsgPtr := AppendStringToBuffer(PreviousAllocationNumberMsg, LMsgPtr, Length(PreviousAllocationNumberMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(APointer.AllocationNumber, LMsgPtr);
|
|||
|
end;
|
|||
|
{Get the call stack for the previous free}
|
|||
|
if APointer.FreeStackTrace[0] <> 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(StackTraceAtFreeMsg, LMsgPtr, Length(StackTraceAtFreeMsg));
|
|||
|
LMsgPtr := LogStackTrace(@APointer.FreeStackTrace, StackTraceDepth, LMsgPtr);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Header has been corrupted}
|
|||
|
LMsgPtr := AppendStringToBuffer(BlockHeaderCorruptedMsg, LMsgPtr, Length(BlockHeaderCorruptedMsg));
|
|||
|
end;
|
|||
|
{Add the current stack trace}
|
|||
|
LMsgPtr := LogCurrentStackTrace(3 + ord(AOperation <> boGetMem) + ord(AOperation = boReallocMem), LMsgPtr);
|
|||
|
{Add the memory dump}
|
|||
|
LMsgPtr := LogMemoryDump(APointer, LMsgPtr);
|
|||
|
{Trailing CRLF}
|
|||
|
LMsgPtr^ := #13;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := #10;
|
|||
|
Inc(LMsgPtr);
|
|||
|
{Trailing #0}
|
|||
|
LMsgPtr^ := #0;
|
|||
|
{$ifdef LogErrorsToFile}
|
|||
|
{Log the error}
|
|||
|
AppendEventLog(@LErrorMessage[0], Cardinal(LMsgPtr) - Cardinal(@LErrorMessage[0]));
|
|||
|
{$endif}
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(LErrorMessage);
|
|||
|
{$endif}
|
|||
|
{Show the message}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
AppendStringToModuleName(BlockErrorMsgTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, LErrorMessage, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
{Logs the stack traces for a memory leak to file}
|
|||
|
procedure LogMemoryLeakOrAllocatedBlock(APointer: PFullDebugBlockHeader; IsALeak: Boolean);
|
|||
|
var
|
|||
|
LHeaderValid: boolean;
|
|||
|
LMsgPtr: PChar;
|
|||
|
LErrorMessage: array[0..32767] of char;
|
|||
|
LClass: TClass;
|
|||
|
LClassName: ShortString;
|
|||
|
begin
|
|||
|
{Display the error header and the operation type.}
|
|||
|
if IsALeak then
|
|||
|
LMsgPtr := AppendStringToBuffer(LeakLogHeader, @LErrorMessage[0], Length(LeakLogHeader))
|
|||
|
else
|
|||
|
LMsgPtr := AppendStringToBuffer(BlockScanLogHeader, @LErrorMessage[0], Length(BlockScanLogHeader));
|
|||
|
LMsgPtr := CardinalToStrBuf(GetAvailableSpaceInBlock(APointer) - FullDebugBlockOverhead, LMsgPtr);
|
|||
|
{Is the debug info surrounding the block valid?}
|
|||
|
LHeaderValid := CalculateHeaderCheckSum(APointer) = APointer.HeaderCheckSum;
|
|||
|
{Is the header still intact?}
|
|||
|
if LHeaderValid then
|
|||
|
begin
|
|||
|
{The header is still intact - display info about this/previous allocation}
|
|||
|
if APointer.AllocationStackTrace[0] <> 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(StackTraceAtAllocMsg, LMsgPtr, Length(StackTraceAtAllocMsg));
|
|||
|
LMsgPtr := LogStackTrace(@APointer.AllocationStackTrace, StackTraceDepth, LMsgPtr);
|
|||
|
end;
|
|||
|
{Get the current class for this block}
|
|||
|
LClass := GetObjectClass(Pointer(Cardinal(APointer) + SizeOf(TFullDebugBlockHeader)));
|
|||
|
if (LClass <> nil) and (Cardinal(LClass) <> Cardinal(@FreedObjectVMT.VMTMethods[0])) then
|
|||
|
LClassName := LClass.ClassName
|
|||
|
else
|
|||
|
LClassName := UnknownClassNameMsg;
|
|||
|
LMsgPtr := AppendStringToBuffer(CurrentObjectClassMsg, LMsgPtr, Length(CurrentObjectClassMsg));
|
|||
|
LMsgPtr := AppendStringToBuffer(@LClassName[1], LMsgPtr, Length(LClassName));
|
|||
|
{Log the allocation group}
|
|||
|
if APointer.AllocationGroup > 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(CurrentAllocationGroupMsg, LMsgPtr, Length(CurrentAllocationGroupMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(APointer.AllocationGroup, LMsgPtr);
|
|||
|
end;
|
|||
|
{Log the allocation number}
|
|||
|
LMsgPtr := AppendStringToBuffer(CurrentAllocationNumberMsg, LMsgPtr, Length(CurrentAllocationNumberMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(APointer.AllocationNumber, LMsgPtr);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Header has been corrupted}
|
|||
|
LMsgPtr^ := '.';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := ' ';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr := AppendStringToBuffer(BlockHeaderCorruptedMsg, LMsgPtr, Length(BlockHeaderCorruptedMsg));
|
|||
|
end;
|
|||
|
{Add the memory dump}
|
|||
|
LMsgPtr := LogMemoryDump(APointer, LMsgPtr);
|
|||
|
{Trailing CRLF}
|
|||
|
LMsgPtr^ := #13;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := #10;
|
|||
|
Inc(LMsgPtr);
|
|||
|
{Trailing #0}
|
|||
|
LMsgPtr^ := #0;
|
|||
|
{Log the error}
|
|||
|
AppendEventLog(@LErrorMessage[0], Cardinal(LMsgPtr) - Cardinal(@LErrorMessage[0]));
|
|||
|
end;
|
|||
|
|
|||
|
{Checks that a free block is unmodified}
|
|||
|
function CheckFreeBlockUnmodified(APBlock: PFullDebugBlockHeader; ABlockSize: Cardinal;
|
|||
|
AOperation: TBlockOperation): Boolean;
|
|||
|
var
|
|||
|
LHeaderCheckSum: Cardinal;
|
|||
|
LHeaderValid, LFooterValid{$ifndef CatchUseOfFreedInterfaces}, LBlockUnmodified{$endif}: boolean;
|
|||
|
begin
|
|||
|
LHeaderCheckSum := CalculateHeaderCheckSum(APBlock);
|
|||
|
LHeaderValid := LHeaderCheckSum = PFullDebugBlockHeader(APBlock).HeaderCheckSum;
|
|||
|
{Is the footer itself still in place}
|
|||
|
LFooterValid := LHeaderValid
|
|||
|
and (PCardinal(Cardinal(APBlock) + SizeOf(TFullDebugBlockHeader) + PFullDebugBlockHeader(APBlock).UserSize)^ = (not LHeaderCheckSum));
|
|||
|
{$ifndef CatchUseOfFreedInterfaces}
|
|||
|
if LFooterValid then
|
|||
|
begin
|
|||
|
{Clear the old footer}
|
|||
|
PCardinal(Cardinal(APBlock) + SizeOf(TFullDebugBlockHeader) + PFullDebugBlockHeader(APBlock).UserSize)^ := DebugFillDWord;
|
|||
|
{Check that all the filler bytes are valid inside the block, except for the four byte "dummy" class header}
|
|||
|
LBlockUnmodified := CheckFillPattern(PCardinal(Cardinal(APBlock) + SizeOf(TFullDebugBlockHeader) + 4),
|
|||
|
ABlockSize - (BlockHeaderSize + FullDebugBlockOverhead));
|
|||
|
{Reset the old footer}
|
|||
|
PCardinal(Cardinal(APBlock) + SizeOf(TFullDebugBlockHeader) + PFullDebugBlockHeader(APBlock).UserSize)^ := not LHeaderCheckSum;
|
|||
|
end
|
|||
|
else
|
|||
|
LBlockUnmodified := False;
|
|||
|
{$endif}
|
|||
|
if (not LHeaderValid) or (not LFooterValid){$ifndef CatchUseOfFreedInterfaces}or (not LBlockUnmodified){$endif} then
|
|||
|
begin
|
|||
|
LogBlockError(APBlock, AOperation, LHeaderValid, LFooterValid);
|
|||
|
Result := False;
|
|||
|
end
|
|||
|
else
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
function DebugGetMem(ASize: Integer): Pointer;
|
|||
|
begin
|
|||
|
{We need extra space for (a) The debug header, (b) the block debug trailer
|
|||
|
and (c) the trailing block size pointer for free blocks}
|
|||
|
Result := FastGetMem(ASize + FullDebugBlockOverhead);
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
if CheckFreeBlockUnmodified(Result, GetAvailableSpaceInBlock(Result) + 4, boGetMem) then
|
|||
|
begin
|
|||
|
{Set the allocation call stack}
|
|||
|
GetStackTrace(@PFullDebugBlockHeader(Result).AllocationStackTrace, StackTraceDepth, 1);
|
|||
|
{Block is now in use}
|
|||
|
PFullDebugBlockHeader(Result).BlockInUse := True;
|
|||
|
{Set the group number}
|
|||
|
PFullDebugBlockHeader(Result).AllocationGroup := AllocationGroupStack[AllocationGroupStackTop];
|
|||
|
{Set the allocation number}
|
|||
|
Inc(CurrentAllocationNumber);
|
|||
|
PFullDebugBlockHeader(Result).AllocationNumber := CurrentAllocationNumber;
|
|||
|
{Clear the previous block trailer}
|
|||
|
PCardinal(Cardinal(Result) + SizeOf(TFullDebugBlockHeader) + PFullDebugBlockHeader(Result).UserSize)^ := DebugFillDWord;
|
|||
|
{Set the user size for the block}
|
|||
|
PFullDebugBlockHeader(Result).UserSize := ASize;
|
|||
|
{Set the checksums}
|
|||
|
UpdateHeaderAndFooterCheckSums(Result);
|
|||
|
{Return the start of the actual block}
|
|||
|
Result := Pointer(Cardinal(Result) + SizeOf(TFullDebugBlockHeader));
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function CheckBlockBeforeFreeOrRealloc(APointer: PFullDebugBlockHeader; AOperation: TBlockOperation): boolean;
|
|||
|
var
|
|||
|
LHeaderValid, LFooterValid: boolean;
|
|||
|
begin
|
|||
|
{Is the debug info surrounding the block valid?}
|
|||
|
LHeaderValid := CalculateHeaderCheckSum(APointer) = APointer.HeaderCheckSum;
|
|||
|
LFooterValid := LHeaderValid
|
|||
|
and (APointer.HeaderCheckSum = (not PCardinal(Cardinal(APointer) + SizeOf(TFullDebugBlockHeader) + PFullDebugBlockHeader(APointer).UserSize)^));
|
|||
|
if LHeaderValid and LFooterValid and APointer.BlockInUse then
|
|||
|
begin
|
|||
|
Result := True;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Log the error}
|
|||
|
LogBlockError(APointer, AOperation, LHeaderValid, LFooterValid);
|
|||
|
{Return an error}
|
|||
|
Result := False;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function DebugFreeMem(APointer: Pointer): Integer;
|
|||
|
var
|
|||
|
LActualBlock: PFullDebugBlockHeader;
|
|||
|
begin
|
|||
|
{Get a pointer to the start of the actual block}
|
|||
|
LActualBlock := PFullDebugBlockHeader(Cardinal(APointer)
|
|||
|
- SizeOf(TFullDebugBlockHeader));
|
|||
|
{Is the debug info surrounding the block valid?}
|
|||
|
if CheckBlockBeforeFreeOrRealloc(LActualBlock, boFreeMem) then
|
|||
|
begin
|
|||
|
{Get the class the block was used for}
|
|||
|
LActualBlock.PreviouslyUsedByClass := PCardinal(APointer)^;
|
|||
|
{Set the free call stack}
|
|||
|
GetStackTrace(@LActualBlock.FreeStackTrace, StackTraceDepth, 1);
|
|||
|
{Block is now free}
|
|||
|
LActualBlock.BlockInUse := False;
|
|||
|
{Clear the user area of the block}
|
|||
|
FillDWord(APointer^, LActualBlock.UserSize,
|
|||
|
{$ifndef CatchUseOfFreedInterfaces}DebugFillDWord{$else}Cardinal(@VMTBadInterface){$endif});
|
|||
|
{Set a pointer to the dummy VMT}
|
|||
|
PCardinal(APointer)^ := Cardinal(@FreedObjectVMT.VMTMethods[0]);
|
|||
|
{Recalculate the checksums}
|
|||
|
UpdateHeaderAndFooterCheckSums(LActualBlock);
|
|||
|
{Free the actual block}
|
|||
|
Result := FastFreeMem(LActualBlock);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
Result := -1;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{In debug mode we never do an in-place resize, data is always moved. This
|
|||
|
increases the likelihood of catching memory overwrite bugs.}
|
|||
|
function DebugReallocMem(APointer: Pointer; ANewSize: Integer): Pointer;
|
|||
|
var
|
|||
|
LMoveSize, LBlockSpace: Cardinal;
|
|||
|
LActualBlock, LNewActualBlock: PFullDebugBlockHeader;
|
|||
|
begin
|
|||
|
{Get a pointer to the start of the actual block}
|
|||
|
LActualBlock := PFullDebugBlockHeader(Cardinal(APointer)
|
|||
|
- SizeOf(TFullDebugBlockHeader));
|
|||
|
{Is the debug info surrounding the block valid?}
|
|||
|
if CheckBlockBeforeFreeOrRealloc(LActualBlock, boReallocMem) then
|
|||
|
begin
|
|||
|
{Get the current block size}
|
|||
|
LBlockSpace := GetAvailableSpaceInBlock(LActualBlock);
|
|||
|
{Can the block fit? We need space for the debug overhead and the block header
|
|||
|
of the next block}
|
|||
|
if LBlockSpace < (Cardinal(ANewSize) + FullDebugBlockOverhead) then
|
|||
|
begin
|
|||
|
{Get a new block of the requested size}
|
|||
|
Result := DebugGetMem(ANewSize);
|
|||
|
if Result <> nil then
|
|||
|
begin
|
|||
|
{How many bytes to move?}
|
|||
|
LMoveSize := LActualBlock.UserSize;
|
|||
|
if LMoveSize > Cardinal(ANewSize) then
|
|||
|
LMoveSize := ANewSize;
|
|||
|
{Move the data across}
|
|||
|
System.Move(APointer^, Result^, LMoveSize);
|
|||
|
{Keep the old group and allocation numbers}
|
|||
|
LNewActualBlock := PFullDebugBlockHeader(Cardinal(Result)
|
|||
|
- SizeOf(TFullDebugBlockHeader));
|
|||
|
LNewActualBlock.AllocationGroup := LActualBlock.AllocationGroup;
|
|||
|
LNewActualBlock.AllocationNumber := LActualBlock.AllocationNumber;
|
|||
|
{This was not a new allocation number - decrement the allocation number
|
|||
|
that was incremented in the DebugGetMem call}
|
|||
|
Dec(CurrentAllocationNumber);
|
|||
|
{Recalculate the header and footer checksums}
|
|||
|
UpdateHeaderAndFooterCheckSums(LNewActualBlock);
|
|||
|
{Free the old block}
|
|||
|
DebugFreeMem(APointer);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Clear all data after the new end of the block up to the old end of the
|
|||
|
block, including the trailer}
|
|||
|
FillDWord(Pointer(Cardinal(APointer) + Cardinal(ANewSize) + 4)^,
|
|||
|
Integer(LActualBlock.UserSize) - ANewSize,
|
|||
|
{$ifndef CatchUseOfFreedInterfaces}DebugFillDWord{$else}Cardinal(@VMTBadInterface){$endif});
|
|||
|
{Update the user size}
|
|||
|
LActualBlock.UserSize := ANewSize;
|
|||
|
{Set the new checksums}
|
|||
|
UpdateHeaderAndFooterCheckSums(LActualBlock);
|
|||
|
{Return the old pointer}
|
|||
|
Result := APointer;
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Allocates a block and fills it with zeroes}
|
|||
|
function DebugAllocMem(ASize: Cardinal): Pointer;
|
|||
|
begin
|
|||
|
Result := DebugGetMem(ASize);
|
|||
|
{Large blocks are already zero filled}
|
|||
|
if (Result <> nil) and (ASize <= (MaximumMediumBlockSize - BlockHeaderSize)) then
|
|||
|
FillChar(Result^, ASize, 0);
|
|||
|
end;
|
|||
|
|
|||
|
{Logs detail about currently allocated memory blocks for the specified range of
|
|||
|
allocation groups. if ALastAllocationGroupToLog is less than
|
|||
|
AFirstAllocationGroupToLog or it is zero, then all allocation groups are
|
|||
|
logged. This routine also checks the memory pool for consistency at the same
|
|||
|
time.}
|
|||
|
procedure LogAllocatedBlocksToFile(AFirstAllocationGroupToLog, ALastAllocationGroupToLog: Cardinal);
|
|||
|
var
|
|||
|
LPLargeBlock: PLargeBlockHeader;
|
|||
|
LPMediumBlock: Pointer;
|
|||
|
LPMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
LMediumBlockHeader: Cardinal;
|
|||
|
|
|||
|
{Checks the small block pool for allocated blocks}
|
|||
|
procedure ScanSmallBlockPool(APSmallBlockPool: PSmallBlockPoolHeader);
|
|||
|
var
|
|||
|
LCurPtr, LEndPtr: Pointer;
|
|||
|
begin
|
|||
|
{Get the first and last pointer for the pool}
|
|||
|
GetFirstAndLastSmallBlockInPool(APSmallBlockPool, LCurPtr, LEndPtr);
|
|||
|
{Step through all blocks}
|
|||
|
while Cardinal(LCurPtr) <= Cardinal(LEndPtr) do
|
|||
|
begin
|
|||
|
{Is this block in use? If so, is the debug info intact?}
|
|||
|
if ((PCardinal(Cardinal(LCurPtr) - 4)^ and IsFreeBlockFlag) = 0) then
|
|||
|
begin
|
|||
|
if CheckBlockBeforeFreeOrRealloc(LCurPtr, boBlockCheck)
|
|||
|
and (PFullDebugBlockHeader(LCurPtr).AllocationGroup >= AFirstAllocationGroupToLog)
|
|||
|
and (PFullDebugBlockHeader(LCurPtr).AllocationGroup <= ALastAllocationGroupToLog) then
|
|||
|
begin
|
|||
|
LogMemoryLeakOrAllocatedBlock(LCurPtr, False);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Check that the block has not been modified since being freed}
|
|||
|
CheckFreeBlockUnmodified(LCurPtr, APSmallBlockPool.BlockType.BlockSize, boBlockCheck);
|
|||
|
end;
|
|||
|
{Next block}
|
|||
|
Inc(Cardinal(LCurPtr), APSmallBlockPool.BlockType.BlockSize);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
{Validate input}
|
|||
|
if (ALastAllocationGroupToLog = 0) or (ALastAllocationGroupToLog < AFirstAllocationGroupToLog) then
|
|||
|
begin
|
|||
|
{Bad input: log all groups}
|
|||
|
AFirstAllocationGroupToLog := 0;
|
|||
|
ALastAllocationGroupToLog := $ffffffff;
|
|||
|
end;
|
|||
|
{Step through all the medium block pools}
|
|||
|
LPMediumBlockPoolHeader := MediumBlockPoolsCircularList.NextMediumBlockPoolHeader;
|
|||
|
while LPMediumBlockPoolHeader <> @MediumBlockPoolsCircularList do
|
|||
|
begin
|
|||
|
LPMediumBlock := GetFirstMediumBlockInPool(LPMediumBlockPoolHeader);
|
|||
|
while LPMediumBlock <> nil do
|
|||
|
begin
|
|||
|
LMediumBlockHeader := PCardinal(Cardinal(LPMediumBlock) - 4)^;
|
|||
|
{Is the block in use?}
|
|||
|
if LMediumBlockHeader and IsFreeBlockFlag = 0 then
|
|||
|
begin
|
|||
|
{Block is in use: Is it a medium block or small block pool?}
|
|||
|
if (LMediumBlockHeader and IsSmallBlockPoolInUseFlag) <> 0 then
|
|||
|
begin
|
|||
|
{Get all the leaks for the small block pool}
|
|||
|
ScanSmallBlockPool(LPMediumBlock);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
if CheckBlockBeforeFreeOrRealloc(LPMediumBlock, boBlockCheck)
|
|||
|
and (PFullDebugBlockHeader(LPMediumBlock).AllocationGroup >= AFirstAllocationGroupToLog)
|
|||
|
and (PFullDebugBlockHeader(LPMediumBlock).AllocationGroup <= ALastAllocationGroupToLog) then
|
|||
|
begin
|
|||
|
LogMemoryLeakOrAllocatedBlock(LPMediumBlock, False);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Check that the block has not been modified since being freed}
|
|||
|
CheckFreeBlockUnmodified(LPMediumBlock, LMediumBlockHeader and DropMediumAndLargeFlagsMask, boBlockCheck);
|
|||
|
end;
|
|||
|
{Next medium block}
|
|||
|
LPMediumBlock := NextMediumBlock(LPMediumBlock);
|
|||
|
end;
|
|||
|
{Get the next medium block pool}
|
|||
|
LPMediumBlockPoolHeader := LPMediumBlockPoolHeader.NextMediumBlockPoolHeader;
|
|||
|
end;
|
|||
|
{Scan large blocks}
|
|||
|
LPLargeBlock := LargeBlocksCircularList.NextLargeBlockHeader;
|
|||
|
while (LPLargeBlock <> @LargeBlocksCircularList) do
|
|||
|
begin
|
|||
|
if CheckBlockBeforeFreeOrRealloc(Pointer(Cardinal(LPLargeBlock) + LargeBlockHeaderSize), boBlockCheck)
|
|||
|
and (PFullDebugBlockHeader(Cardinal(LPLargeBlock) + LargeBlockHeaderSize).AllocationGroup >= AFirstAllocationGroupToLog)
|
|||
|
and (PFullDebugBlockHeader(Cardinal(LPLargeBlock) + LargeBlockHeaderSize).AllocationGroup <= ALastAllocationGroupToLog) then
|
|||
|
begin
|
|||
|
LogMemoryLeakOrAllocatedBlock(Pointer(Cardinal(LPLargeBlock) + LargeBlockHeaderSize), False);
|
|||
|
end;
|
|||
|
{Get the next large block}
|
|||
|
LPLargeBlock := LPLargeBlock.NextLargeBlockHeader;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{-----------------------Invalid Virtual Method Calls-------------------------}
|
|||
|
|
|||
|
{ TFreedObject }
|
|||
|
|
|||
|
{Used to determine the index of the virtual method call on the freed object.
|
|||
|
Do not change this without updating MaxFakeVMTEntries. Currently 200.}
|
|||
|
procedure TFreedObject.GetVirtualMethodIndex;
|
|||
|
asm
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex); Inc(VMIndex);
|
|||
|
|
|||
|
jmp TFreedObject.VirtualMethodError
|
|||
|
end;
|
|||
|
|
|||
|
procedure TFreedObject.VirtualMethodError;
|
|||
|
var
|
|||
|
LVMOffset: Integer;
|
|||
|
LMsgPtr: PChar;
|
|||
|
LErrorMessage: array[0..32767] of char;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
LErrorMessageTitle: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
LClass: TClass;
|
|||
|
LClassName: ShortString;
|
|||
|
LActualBlock: PFullDebugBlockHeader;
|
|||
|
begin
|
|||
|
{Get the offset of the virtual method}
|
|||
|
LVMOffset := (MaxFakeVMTEntries - VMIndex) * 4 + vmtParent + 4;
|
|||
|
{Reset the index for the next error}
|
|||
|
VMIndex := 0;
|
|||
|
{Get the address of the actual block}
|
|||
|
LActualBlock := PFullDebugBlockHeader(Cardinal(Self) - SizeOf(TFullDebugBlockHeader));
|
|||
|
{Display the error header}
|
|||
|
LMsgPtr := AppendStringToBuffer(VirtualMethodErrorHeader, @LErrorMessage[0], Length(VirtualMethodErrorHeader));
|
|||
|
{Is the debug info surrounding the block valid?}
|
|||
|
if CalculateHeaderCheckSum(LActualBlock) = LActualBlock.HeaderCheckSum then
|
|||
|
begin
|
|||
|
{Get the class this block was used for previously}
|
|||
|
LClass := GetObjectClass(@LActualBlock.PreviouslyUsedByClass);
|
|||
|
if (LClass <> nil) and (Cardinal(LClass) <> Cardinal(@FreedObjectVMT.VMTMethods[0])) then
|
|||
|
begin
|
|||
|
LClassName := LClass.ClassName;
|
|||
|
LMsgPtr := AppendStringToBuffer(FreedObjectClassMsg, LMsgPtr, Length(FreedObjectClassMsg));
|
|||
|
LMsgPtr := AppendStringToBuffer(@LClassName[1], LMsgPtr, Length(LClassName));
|
|||
|
end;
|
|||
|
{Get the virtual method name}
|
|||
|
LMsgPtr := AppendStringToBuffer(VirtualMethodName, LMsgPtr, Length(VirtualMethodName));
|
|||
|
if LVMOffset < 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(StandardVirtualMethodNames[LVMOffset div 4], LMsgPtr, Length(StandardVirtualMethodNames[LVMOffset div 4]));
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(VirtualMethodOffset, LMsgPtr, Length(VirtualMethodOffset));
|
|||
|
LMsgPtr := CardinalToStrBuf(LVMOffset, LMsgPtr);
|
|||
|
end;
|
|||
|
{Virtual method address}
|
|||
|
if (LClass <> nil) and (Cardinal(LClass) <> Cardinal(@FreedObjectVMT.VMTMethods[0])) then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(VirtualMethodAddress, LMsgPtr, Length(VirtualMethodAddress));
|
|||
|
LMsgPtr := CardinalToHexBuf(PCardinal(Integer(LClass) + LVMOffset)^, LMsgPtr);
|
|||
|
end;
|
|||
|
{Log the allocation group}
|
|||
|
if LActualBlock.AllocationGroup > 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(PreviousAllocationGroupMsg, LMsgPtr, Length(PreviousAllocationGroupMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(LActualBlock.AllocationGroup, LMsgPtr);
|
|||
|
end;
|
|||
|
{Log the allocation number}
|
|||
|
LMsgPtr := AppendStringToBuffer(PreviousAllocationNumberMsg, LMsgPtr, Length(PreviousAllocationNumberMsg));
|
|||
|
LMsgPtr := CardinalToStrBuf(LActualBlock.AllocationNumber, LMsgPtr);
|
|||
|
{The header is still intact - display info about the this/previous allocation}
|
|||
|
if LActualBlock.AllocationStackTrace[0] <> 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(StackTraceAtObjectAllocMsg, LMsgPtr, Length(StackTraceAtObjectAllocMsg));
|
|||
|
LMsgPtr := LogStackTrace(@LActualBlock.AllocationStackTrace, StackTraceDepth, LMsgPtr);
|
|||
|
end;
|
|||
|
{Get the call stack for the previous free}
|
|||
|
if LActualBlock.FreeStackTrace[0] <> 0 then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(StackTraceAtObjectFreeMsg, LMsgPtr, Length(StackTraceAtObjectFreeMsg));
|
|||
|
LMsgPtr := LogStackTrace(@LActualBlock.FreeStackTrace, StackTraceDepth, LMsgPtr);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Header has been corrupted}
|
|||
|
LMsgPtr := AppendStringToBuffer(BlockHeaderCorruptedNoHistoryMsg, LMsgPtr, Length(BlockHeaderCorruptedNoHistoryMsg));
|
|||
|
end;
|
|||
|
{Add the current stack trace}
|
|||
|
LMsgPtr := LogCurrentStackTrace(2, LMsgPtr);
|
|||
|
{Add the pointer address}
|
|||
|
LMsgPtr := LogMemoryDump(LActualBlock, LMsgPtr);
|
|||
|
{Trailing CRLF}
|
|||
|
LMsgPtr^ := #13;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := #10;
|
|||
|
Inc(LMsgPtr);
|
|||
|
{Trailing #0}
|
|||
|
LMsgPtr^ := #0;
|
|||
|
{$ifdef LogErrorsToFile}
|
|||
|
{Log the error}
|
|||
|
AppendEventLog(@LErrorMessage[0], Cardinal(LMsgPtr) - Cardinal(@LErrorMessage[0]));
|
|||
|
{$endif}
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(LErrorMessage);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
{Show the message}
|
|||
|
AppendStringToModuleName(BlockErrorMsgTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, LErrorMessage, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
{Raise an access violation}
|
|||
|
RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, nil);
|
|||
|
end;
|
|||
|
|
|||
|
{$ifdef CatchUseOfFreedInterfaces}
|
|||
|
procedure TFreedObject.InterfaceError;
|
|||
|
var
|
|||
|
LMsgPtr: PChar;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
LErrorMessageTitle: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
LErrorMessage: array[0..4000] of char;
|
|||
|
begin
|
|||
|
{Display the error header}
|
|||
|
LMsgPtr := AppendStringToBuffer(InterfaceErrorHeader, @LErrorMessage[0], Length(InterfaceErrorHeader));
|
|||
|
{Add the current stack trace}
|
|||
|
LMsgPtr := LogCurrentStackTrace(2, LMsgPtr);
|
|||
|
{Trailing CRLF}
|
|||
|
LMsgPtr^ := #13;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := #10;
|
|||
|
Inc(LMsgPtr);
|
|||
|
{Trailing #0}
|
|||
|
LMsgPtr^ := #0;
|
|||
|
{$ifdef LogErrorsToFile}
|
|||
|
{Log the error}
|
|||
|
AppendEventLog(@LErrorMessage[0], Cardinal(LMsgPtr) - Cardinal(@LErrorMessage[0]));
|
|||
|
{$endif}
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(LErrorMessage);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
{Show the message}
|
|||
|
AppendStringToModuleName(BlockErrorMsgTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, LErrorMessage, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
{Raise an access violation}
|
|||
|
RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, nil);
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{$endif}
|
|||
|
|
|||
|
{----------------------------Memory Leak Checking-----------------------------}
|
|||
|
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
|
|||
|
{Adds a leak to the specified list}
|
|||
|
function UpdateExpectedLeakList(APLeakList: PPExpectedMemoryLeak;
|
|||
|
APNewEntry: PExpectedMemoryLeak; AExactSizeMatch: Boolean = True): boolean;
|
|||
|
var
|
|||
|
LPInsertAfter, LPNewEntry: PExpectedMemoryLeak;
|
|||
|
begin
|
|||
|
{Default to error}
|
|||
|
Result := False;
|
|||
|
{Find the insertion spot}
|
|||
|
LPInsertAfter := APLeakList^;
|
|||
|
while (LPInsertAfter <> nil) do
|
|||
|
begin
|
|||
|
{Too big?}
|
|||
|
if (LPInsertAfter.LeakSize > APNewEntry.LeakSize) then
|
|||
|
begin
|
|||
|
LPInsertAfter := LPInsertAfter.PreviousLeak;
|
|||
|
break;
|
|||
|
end;
|
|||
|
{Find a matching entry. If an exact size match is not required and the leak
|
|||
|
is larger than the current entry, use it if the expected size of the next
|
|||
|
entry is too large.}
|
|||
|
if (Cardinal(LPInsertAfter.LeakAddress) = Cardinal(APNewEntry.LeakAddress))
|
|||
|
and (Cardinal(LPInsertAfter.LeakedClass) = Cardinal(APNewEntry.LeakedClass))
|
|||
|
and ((LPInsertAfter.LeakSize = APNewEntry.LeakSize)
|
|||
|
or ((not AExactSizeMatch)
|
|||
|
and (LPInsertAfter.LeakSize < APNewEntry.LeakSize)
|
|||
|
and ((LPInsertAfter.NextLeak = nil)
|
|||
|
or (LPInsertAfter.NextLeak.LeakSize > APNewEntry.LeakSize))
|
|||
|
)) then
|
|||
|
begin
|
|||
|
if Integer(LPInsertAfter.LeakCount + APNewEntry.LeakCount) >= 0 then
|
|||
|
begin
|
|||
|
Inc(LPInsertAfter.LeakCount, APNewEntry.LeakCount);
|
|||
|
{Is the count now 0?}
|
|||
|
if LPInsertAfter.LeakCount = 0 then
|
|||
|
begin
|
|||
|
{Delete the entry}
|
|||
|
if LPInsertAfter.NextLeak <> nil then
|
|||
|
LPInsertAfter.NextLeak.PreviousLeak := LPInsertAfter.PreviousLeak;
|
|||
|
if LPInsertAfter.PreviousLeak <> nil then
|
|||
|
LPInsertAfter.PreviousLeak.NextLeak := LPInsertAfter.NextLeak
|
|||
|
else
|
|||
|
APLeakList^ := LPInsertAfter.NextLeak;
|
|||
|
{Insert it as the first free slot}
|
|||
|
LPInsertAfter.NextLeak := ExpectedMemoryLeaks.FirstFreeSlot;
|
|||
|
ExpectedMemoryLeaks.FirstFreeSlot := LPInsertAfter;
|
|||
|
end;
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
exit;
|
|||
|
end;
|
|||
|
{Next entry}
|
|||
|
if LPInsertAfter.NextLeak <> nil then
|
|||
|
LPInsertAfter := LPInsertAfter.NextLeak
|
|||
|
else
|
|||
|
break;
|
|||
|
end;
|
|||
|
if APNewEntry.LeakCount > 0 then
|
|||
|
begin
|
|||
|
{Get a position for the entry}
|
|||
|
LPNewEntry := ExpectedMemoryLeaks.FirstFreeSlot;
|
|||
|
if LPNewEntry <> nil then
|
|||
|
begin
|
|||
|
ExpectedMemoryLeaks.FirstFreeSlot := LPNewEntry.NextLeak;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
if (ExpectedMemoryLeaks.EntriesUsed < length(ExpectedMemoryLeaks.ExpectedLeaks)) then
|
|||
|
begin
|
|||
|
LPNewEntry := @ExpectedMemoryLeaks.ExpectedLeaks[ExpectedMemoryLeaks.EntriesUsed];
|
|||
|
Inc(ExpectedMemoryLeaks.EntriesUsed);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{No more space}
|
|||
|
exit;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Set the entry}
|
|||
|
LPNewEntry^ := APNewEntry^;
|
|||
|
{Insert it into the list}
|
|||
|
LPNewEntry.PreviousLeak := LPInsertAfter;
|
|||
|
if LPInsertAfter <> nil then
|
|||
|
begin
|
|||
|
LPNewEntry.NextLeak := LPInsertAfter.NextLeak;
|
|||
|
LPInsertAfter.NextLeak := LPNewEntry;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
LPNewEntry.NextLeak := APLeakList^;
|
|||
|
APLeakList^ := LPNewEntry;
|
|||
|
end;
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Locks the expected leaks. Returns false if the list could not be allocated.}
|
|||
|
function LockExpectedMemoryLeaksList: Boolean;
|
|||
|
begin
|
|||
|
{Lock the expected leaks list}
|
|||
|
{$ifndef AssumeMultiThreaded}
|
|||
|
if IsMultiThread then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
while LockCmpxchg(0, 1, @ExpectedMemoryLeaksListLocked) <> 0 do
|
|||
|
begin
|
|||
|
Sleep(InitialSleepTime);
|
|||
|
if LockCmpxchg(0, 1, @ExpectedMemoryLeaksListLocked) = 0 then
|
|||
|
break;
|
|||
|
Sleep(AdditionalSleepTime);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Allocate the list if it does not exist}
|
|||
|
if ExpectedMemoryLeaks = nil then
|
|||
|
ExpectedMemoryLeaks := VirtualAlloc(nil, ExpectedMemoryLeaksListSize, MEM_COMMIT, PAGE_READWRITE);
|
|||
|
{Done}
|
|||
|
Result := ExpectedMemoryLeaks <> nil;
|
|||
|
end;
|
|||
|
|
|||
|
{Registers expected memory leaks. Returns true on success. The list of leaked
|
|||
|
blocks is limited, so failure is possible if the list is full.}
|
|||
|
function RegisterExpectedMemoryLeak(ALeakedPointer: Pointer): boolean; overload;
|
|||
|
var
|
|||
|
LNewEntry: TExpectedMemoryLeak;
|
|||
|
begin
|
|||
|
{Fill out the structure}
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
LNewEntry.LeakAddress := ALeakedPointer;
|
|||
|
{$else}
|
|||
|
LNewEntry.LeakAddress := Pointer(Cardinal(ALeakedPointer) - SizeOf(TFullDebugBlockHeader));
|
|||
|
{$endif}
|
|||
|
LNewEntry.LeakedClass := nil;
|
|||
|
LNewEntry.LeakSize := 0;
|
|||
|
LNewEntry.LeakCount := 1;
|
|||
|
{Add it to the correct list}
|
|||
|
Result := LockExpectedMemoryLeaksList
|
|||
|
and UpdateExpectedLeakList(@ExpectedMemoryLeaks.FirstEntryByAddress, @LNewEntry);
|
|||
|
ExpectedMemoryLeaksListLocked := False;
|
|||
|
end;
|
|||
|
|
|||
|
function RegisterExpectedMemoryLeak(ALeakedObjectClass: TClass; ACount: Integer = 1): boolean; overload;
|
|||
|
var
|
|||
|
LNewEntry: TExpectedMemoryLeak;
|
|||
|
begin
|
|||
|
{Fill out the structure}
|
|||
|
LNewEntry.LeakAddress := nil;
|
|||
|
LNewEntry.LeakedClass := ALeakedObjectClass;
|
|||
|
LNewEntry.LeakSize := ALeakedObjectClass.InstanceSize;
|
|||
|
LNewEntry.LeakCount := ACount;
|
|||
|
{Add it to the correct list}
|
|||
|
Result := LockExpectedMemoryLeaksList
|
|||
|
and UpdateExpectedLeakList(@ExpectedMemoryLeaks.FirstEntryByClass, @LNewEntry);
|
|||
|
ExpectedMemoryLeaksListLocked := False;
|
|||
|
end;
|
|||
|
|
|||
|
function RegisterExpectedMemoryLeak(ALeakedBlockSize: Integer; ACount: Integer = 1): boolean; overload;
|
|||
|
var
|
|||
|
LNewEntry: TExpectedMemoryLeak;
|
|||
|
begin
|
|||
|
{Fill out the structure}
|
|||
|
LNewEntry.LeakAddress := nil;
|
|||
|
LNewEntry.LeakedClass := nil;
|
|||
|
LNewEntry.LeakSize := ALeakedBlockSize;
|
|||
|
LNewEntry.LeakCount := ACount;
|
|||
|
{Add it to the correct list}
|
|||
|
Result := LockExpectedMemoryLeaksList
|
|||
|
and UpdateExpectedLeakList(@ExpectedMemoryLeaks.FirstEntryBySizeOnly, @LNewEntry);
|
|||
|
ExpectedMemoryLeaksListLocked := False;
|
|||
|
end;
|
|||
|
|
|||
|
function UnregisterExpectedMemoryLeak(ALeakedPointer: Pointer): boolean; overload;
|
|||
|
var
|
|||
|
LNewEntry: TExpectedMemoryLeak;
|
|||
|
begin
|
|||
|
{Fill out the structure}
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
LNewEntry.LeakAddress := ALeakedPointer;
|
|||
|
{$else}
|
|||
|
LNewEntry.LeakAddress := Pointer(Cardinal(ALeakedPointer) - SizeOf(TFullDebugBlockHeader));
|
|||
|
{$endif}
|
|||
|
LNewEntry.LeakedClass := nil;
|
|||
|
LNewEntry.LeakSize := 0;
|
|||
|
LNewEntry.LeakCount := -1;
|
|||
|
{Remove it from the list}
|
|||
|
Result := LockExpectedMemoryLeaksList
|
|||
|
and UpdateExpectedLeakList(@ExpectedMemoryLeaks.FirstEntryByAddress, @LNewEntry);
|
|||
|
ExpectedMemoryLeaksListLocked := False;
|
|||
|
end;
|
|||
|
|
|||
|
function UnregisterExpectedMemoryLeak(ALeakedObjectClass: TClass; ACount: Integer = 1): boolean; overload;
|
|||
|
begin
|
|||
|
Result := RegisterExpectedMemoryLeak(ALeakedObjectClass, - ACount);
|
|||
|
end;
|
|||
|
|
|||
|
function UnregisterExpectedMemoryLeak(ALeakedBlockSize: Integer; ACount: Integer = 1): boolean; overload;
|
|||
|
begin
|
|||
|
Result := RegisterExpectedMemoryLeak(ALeakedBlockSize, - ACount);
|
|||
|
end;
|
|||
|
|
|||
|
{Returns a list of all expected memory leaks}
|
|||
|
function GetRegisteredMemoryLeaks: TRegisteredMemoryLeaks;
|
|||
|
|
|||
|
procedure AddEntries(AEntry: PExpectedMemoryLeak);
|
|||
|
var
|
|||
|
LInd: integer;
|
|||
|
begin
|
|||
|
while AEntry <> nil do
|
|||
|
begin
|
|||
|
LInd := length(Result);
|
|||
|
SetLength(Result, LInd + 1);
|
|||
|
{Add the entry}
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
Result[LInd].LeakAddress := AEntry.LeakAddress;
|
|||
|
{$else}
|
|||
|
Result[LInd].LeakAddress := Pointer(Cardinal(AEntry.LeakAddress) + SizeOf(TFullDebugBlockHeader));
|
|||
|
{$endif}
|
|||
|
Result[LInd].LeakedClass := AEntry.LeakedClass;
|
|||
|
Result[LInd].LeakSize := AEntry.LeakSize;
|
|||
|
Result[LInd].LeakCount := AEntry.LeakCount;
|
|||
|
{Next entry}
|
|||
|
AEntry := AEntry.NextLeak;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
SetLength(Result, 0);
|
|||
|
if (ExpectedMemoryLeaks <> nil) and LockExpectedMemoryLeaksList then
|
|||
|
begin
|
|||
|
{Add all entries}
|
|||
|
AddEntries(ExpectedMemoryLeaks.FirstEntryByAddress);
|
|||
|
AddEntries(ExpectedMemoryLeaks.FirstEntryByClass);
|
|||
|
AddEntries(ExpectedMemoryLeaks.FirstEntryBySizeOnly);
|
|||
|
{Unlock the list}
|
|||
|
ExpectedMemoryLeaksListLocked := False;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Checks blocks for modification after free and also for memory
|
|||
|
leaks}
|
|||
|
procedure CheckBlocksOnShutdown(ACheckForLeakedBlocks: Boolean);
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
type
|
|||
|
{Leaked class type}
|
|||
|
TLeakedClass = packed record
|
|||
|
ClassPointer: TClass;
|
|||
|
NumLeaks: Cardinal;
|
|||
|
end;
|
|||
|
TLeakedClasses = array[0..255] of TLeakedClass;
|
|||
|
PLeakedClasses = ^TLeakedClasses;
|
|||
|
{Leak statistics for a small block type}
|
|||
|
TSmallBlockLeaks = array[0..NumSmallBlockTypes - 1] of TLeakedClasses;
|
|||
|
{A leaked medium or large block}
|
|||
|
TMediumAndLargeBlockLeaks = array[0..4095] of Cardinal;
|
|||
|
{$endif}
|
|||
|
var
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
{The leaked classes for small blocks}
|
|||
|
LSmallBlockLeaks: TSmallBlockLeaks;
|
|||
|
LLeakType: TMemoryLeakType;
|
|||
|
LMediumAndLargeBlockLeaks: TMediumAndLargeBlockLeaks;
|
|||
|
LNumMediumAndLargeLeaks: Integer;
|
|||
|
LPLargeBlock: PLargeBlockHeader;
|
|||
|
LLeakMessage: array[0..32767] of char;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
LMessageTitleBuffer: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
LMsgPtr: PChar;
|
|||
|
LClassName: ShortString;
|
|||
|
LExpectedLeaksOnly, LSmallLeakHeaderAdded, LBlockSizeHeaderAdded: Boolean;
|
|||
|
LBlockTypeInd, LMediumBlockSize, LLargeBlockSize,
|
|||
|
LClassInd, LPreviousBlockSize, LThisBlockSize, LBlockInd: Cardinal;
|
|||
|
{$endif}
|
|||
|
LPMediumBlock: Pointer;
|
|||
|
LPMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
LMediumBlockHeader: Cardinal;
|
|||
|
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
{Tries to account for a memory leak. Returns true if the leak is expected and
|
|||
|
removes the leak from the list}
|
|||
|
function GetMemoryLeakType(AAddress: Pointer; ASpaceInsideBlock: Cardinal): TMemoryLeakType;
|
|||
|
var
|
|||
|
LLeak: TExpectedMemoryLeak;
|
|||
|
begin
|
|||
|
{Default to not found}
|
|||
|
Result := mltUnexpectedLeak;
|
|||
|
if ExpectedMemoryLeaks <> nil then
|
|||
|
begin
|
|||
|
{Check by pointer address}
|
|||
|
LLeak.LeakAddress := AAddress;
|
|||
|
LLeak.LeakedClass := nil;
|
|||
|
LLeak.LeakSize := 0;
|
|||
|
LLeak.LeakCount := -1;
|
|||
|
if UpdateExpectedLeakList(@ExpectedMemoryLeaks.FirstEntryByAddress, @LLeak, False) then
|
|||
|
begin
|
|||
|
Result := mltExpectedLeakRegisteredByPointer;
|
|||
|
exit;
|
|||
|
end;
|
|||
|
{Check by class}
|
|||
|
LLeak.LeakAddress := nil;
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
LLeak.LeakedClass := TClass(PCardinal(Cardinal(AAddress)+ SizeOf(TFullDebugBlockHeader))^);
|
|||
|
{$else}
|
|||
|
LLeak.LeakedClass := TClass(PCardinal(AAddress)^);
|
|||
|
{$endif}
|
|||
|
LLeak.LeakSize := ASpaceInsideBlock;
|
|||
|
if UpdateExpectedLeakList(@ExpectedMemoryLeaks.FirstEntryByClass, @LLeak, False) then
|
|||
|
begin
|
|||
|
Result := mltExpectedLeakRegisteredByClass;
|
|||
|
exit;
|
|||
|
end;
|
|||
|
{Check by size: the block must be large enough to hold the leak}
|
|||
|
LLeak.LeakedClass := nil;
|
|||
|
if UpdateExpectedLeakList(@ExpectedMemoryLeaks.FirstEntryBySizeOnly, @LLeak, False) then
|
|||
|
Result := mltExpectedLeakRegisteredBySize;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{Checks the small block pool for leaks.}
|
|||
|
procedure CheckSmallBlockPoolForLeaks(APSmallBlockPool: PSmallBlockPoolHeader);
|
|||
|
var
|
|||
|
LLeakedClass: TClass;
|
|||
|
LSmallBlockLeakType: TMemoryLeakType;
|
|||
|
LCharInd, LClassIndex, LStringLength: Integer;
|
|||
|
LPStr: PChar;
|
|||
|
LPossibleString: boolean;
|
|||
|
LCurPtr, LEndPtr, LDataPtr: Pointer;
|
|||
|
LBlockTypeIndex: Cardinal;
|
|||
|
LPLeakedClasses: PLeakedClasses;
|
|||
|
LSmallBlockSize: Cardinal;
|
|||
|
begin
|
|||
|
{Get the useable size inside a block}
|
|||
|
LSmallBlockSize := APSmallBlockPool.BlockType.BlockSize - BlockHeaderSize;
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
Dec(LSmallBlockSize, FullDebugBlockOverhead);
|
|||
|
{$endif}
|
|||
|
{Get the block type index}
|
|||
|
LBlockTypeIndex := (Cardinal(APSmallBlockPool.BlockType) - Cardinal(@SmallBlockTypes[0])) div SizeOf(TSmallBlockType);
|
|||
|
LPLeakedClasses := @LSmallBlockLeaks[LBlockTypeIndex];
|
|||
|
{Get the first and last pointer for the pool}
|
|||
|
GetFirstAndLastSmallBlockInPool(APSmallBlockPool, LCurPtr, LEndPtr);
|
|||
|
{Step through all blocks}
|
|||
|
while Cardinal(LCurPtr) <= Cardinal(LEndPtr) do
|
|||
|
begin
|
|||
|
{Is this block in use? If so, is the debug info intact?}
|
|||
|
if ((PCardinal(Cardinal(LCurPtr) - 4)^ and IsFreeBlockFlag) = 0) then
|
|||
|
begin
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
if CheckBlockBeforeFreeOrRealloc(LCurPtr, boBlockCheck) then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{Get the leak type}
|
|||
|
LSmallBlockLeakType := GetMemoryLeakType(LCurPtr, LSmallBlockSize);
|
|||
|
{$ifdef LogMemoryLeakDetailToFile}
|
|||
|
{$ifdef HideExpectedLeaksRegisteredByPointer}
|
|||
|
if LSmallBlockLeakType <> mltExpectedLeakRegisteredByPointer then
|
|||
|
{$endif}
|
|||
|
LogMemoryLeakOrAllocatedBlock(LCurPtr, True);
|
|||
|
{$endif}
|
|||
|
{Only expected leaks?}
|
|||
|
LExpectedLeaksOnly := LExpectedLeaksOnly and (LSmallBlockLeakType <> mltUnexpectedLeak);
|
|||
|
{$ifdef HideExpectedLeaksRegisteredByPointer}
|
|||
|
if LSmallBlockLeakType <> mltExpectedLeakRegisteredByPointer then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{Get a pointer to the user data}
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
LDataPtr := LCurPtr;
|
|||
|
{$else}
|
|||
|
LDataPtr := Pointer(Cardinal(LCurPtr) + SizeOf(TFullDebugBlockHeader));
|
|||
|
{$endif}
|
|||
|
{Default to an unknown block}
|
|||
|
LClassIndex := 0;
|
|||
|
{Get the class contained by the block}
|
|||
|
LLeakedClass := GetObjectClass(LDataPtr);
|
|||
|
{Not a class? -> is it perhaps a string?}
|
|||
|
if LLeakedClass = nil then
|
|||
|
begin
|
|||
|
{Reference count < 256}
|
|||
|
if (PCardinal(LDataPtr)^ < 256) then
|
|||
|
begin
|
|||
|
LStringLength := PCardinal(Cardinal(LDataPtr) + 4)^;
|
|||
|
{Does the string fit?}
|
|||
|
if (LStringLength > 0)
|
|||
|
and (LStringLength < (APSmallBlockPool.BlockType.BlockSize - (8 + 1 + 4 {$ifdef FullDebugMode} + FullDebugBlockOverhead{$endif}))) then
|
|||
|
begin
|
|||
|
{Check that all characters are in range #32..#127}
|
|||
|
LPStr := PChar(Cardinal(LDataPtr) + 8);
|
|||
|
LPossibleString := True;
|
|||
|
for LCharInd := 1 to LStringLength do
|
|||
|
begin
|
|||
|
LPossibleString := LPossibleString and (LPStr^ >= #32) and (LPStr^ < #128);
|
|||
|
Inc(LPStr);
|
|||
|
end;
|
|||
|
{Must have a trailing #0}
|
|||
|
if LPossibleString and (LPStr^ = #0) then
|
|||
|
begin
|
|||
|
LClassIndex := 1;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
LClassIndex := 2;
|
|||
|
while LClassIndex <= High(TLeakedClasses) do
|
|||
|
begin
|
|||
|
if (LPLeakedClasses[LClassIndex].ClassPointer = LLeakedClass)
|
|||
|
or (LPLeakedClasses[LClassIndex].ClassPointer = nil) then
|
|||
|
begin
|
|||
|
break;
|
|||
|
end;
|
|||
|
Inc(LClassIndex);
|
|||
|
end;
|
|||
|
if LClassIndex <= High(TLeakedClasses) then
|
|||
|
LPLeakedClasses[LClassIndex].ClassPointer := LLeakedClass
|
|||
|
else
|
|||
|
LClassIndex := 0;
|
|||
|
end;
|
|||
|
{Add to the number of leaks for the class}
|
|||
|
Inc(LPLeakedClasses[LClassIndex].NumLeaks);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$ifdef CheckUseOfFreedBlocksOnShutdown}
|
|||
|
{Check that the block has not been modified since being freed}
|
|||
|
CheckFreeBlockUnmodified(LCurPtr, APSmallBlockPool.BlockType.BlockSize, boBlockCheck);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{Next block}
|
|||
|
Inc(Cardinal(LCurPtr), APSmallBlockPool.BlockType.BlockSize);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
begin
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
{Clear the leak arrays}
|
|||
|
FillChar(LSmallBlockLeaks, SizeOf(LSmallBlockLeaks), 0);
|
|||
|
FillChar(LMediumAndLargeBlockLeaks, SizeOf(LMediumAndLargeBlockLeaks), 0);
|
|||
|
{Step through all the medium block pools}
|
|||
|
LNumMediumAndLargeLeaks := 0;
|
|||
|
{No unexpected leaks so far}
|
|||
|
LExpectedLeaksOnly := True;
|
|||
|
{$endif}
|
|||
|
{Step through all the medium block pools}
|
|||
|
LPMediumBlockPoolHeader := MediumBlockPoolsCircularList.NextMediumBlockPoolHeader;
|
|||
|
while LPMediumBlockPoolHeader <> @MediumBlockPoolsCircularList do
|
|||
|
begin
|
|||
|
LPMediumBlock := GetFirstMediumBlockInPool(LPMediumBlockPoolHeader);
|
|||
|
while LPMediumBlock <> nil do
|
|||
|
begin
|
|||
|
LMediumBlockHeader := PCardinal(Cardinal(LPMediumBlock) - 4)^;
|
|||
|
{Is the block in use?}
|
|||
|
if LMediumBlockHeader and IsFreeBlockFlag = 0 then
|
|||
|
begin
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
if ACheckForLeakedBlocks then
|
|||
|
begin
|
|||
|
if (LMediumBlockHeader and IsSmallBlockPoolInUseFlag) <> 0 then
|
|||
|
begin
|
|||
|
{Get all the leaks for the small block pool}
|
|||
|
CheckSmallBlockPoolForLeaks(LPMediumBlock);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
if (LNumMediumAndLargeLeaks < length(LMediumAndLargeBlockLeaks))
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
and CheckBlockBeforeFreeOrRealloc(LPMediumBlock, boBlockCheck)
|
|||
|
{$endif}
|
|||
|
then
|
|||
|
begin
|
|||
|
LMediumBlockSize := (LMediumBlockHeader and DropMediumAndLargeFlagsMask) - BlockHeaderSize;
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
Dec(LMediumBlockSize, FullDebugBlockOverhead);
|
|||
|
{$endif}
|
|||
|
{Get the leak type}
|
|||
|
LLeakType := GetMemoryLeakType(LPMediumBlock, LMediumBlockSize);
|
|||
|
{Is it an expected leak?}
|
|||
|
LExpectedLeaksOnly := LExpectedLeaksOnly and (LLeakType <> mltUnexpectedLeak);
|
|||
|
{$ifdef LogMemoryLeakDetailToFile}
|
|||
|
{$ifdef HideExpectedLeaksRegisteredByPointer}
|
|||
|
if LLeakType <> mltExpectedLeakRegisteredByPointer then
|
|||
|
{$endif}
|
|||
|
LogMemoryLeakOrAllocatedBlock(LPMediumBlock, True);
|
|||
|
{$endif}
|
|||
|
{$ifdef HideExpectedLeaksRegisteredByPointer}
|
|||
|
if LLeakType <> mltExpectedLeakRegisteredByPointer then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{Add the leak to the list}
|
|||
|
LMediumAndLargeBlockLeaks[LNumMediumAndLargeLeaks] := LMediumBlockSize;
|
|||
|
Inc(LNumMediumAndLargeLeaks);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$ifdef CheckUseOfFreedBlocksOnShutdown}
|
|||
|
{Check that the block has not been modified since being freed}
|
|||
|
CheckFreeBlockUnmodified(LPMediumBlock, LMediumBlockHeader and DropMediumAndLargeFlagsMask, boBlockCheck);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{Next medium block}
|
|||
|
LPMediumBlock := NextMediumBlock(LPMediumBlock);
|
|||
|
end;
|
|||
|
{Get the next medium block pool}
|
|||
|
LPMediumBlockPoolHeader := LPMediumBlockPoolHeader.NextMediumBlockPoolHeader;
|
|||
|
end;
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
if ACheckForLeakedBlocks then
|
|||
|
begin
|
|||
|
{Get all leaked large blocks}
|
|||
|
LPLargeBlock := LargeBlocksCircularList.NextLargeBlockHeader;
|
|||
|
while (LPLargeBlock <> @LargeBlocksCircularList) do
|
|||
|
begin
|
|||
|
if (LNumMediumAndLargeLeaks < length(LMediumAndLargeBlockLeaks))
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
and CheckBlockBeforeFreeOrRealloc(Pointer(Cardinal(LPLargeBlock) + LargeBlockHeaderSize), boBlockCheck)
|
|||
|
{$endif}
|
|||
|
then
|
|||
|
begin
|
|||
|
LLargeBlockSize := (LPLargeBlock.BlockSizeAndFlags and DropMediumAndLargeFlagsMask) - BlockHeaderSize - LargeBlockHeaderSize;
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
Dec(LLargeBlockSize, FullDebugBlockOverhead);
|
|||
|
{$endif}
|
|||
|
{Get the leak type}
|
|||
|
LLeakType := GetMemoryLeakType(Pointer(Cardinal(LPLargeBlock) + LargeBlockHeaderSize), LLargeBlockSize);
|
|||
|
{Is it an expected leak?}
|
|||
|
LExpectedLeaksOnly := LExpectedLeaksOnly and (LLeakType <> mltUnexpectedLeak);
|
|||
|
{$ifdef LogMemoryLeakDetailToFile}
|
|||
|
{$ifdef HideExpectedLeaksRegisteredByPointer}
|
|||
|
if LLeakType <> mltExpectedLeakRegisteredByPointer then
|
|||
|
{$endif}
|
|||
|
LogMemoryLeakOrAllocatedBlock(Pointer(Cardinal(LPLargeBlock) + LargeBlockHeaderSize), True);
|
|||
|
{$endif}
|
|||
|
{$ifdef HideExpectedLeaksRegisteredByPointer}
|
|||
|
if LLeakType <> mltExpectedLeakRegisteredByPointer then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{Add the leak}
|
|||
|
LMediumAndLargeBlockLeaks[LNumMediumAndLargeLeaks] := LLargeBlockSize;
|
|||
|
Inc(LNumMediumAndLargeLeaks);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Get the next large block}
|
|||
|
LPLargeBlock := LPLargeBlock.NextLargeBlockHeader;
|
|||
|
end;
|
|||
|
{Display the leak message if required}
|
|||
|
if not LExpectedLeaksOnly then
|
|||
|
begin
|
|||
|
{Small leak header has not been added}
|
|||
|
LSmallLeakHeaderAdded := False;
|
|||
|
LPreviousBlockSize := 0;
|
|||
|
{Set up the leak message header so long}
|
|||
|
LMsgPtr := AppendStringToBuffer(LeakMessageHeader, @LLeakMessage[0], length(LeakMessageHeader));
|
|||
|
{Step through all the small block types}
|
|||
|
for LBlockTypeInd := 0 to NumSmallBlockTypes - 1 do
|
|||
|
begin
|
|||
|
LThisBlockSize := SmallBlockTypes[LBlockTypeInd].BlockSize - BlockHeaderSize;
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
Dec(LThisBlockSize, FullDebugBlockOverhead);
|
|||
|
if Integer(LThisBlockSize) < 0 then
|
|||
|
LThisBlockSize := 0;
|
|||
|
{$endif}
|
|||
|
LBlockSizeHeaderAdded := False;
|
|||
|
{Any leaks?}
|
|||
|
for LClassInd := high(LSmallBlockLeaks[LBlockTypeInd]) downto 0 do
|
|||
|
begin
|
|||
|
{Is there still space in the message buffer? Reserve space for the message
|
|||
|
footer.}
|
|||
|
if LMsgPtr > @LLeakMessage[high(LLeakMessage) - 2048] then
|
|||
|
break;
|
|||
|
{Check the count}
|
|||
|
if LSmallBlockLeaks[LBlockTypeInd][LClassInd].NumLeaks > 0 then
|
|||
|
begin
|
|||
|
{Need to add the header?}
|
|||
|
if not LSmallLeakHeaderAdded then
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(SmallLeakDetail, LMsgPtr, Length(SmallLeakDetail));
|
|||
|
LSmallLeakHeaderAdded := True;
|
|||
|
end;
|
|||
|
{Need to add the size header?}
|
|||
|
if not LBlockSizeHeaderAdded then
|
|||
|
begin
|
|||
|
LMsgPtr^ := #13;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := #10;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr := CardinalToStrBuf(LPreviousBlockSize + 1, LMsgPtr);
|
|||
|
LMsgPtr^ := ' ';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := '-';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := ' ';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr := CardinalToStrBuf(LThisBlockSize, LMsgPtr);
|
|||
|
LMsgPtr := AppendStringToBuffer(BytesMessage, LMsgPtr, Length(BytesMessage));
|
|||
|
LBlockSizeHeaderAdded := True;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
LMsgPtr^ := ',';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := ' ';
|
|||
|
Inc(LMsgPtr);
|
|||
|
end;
|
|||
|
{Show the count}
|
|||
|
case LClassInd of
|
|||
|
{Unknown}
|
|||
|
0:
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(UnknownClassNameMsg, LMsgPtr, Length(UnknownClassNameMsg));
|
|||
|
end;
|
|||
|
{Strings}
|
|||
|
1:
|
|||
|
begin
|
|||
|
LMsgPtr := AppendStringToBuffer(StringBlockMessage, LMsgPtr, Length(StringBlockMessage));
|
|||
|
end;
|
|||
|
{Classes}
|
|||
|
else
|
|||
|
begin
|
|||
|
LClassName := LSmallBlockLeaks[LBlockTypeInd][LClassInd].ClassPointer.ClassName;
|
|||
|
LMsgPtr := AppendStringToBuffer(@LClassName[1], LMsgPtr, Length(LClassName));
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Add the count}
|
|||
|
LMsgPtr^ := ' ';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := 'x';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := ' ';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr := CardinalToStrBuf(LSmallBlockLeaks[LBlockTypeInd][LClassInd].NumLeaks, LMsgPtr);
|
|||
|
end;
|
|||
|
end;
|
|||
|
LPreviousBlockSize := LThisBlockSize;
|
|||
|
end;
|
|||
|
{Add the medium/large block leak message}
|
|||
|
if LNumMediumAndLargeLeaks > 0 then
|
|||
|
begin
|
|||
|
{Any non-small leaks?}
|
|||
|
if LSmallLeakHeaderAdded then
|
|||
|
begin
|
|||
|
LMsgPtr^ := #13;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := #10;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := #13;
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := #10;
|
|||
|
Inc(LMsgPtr);
|
|||
|
end;
|
|||
|
{Add the medium/large block leak message}
|
|||
|
LMsgPtr := AppendStringToBuffer(LargeLeakDetail, LMsgPtr, Length(LargeLeakDetail));
|
|||
|
{List all the blocks}
|
|||
|
for LBlockInd := 0 to LNumMediumAndLargeLeaks - 1 do
|
|||
|
begin
|
|||
|
if LBlockInd <> 0 then
|
|||
|
begin
|
|||
|
LMsgPtr^ := ',';
|
|||
|
Inc(LMsgPtr);
|
|||
|
LMsgPtr^ := ' ';
|
|||
|
Inc(LMsgPtr);
|
|||
|
end;
|
|||
|
LMsgPtr := CardinalToStrBuf(LMediumAndLargeBlockLeaks[LBlockInd], LMsgPtr);
|
|||
|
{Is there still space in the message buffer? Reserve space for the
|
|||
|
message footer.}
|
|||
|
if LMsgPtr > @LLeakMessage[high(LLeakMessage) - 2048] then
|
|||
|
break;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$ifdef LogErrorsToFile}
|
|||
|
{Set the message footer}
|
|||
|
LMsgPtr := AppendStringToBuffer(LeakMessageFooter, LMsgPtr, Length(LeakMessageFooter));
|
|||
|
{Append the message to the memory errors file}
|
|||
|
AppendEventLog(@LLeakMessage[0], Cardinal(LMsgPtr) - Cardinal(@LLeakMessage[1]));
|
|||
|
{$else}
|
|||
|
{Set the message footer}
|
|||
|
AppendStringToBuffer(LeakMessageFooter, LMsgPtr, Length(LeakMessageFooter));
|
|||
|
{$endif}
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(LLeakMessage);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
{Show the message}
|
|||
|
AppendStringToModuleName(LeakMessageTitle, LMessageTitleBuffer);
|
|||
|
MessageBox(0, LLeakMessage, LMessageTitleBuffer,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
{Returns statistics about the current state of the memory manager}
|
|||
|
procedure GetMemoryManagerState(var AMemoryManagerState: TMemoryManagerState);
|
|||
|
var
|
|||
|
LPMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
LPMediumBlock: Pointer;
|
|||
|
LInd: Integer;
|
|||
|
LBlockTypeIndex, LMediumBlockSize, LMediumBlockHeader, LLargeBlockSize: Cardinal;
|
|||
|
LPLargeBlock: PLargeBlockHeader;
|
|||
|
begin
|
|||
|
{Clear the structure}
|
|||
|
FillChar(AMemoryManagerState, SizeOf(AMemoryManagerState), 0);
|
|||
|
{Set the small block size stats}
|
|||
|
for LInd := 0 to NumSmallBlockTypes - 1 do
|
|||
|
begin
|
|||
|
AMemoryManagerState.SmallBlockTypeStates[LInd].InternalBlockSize :=
|
|||
|
SmallBlockTypes[LInd].BlockSize;
|
|||
|
AMemoryManagerState.SmallBlockTypeStates[LInd].UseableBlockSize :=
|
|||
|
SmallBlockTypes[LInd].BlockSize - BlockHeaderSize{$ifdef FullDebugMode} - FullDebugBlockOverhead{$endif};
|
|||
|
if Integer(AMemoryManagerState.SmallBlockTypeStates[LInd].UseableBlockSize) < 0 then
|
|||
|
AMemoryManagerState.SmallBlockTypeStates[LInd].UseableBlockSize := 0;
|
|||
|
end;
|
|||
|
{Lock all small block types}
|
|||
|
LockAllSmallBlockTypes;
|
|||
|
{Lock the medium blocks}
|
|||
|
LockMediumBlocks;
|
|||
|
{Step through all the medium block pools}
|
|||
|
LPMediumBlockPoolHeader := MediumBlockPoolsCircularList.NextMediumBlockPoolHeader;
|
|||
|
while LPMediumBlockPoolHeader <> @MediumBlockPoolsCircularList do
|
|||
|
begin
|
|||
|
{Add to the medium block used space}
|
|||
|
Inc(AMemoryManagerState.ReservedMediumBlockAddressSpace, MediumBlockPoolSize);
|
|||
|
LPMediumBlock := GetFirstMediumBlockInPool(LPMediumBlockPoolHeader);
|
|||
|
while LPMediumBlock <> nil do
|
|||
|
begin
|
|||
|
LMediumBlockHeader := PCardinal(Cardinal(LPMediumBlock) - 4)^;
|
|||
|
{Is the block in use?}
|
|||
|
if LMediumBlockHeader and IsFreeBlockFlag = 0 then
|
|||
|
begin
|
|||
|
{Get the block size}
|
|||
|
LMediumBlockSize := LMediumBlockHeader and DropMediumAndLargeFlagsMask;
|
|||
|
if (LMediumBlockHeader and IsSmallBlockPoolInUseFlag) <> 0 then
|
|||
|
begin
|
|||
|
{Get the block type index}
|
|||
|
LBlockTypeIndex := (Cardinal(PSmallBlockPoolHeader(LPMediumBlock).BlockType) - Cardinal(@SmallBlockTypes[0])) div SizeOf(TSmallBlockType);
|
|||
|
{Subtract from medium block usage}
|
|||
|
Dec(AMemoryManagerState.ReservedMediumBlockAddressSpace, LMediumBlockSize);
|
|||
|
{Add it to the reserved space for the block size}
|
|||
|
Inc(AMemoryManagerState.SmallBlockTypeStates[LBlockTypeIndex].ReservedAddressSpace, LMediumBlockSize);
|
|||
|
{Add the usage for the pool}
|
|||
|
Inc(AMemoryManagerState.SmallBlockTypeStates[LBlockTypeIndex].AllocatedBlockCount,
|
|||
|
PSmallBlockPoolHeader(LPMediumBlock).BlocksInUse);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
Dec(LMediumBlockSize, FullDebugBlockOverhead);
|
|||
|
{$endif}
|
|||
|
Inc(AMemoryManagerState.AllocatedMediumBlockCount);
|
|||
|
Inc(AMemoryManagerState.TotalAllocatedMediumBlockSize, LMediumBlockSize - BlockHeaderSize);
|
|||
|
end;
|
|||
|
end;
|
|||
|
{Next medium block}
|
|||
|
LPMediumBlock := NextMediumBlock(LPMediumBlock);
|
|||
|
end;
|
|||
|
{Get the next medium block pool}
|
|||
|
LPMediumBlockPoolHeader := LPMediumBlockPoolHeader.NextMediumBlockPoolHeader;
|
|||
|
end;
|
|||
|
{Unlock medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{Unlock all the small block types}
|
|||
|
for LInd := 0 to NumSmallBlockTypes - 1 do
|
|||
|
SmallBlockTypes[LInd].BlockTypeLocked := False;
|
|||
|
{Step through all the large blocks}
|
|||
|
LockLargeBlocks;
|
|||
|
LPLargeBlock := LargeBlocksCircularList.NextLargeBlockHeader;
|
|||
|
while (LPLargeBlock <> @LargeBlocksCircularList) do
|
|||
|
begin
|
|||
|
LLargeBlockSize := LPLargeBlock.BlockSizeAndFlags and DropMediumAndLargeFlagsMask;
|
|||
|
Inc(AMemoryManagerState.AllocatedLargeBlockCount);
|
|||
|
Inc(AMemoryManagerState.ReservedLargeBlockAddressSpace, LLargeBlockSize);
|
|||
|
Inc(AMemoryManagerState.TotalAllocatedLargeBlockSize, LPLargeBlock.UserAllocatedSize);
|
|||
|
{Get the next large block}
|
|||
|
LPLargeBlock := LPLargeBlock.NextLargeBlockHeader;
|
|||
|
end;
|
|||
|
LargeBlocksLocked := False;
|
|||
|
end;
|
|||
|
|
|||
|
{$ifndef Linux}
|
|||
|
{Gets the state of every 64K block in the 4GB address space}
|
|||
|
procedure GetMemoryMap(var AMemoryMap: TMemoryMap);
|
|||
|
var
|
|||
|
LPMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
LPLargeBlock: PLargeBlockHeader;
|
|||
|
LLargeBlockSize, LChunkIndex, LInd: Cardinal;
|
|||
|
LMBI: TMemoryBasicInformation;
|
|||
|
begin
|
|||
|
{Clear the map}
|
|||
|
FillChar(AMemoryMap, SizeOf(AMemoryMap), ord(csUnallocated));
|
|||
|
{Step through all the medium block pools}
|
|||
|
LockMediumBlocks;
|
|||
|
LPMediumBlockPoolHeader := MediumBlockPoolsCircularList.NextMediumBlockPoolHeader;
|
|||
|
while LPMediumBlockPoolHeader <> @MediumBlockPoolsCircularList do
|
|||
|
begin
|
|||
|
{Add to the medium block used space}
|
|||
|
LChunkIndex := Cardinal(LPMediumBlockPoolHeader) shr 16;
|
|||
|
for LInd := 0 to (MediumBlockPoolSize - 1) shr 16 do
|
|||
|
AMemoryMap[LChunkIndex + LInd] := csAllocated;
|
|||
|
{Get the next medium block pool}
|
|||
|
LPMediumBlockPoolHeader := LPMediumBlockPoolHeader.NextMediumBlockPoolHeader;
|
|||
|
end;
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{Step through all the large blocks}
|
|||
|
LockLargeBlocks;
|
|||
|
LPLargeBlock := LargeBlocksCircularList.NextLargeBlockHeader;
|
|||
|
while (LPLargeBlock <> @LargeBlocksCircularList) do
|
|||
|
begin
|
|||
|
LChunkIndex := Cardinal(LPLargeBlock) shr 16;
|
|||
|
LLargeBlockSize := LPLargeBlock.BlockSizeAndFlags and DropMediumAndLargeFlagsMask;
|
|||
|
for LInd := 0 to (LLargeBlockSize - 1) shr 16 do
|
|||
|
AMemoryMap[LChunkIndex + LInd] := csAllocated;
|
|||
|
{Get the next large block}
|
|||
|
LPLargeBlock := LPLargeBlock.NextLargeBlockHeader;
|
|||
|
end;
|
|||
|
LargeBlocksLocked := False;
|
|||
|
{Fill in the rest of the map}
|
|||
|
for LInd := 0 to 65535 do
|
|||
|
begin
|
|||
|
{If the chunk is not allocated by this MM, what is its status?}
|
|||
|
if AMemoryMap[LInd] = csUnallocated then
|
|||
|
begin
|
|||
|
{Get all the reserved memory blocks and windows allocated memory blocks, etc.}
|
|||
|
VirtualQuery(Pointer(LInd * 65536), LMBI, SizeOf(LMBI));
|
|||
|
if LMBI.State = MEM_COMMIT then
|
|||
|
AMemoryMap[LInd] := csSysAllocated
|
|||
|
else
|
|||
|
if LMBI.State = MEM_RESERVE then
|
|||
|
AMemoryMap[LInd] := csSysReserved;
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
{Returns summarised information about the state of the memory manager. (For
|
|||
|
backward compatibility.)}
|
|||
|
function FastGetHeapStatus: THeapStatus;
|
|||
|
var
|
|||
|
LPMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
LPMediumBlock: Pointer;
|
|||
|
LBlockTypeIndex, LMediumBlockSize, LMediumBlockHeader, LLargeBlockSize,
|
|||
|
LSmallBlockUsage, LSmallBlockOverhead: Cardinal;
|
|||
|
LInd: Integer;
|
|||
|
LPLargeBlock: PLargeBlockHeader;
|
|||
|
begin
|
|||
|
{Clear the structure}
|
|||
|
FillChar(Result, SizeOf(Result), 0);
|
|||
|
{Lock all small block types}
|
|||
|
LockAllSmallBlockTypes;
|
|||
|
{Lock the medium blocks}
|
|||
|
LockMediumBlocks;
|
|||
|
{Step through all the medium block pools}
|
|||
|
LPMediumBlockPoolHeader := MediumBlockPoolsCircularList.NextMediumBlockPoolHeader;
|
|||
|
while LPMediumBlockPoolHeader <> @MediumBlockPoolsCircularList do
|
|||
|
begin
|
|||
|
{Add to the total and committed address space}
|
|||
|
Inc(Result.TotalAddrSpace, ((MediumBlockPoolSize + $ffff) and $ffff0000));
|
|||
|
Inc(Result.TotalCommitted, ((MediumBlockPoolSize + $ffff) and $ffff0000));
|
|||
|
{Add the medium block pool overhead}
|
|||
|
Inc(Result.Overhead, (((MediumBlockPoolSize + $ffff) and $ffff0000)
|
|||
|
- MediumBlockPoolSize + MediumBlockPoolHeaderSize));
|
|||
|
{Get the first medium block in the pool}
|
|||
|
LPMediumBlock := GetFirstMediumBlockInPool(LPMediumBlockPoolHeader);
|
|||
|
while LPMediumBlock <> nil do
|
|||
|
begin
|
|||
|
{Get the block header}
|
|||
|
LMediumBlockHeader := PCardinal(Cardinal(LPMediumBlock) - 4)^;
|
|||
|
{Get the block size}
|
|||
|
LMediumBlockSize := LMediumBlockHeader and DropMediumAndLargeFlagsMask;
|
|||
|
{Is the block in use?}
|
|||
|
if LMediumBlockHeader and IsFreeBlockFlag = 0 then
|
|||
|
begin
|
|||
|
if (LMediumBlockHeader and IsSmallBlockPoolInUseFlag) <> 0 then
|
|||
|
begin
|
|||
|
{Get the block type index}
|
|||
|
LBlockTypeIndex := (Cardinal(PSmallBlockPoolHeader(LPMediumBlock).BlockType) - Cardinal(@SmallBlockTypes[0])) div SizeOf(TSmallBlockType);
|
|||
|
{Get the usage in the block}
|
|||
|
LSmallBlockUsage := PSmallBlockPoolHeader(LPMediumBlock).BlocksInUse
|
|||
|
* SmallBlockTypes[LBlockTypeIndex].BlockSize;
|
|||
|
{Get the total overhead for all the small blocks}
|
|||
|
LSmallBlockOverhead := PSmallBlockPoolHeader(LPMediumBlock).BlocksInUse
|
|||
|
* (BlockHeaderSize{$ifdef FullDebugMode} + FullDebugBlockOverhead{$endif});
|
|||
|
{Add to the totals}
|
|||
|
Inc(Result.FreeSmall, LMediumBlockSize - LSmallBlockUsage - BlockHeaderSize);
|
|||
|
Inc(Result.Overhead, LSmallBlockOverhead + BlockHeaderSize);
|
|||
|
Inc(Result.TotalAllocated, LSmallBlockUsage - LSmallBlockOverhead);
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
Dec(LMediumBlockSize, FullDebugBlockOverhead);
|
|||
|
Inc(Result.Overhead, FullDebugBlockOverhead);
|
|||
|
{$endif}
|
|||
|
{Add to the result}
|
|||
|
Inc(Result.TotalAllocated, LMediumBlockSize - BlockHeaderSize);
|
|||
|
Inc(Result.Overhead, BlockHeaderSize);
|
|||
|
end;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{The medium block is free}
|
|||
|
Inc(Result.FreeBig, LMediumBlockSize);
|
|||
|
end;
|
|||
|
{Next medium block}
|
|||
|
LPMediumBlock := NextMediumBlock(LPMediumBlock);
|
|||
|
end;
|
|||
|
{Get the next medium block pool}
|
|||
|
LPMediumBlockPoolHeader := LPMediumBlockPoolHeader.NextMediumBlockPoolHeader;
|
|||
|
end;
|
|||
|
{Add the sequential feed unused space}
|
|||
|
Inc(Result.Unused, MediumSequentialFeedBytesLeft);
|
|||
|
{Unlock the medium blocks}
|
|||
|
MediumBlocksLocked := False;
|
|||
|
{Unlock all the small block types}
|
|||
|
for LInd := 0 to NumSmallBlockTypes - 1 do
|
|||
|
SmallBlockTypes[LInd].BlockTypeLocked := False;
|
|||
|
{Step through all the large blocks}
|
|||
|
LockLargeBlocks;
|
|||
|
LPLargeBlock := LargeBlocksCircularList.NextLargeBlockHeader;
|
|||
|
while (LPLargeBlock <> @LargeBlocksCircularList) do
|
|||
|
begin
|
|||
|
LLargeBlockSize := LPLargeBlock.BlockSizeAndFlags and DropMediumAndLargeFlagsMask;
|
|||
|
Inc(Result.TotalAddrSpace, LLargeBlockSize);
|
|||
|
Inc(Result.TotalCommitted, LLargeBlockSize);
|
|||
|
Inc(Result.TotalAllocated, LPLargeBlock.UserAllocatedSize
|
|||
|
{$ifdef FullDebugMode} - FullDebugBlockOverhead{$endif});
|
|||
|
Inc(Result.Overhead, LLargeBlockSize - LPLargeBlock.UserAllocatedSize
|
|||
|
{$ifdef FullDebugMode} + FullDebugBlockOverhead{$endif});
|
|||
|
{Get the next large block}
|
|||
|
LPLargeBlock := LPLargeBlock.NextLargeBlockHeader;
|
|||
|
end;
|
|||
|
LargeBlocksLocked := False;
|
|||
|
{Set the total number of free bytes}
|
|||
|
Result.TotalFree := Result.FreeSmall + Result.FreeBig + Result.Unused;
|
|||
|
end;
|
|||
|
|
|||
|
{Frees all allocated memory.}
|
|||
|
procedure FreeAllMemory;
|
|||
|
var
|
|||
|
LPMediumBlockPoolHeader, LPNextMediumBlockPoolHeader: PMediumBlockPoolHeader;
|
|||
|
LPMediumFreeBlock: PMediumFreeBlock;
|
|||
|
LPLargeBlock, LPNextLargeBlock: PLargeBlockHeader;
|
|||
|
LInd: integer;
|
|||
|
begin
|
|||
|
{Free all block pools}
|
|||
|
LPMediumBlockPoolHeader := MediumBlockPoolsCircularList.NextMediumBlockPoolHeader;
|
|||
|
while LPMediumBlockPoolHeader <> @MediumBlockPoolsCircularList do
|
|||
|
begin
|
|||
|
{Get the next medium block pool so long}
|
|||
|
LPNextMediumBlockPoolHeader := LPMediumBlockPoolHeader.NextMediumBlockPoolHeader;
|
|||
|
{Free this pool}
|
|||
|
VirtualFree(LPMediumBlockPoolHeader, 0, MEM_RELEASE);
|
|||
|
{Next pool}
|
|||
|
LPMediumBlockPoolHeader := LPNextMediumBlockPoolHeader;
|
|||
|
end;
|
|||
|
{Clear all small block types}
|
|||
|
for LInd := 0 to high(SmallBlockTypes) do
|
|||
|
begin
|
|||
|
SmallBlockTypes[Lind].PreviousPartiallyFreePool := @SmallBlockTypes[Lind];
|
|||
|
SmallBlockTypes[Lind].NextPartiallyFreePool := @SmallBlockTypes[Lind];
|
|||
|
SmallBlockTypes[Lind].NextSequentialFeedBlockAddress := pointer(1);
|
|||
|
SmallBlockTypes[Lind].MaxSequentialFeedBlockAddress := nil;
|
|||
|
end;
|
|||
|
{Clear all medium block pools}
|
|||
|
MediumBlockPoolsCircularList.PreviousMediumBlockPoolHeader := @MediumBlockPoolsCircularList;
|
|||
|
MediumBlockPoolsCircularList.NextMediumBlockPoolHeader := @MediumBlockPoolsCircularList;
|
|||
|
{All medium bins are empty}
|
|||
|
for LInd := 0 to high(MediumBlockBins) do
|
|||
|
begin
|
|||
|
LPMediumFreeBlock := @MediumBlockBins[LInd];
|
|||
|
LPMediumFreeBlock.PreviousFreeBlock := LPMediumFreeBlock;
|
|||
|
LPMediumFreeBlock.NextFreeBlock := LPMediumFreeBlock;
|
|||
|
end;
|
|||
|
{Free all large blocks}
|
|||
|
LPLargeBlock := LargeBlocksCircularList.NextLargeBlockHeader;
|
|||
|
while LPLargeBlock <> @LargeBlocksCircularList do
|
|||
|
begin
|
|||
|
{Get the next large block}
|
|||
|
LPNextLargeBlock := LPLargeBlock.NextLargeBlockHeader;
|
|||
|
{Free this large block}
|
|||
|
VirtualFree(LPLargeBlock, 0, MEM_RELEASE);
|
|||
|
{Next large block}
|
|||
|
LPLargeBlock := LPNextLargeBlock;
|
|||
|
end;
|
|||
|
{There are no large blocks allocated}
|
|||
|
LargeBlocksCircularList.PreviousLargeBlockHeader := @LargeBlocksCircularList;
|
|||
|
LargeBlocksCircularList.NextLargeBlockHeader := @LargeBlocksCircularList;
|
|||
|
end;
|
|||
|
|
|||
|
{----------------------------Memory Manager Setup-----------------------------}
|
|||
|
|
|||
|
{Checks that no other memory manager has been installed after the RTL MM and
|
|||
|
that there are currently no live pointers allocated through the RTL MM.}
|
|||
|
function CheckCanInstallMemoryManager: boolean;
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
var
|
|||
|
LErrorMessageTitle: array[0..1023] of char;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{Default to error}
|
|||
|
Result := False;
|
|||
|
{Is FastMM already installed?}
|
|||
|
if FastMMIsInstalled then
|
|||
|
begin
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(AlreadyInstalledMsg);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
AppendStringToModuleName(AlreadyInstalledTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, AlreadyInstalledMsg, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
exit;
|
|||
|
end;
|
|||
|
{Has another MM been set, or has the Borland MM been used? If so, this file
|
|||
|
is not the first unit in the uses clause of the project's .dpr file.}
|
|||
|
if IsMemoryManagerSet then
|
|||
|
begin
|
|||
|
{When using runtime packages, another library may already have installed
|
|||
|
FastMM: Silently ignore the installation request.}
|
|||
|
{$ifndef UseRuntimePackages}
|
|||
|
{Another memory manager has been set.}
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(OtherMMInstalledMsg);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
AppendStringToModuleName(OtherMMInstalledTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, OtherMMInstalledMsg, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
exit;
|
|||
|
end;
|
|||
|
{$ifndef Linux}
|
|||
|
if (GetHeapStatus.TotalAllocated <> 0) then
|
|||
|
begin
|
|||
|
{Memory has been already been allocated with the RTL MM}
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
OutputDebugString(MemoryAllocatedMsg);
|
|||
|
{$endif}
|
|||
|
{$ifndef NoMessageBoxes}
|
|||
|
AppendStringToModuleName(MemoryAllocatedTitle, LErrorMessageTitle);
|
|||
|
MessageBox(0, MemoryAllocatedMsg, LErrorMessageTitle,
|
|||
|
MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|||
|
{$endif}
|
|||
|
exit;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{All OK}
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
{Initializes the lookup tables for the memory manager}
|
|||
|
procedure InitializeMemoryManager;
|
|||
|
var
|
|||
|
i, LSizeInd, LMinimumPoolSize, LOptimalPoolSize, LGroupNumber,
|
|||
|
LBlocksPerPool, LPreviousBlockSize: Cardinal;
|
|||
|
LPMediumFreeBlock: PMediumFreeBlock;
|
|||
|
begin
|
|||
|
{$ifdef EnableMMX}
|
|||
|
{$ifndef ForceMMX}
|
|||
|
UseMMX := MMX_Supported;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{Initialize the memory manager}
|
|||
|
{-------------Set up the small block types-------------}
|
|||
|
LPreviousBlockSize := 0;
|
|||
|
for i := 0 to high(SmallBlockTypes) do
|
|||
|
begin
|
|||
|
{Set the move procedure}
|
|||
|
{$ifdef UseCustomFixedSizeMoveRoutines}
|
|||
|
{The upsize move procedure may move chunks in 16 bytes even with 8-byte
|
|||
|
alignment, since the new size will always be at least 8 bytes bigger than
|
|||
|
the old size.}
|
|||
|
if not Assigned(SmallBlockTypes[i].UpsizeMoveProcedure) then
|
|||
|
{$ifdef UseCustomVariableSizeMoveRoutines}
|
|||
|
SmallBlockTypes[i].UpsizeMoveProcedure := MoveX16L4;
|
|||
|
{$else}
|
|||
|
SmallBlockTypes[i].UpsizeMoveProcedure := @System.Move;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{Set the first "available pool" to the block type itself, so that the
|
|||
|
allocation routines know that there are currently no pools with free
|
|||
|
blocks of this size.}
|
|||
|
SmallBlockTypes[i].PreviousPartiallyFreePool := @SmallBlockTypes[i];
|
|||
|
SmallBlockTypes[i].NextPartiallyFreePool := @SmallBlockTypes[i];
|
|||
|
{Set the block size to block type index translation table}
|
|||
|
for LSizeInd := (LPreviousBlockSize div SmallBlockGranularity) to ((SmallBlockTypes[i].BlockSize - 1) div SmallBlockGranularity) do
|
|||
|
AllocSize2SmallBlockTypeIndX4[LSizeInd] := i * 4;
|
|||
|
{Cannot sequential feed yet: Ensure that the next address is greater than
|
|||
|
the maximum address}
|
|||
|
SmallBlockTypes[i].MaxSequentialFeedBlockAddress := pointer(0);
|
|||
|
SmallBlockTypes[i].NextSequentialFeedBlockAddress := pointer(1);
|
|||
|
{Get the mask to use for finding a medium block suitable for a block pool}
|
|||
|
LMinimumPoolSize :=
|
|||
|
((SmallBlockTypes[i].BlockSize * MinimumSmallBlocksPerPool
|
|||
|
+ SmallBlockPoolHeaderSize + MediumBlockGranularity - 1 - MediumBlockSizeOffset)
|
|||
|
and -MediumBlockGranularity) + MediumBlockSizeOffset;
|
|||
|
if LMinimumPoolSize < MinimumMediumBlockSize then
|
|||
|
LMinimumPoolSize := MinimumMediumBlockSize;
|
|||
|
{Get the closest group number for the minimum pool size}
|
|||
|
LGroupNumber := (LMinimumPoolSize - MinimumMediumBlockSize + MediumBlockBinsPerGroup * MediumBlockGranularity div 2)
|
|||
|
div (MediumBlockBinsPerGroup * MediumBlockGranularity);
|
|||
|
{Too large?}
|
|||
|
if LGroupNumber > 7 then
|
|||
|
LGroupNumber := 7;
|
|||
|
{Set the bitmap}
|
|||
|
SmallBlockTypes[i].AllowedGroupsForBlockPoolBitmap := Byte(-(1 shl LGroupNumber));
|
|||
|
{Set the minimum pool size}
|
|||
|
SmallBlockTypes[i].MinimumBlockPoolSize := MinimumMediumBlockSize + LGroupNumber * (MediumBlockBinsPerGroup * MediumBlockGranularity);
|
|||
|
{Get the optimal block pool size}
|
|||
|
LOptimalPoolSize := ((SmallBlockTypes[i].BlockSize * TargetSmallBlocksPerPool
|
|||
|
+ SmallBlockPoolHeaderSize + MediumBlockGranularity - 1 - MediumBlockSizeOffset)
|
|||
|
and -MediumBlockGranularity) + MediumBlockSizeOffset;
|
|||
|
{Limit the optimal pool size to within range}
|
|||
|
if LOptimalPoolSize < OptimalSmallBlockPoolSizeLowerLimit then
|
|||
|
LOptimalPoolSize := OptimalSmallBlockPoolSizeLowerLimit;
|
|||
|
if LOptimalPoolSize > OptimalSmallBlockPoolSizeUpperLimit then
|
|||
|
LOptimalPoolSize := OptimalSmallBlockPoolSizeUpperLimit;
|
|||
|
{How many blocks will fit in the adjusted optimal size?}
|
|||
|
LBlocksPerPool := (LOptimalPoolSize - SmallBlockPoolHeaderSize) div SmallBlockTypes[i].BlockSize;
|
|||
|
{Recalculate the optimal pool size to minimize wastage due to a partial
|
|||
|
last block.}
|
|||
|
SmallBlockTypes[i].OptimalBlockPoolSize :=
|
|||
|
((LBlocksPerPool * SmallBlockTypes[i].BlockSize + SmallBlockPoolHeaderSize + MediumBlockGranularity - 1 - MediumBlockSizeOffset) and -MediumBlockGranularity) + MediumBlockSizeOffset;
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{Debug checks}
|
|||
|
if (SmallBlockTypes[i].OptimalBlockPoolSize < MinimumMediumBlockSize)
|
|||
|
or (SmallBlockTypes[i].BlockSize div SmallBlockGranularity * SmallBlockGranularity <> SmallBlockTypes[i].BlockSize) then
|
|||
|
begin
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Set the previous small block size}
|
|||
|
LPreviousBlockSize := SmallBlockTypes[i].BlockSize;
|
|||
|
end;
|
|||
|
{-------------------Set up the medium blocks-------------------}
|
|||
|
{$ifdef CheckHeapForCorruption}
|
|||
|
{Check that there are no gaps between where the small blocks end and the
|
|||
|
medium blocks start}
|
|||
|
if (((MaximumSmallBlockSize - 3) + (MediumBlockGranularity - 1 + BlockHeaderSize - MediumBlockSizeOffset))
|
|||
|
and -MediumBlockGranularity) + MediumBlockSizeOffset < MinimumMediumBlockSize then
|
|||
|
begin
|
|||
|
{$ifdef BCB6OrDelphi7AndUp}
|
|||
|
System.Error(reInvalidPtr);
|
|||
|
{$else}
|
|||
|
System.RunError(reInvalidPtr);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{There are currently no medium block pools}
|
|||
|
MediumBlockPoolsCircularList.PreviousMediumBlockPoolHeader := @MediumBlockPoolsCircularList;
|
|||
|
MediumBlockPoolsCircularList.NextMediumBlockPoolHeader := @MediumBlockPoolsCircularList;
|
|||
|
{All medium bins are empty}
|
|||
|
for i := 0 to high(MediumBlockBins) do
|
|||
|
begin
|
|||
|
LPMediumFreeBlock := @MediumBlockBins[i];
|
|||
|
LPMediumFreeBlock.PreviousFreeBlock := LPMediumFreeBlock;
|
|||
|
LPMediumFreeBlock.NextFreeBlock := LPMediumFreeBlock;
|
|||
|
end;
|
|||
|
{------------------Set up the large blocks---------------------}
|
|||
|
LargeBlocksCircularList.PreviousLargeBlockHeader := @LargeBlocksCircularList;
|
|||
|
LargeBlocksCircularList.NextLargeBlockHeader := @LargeBlocksCircularList;
|
|||
|
{------------------Set up the debugging structures---------------------}
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{Set up the fake VMT}
|
|||
|
{Copy the basic info from the TFreedObject class}
|
|||
|
System.Move(Pointer(Integer(TFreedObject) + vmtSelfPtr + 4)^,
|
|||
|
FreedObjectVMT.VMTData[vmtSelfPtr + 4], vmtParent - vmtSelfPtr);
|
|||
|
PCardinal(@FreedObjectVMT.VMTData[vmtSelfPtr])^ := Cardinal(@FreedObjectVMT.VMTMethods[0]);
|
|||
|
{Set up the virtual method table}
|
|||
|
for i := 0 to MaxFakeVMTEntries - 1 do
|
|||
|
begin
|
|||
|
PCardinal(@FreedObjectVMT.VMTMethods[low(FreedObjectVMT.VMTMethods) + Integer(i * 4)])^ :=
|
|||
|
Cardinal(@TFreedObject.GetVirtualMethodIndex) + i * 6;
|
|||
|
{$ifdef CatchUseOfFreedInterfaces}
|
|||
|
VMTBadInterface[i] := @TFreedObject.InterfaceError;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{Set up the default log file name}
|
|||
|
SetDefaultMMLogFileName;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
{Installs the memory manager (InitializeMemoryManager should be called first)}
|
|||
|
procedure InstallMemoryManager;
|
|||
|
{$ifndef Linux}
|
|||
|
var
|
|||
|
i, LCurrentProcessID: Cardinal;
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
if not FastMMIsInstalled then
|
|||
|
begin
|
|||
|
{$ifndef Linux}
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{Try to reserve the 64K block}
|
|||
|
ReservedBlock := VirtualAlloc(Pointer(DebugReservedAddress), 65536, MEM_RESERVE, PAGE_NOACCESS);
|
|||
|
{$endif}
|
|||
|
{Build a string identifying the current process}
|
|||
|
LCurrentProcessID := GetCurrentProcessId;
|
|||
|
for i := 0 to 7 do
|
|||
|
begin
|
|||
|
UniqueProcessIDString[8 - i] :=
|
|||
|
HexTable[((LCurrentProcessID shr (i * 4)) and $F)];
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
UniqueProcessIDStringBE[8 - i] := UniqueProcessIDString[8 - i];
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{$ifdef AttemptToUseSharedMM}
|
|||
|
{Is the replacement memory manager already installed for this process?}
|
|||
|
MMWindow := FindWindow('STATIC', PChar(@UniqueProcessIDString[1]));
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
MMWindowBE := FindWindow('STATIC', PChar(@UniqueProcessIDStringBE[1]));
|
|||
|
{$endif}
|
|||
|
if (MMWindow = 0)
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
and (MMWindowBE = 0)
|
|||
|
{$endif}
|
|||
|
then
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
{$ifdef ShareMM}
|
|||
|
{Share the MM with other DLLs? - if this DLL is unloaded, then
|
|||
|
dependent DLLs will cause a crash.}
|
|||
|
{$ifndef ShareMMIfLibrary}
|
|||
|
if not IsLibrary then
|
|||
|
{$endif}
|
|||
|
begin
|
|||
|
{No memory manager installed yet - create the invisible window}
|
|||
|
MMWindow := CreateWindow('STATIC', PChar(@UniqueProcessIDString[1]),
|
|||
|
WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
MMWindowBE := CreateWindow('STATIC', PChar(@UniqueProcessIDStringBE[1]),
|
|||
|
WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
|
|||
|
{$endif}
|
|||
|
{The window data is a pointer to this memory manager}
|
|||
|
if MMWindow <> 0 then
|
|||
|
SetWindowLong(MMWindow, GWL_USERDATA, Integer(@NewMemoryManager));
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
if MMWindowBE <> 0 then
|
|||
|
SetWindowLong(MMWindowBE, GWL_USERDATA, Integer(@NewMemoryManager));
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{We will be using this memory manager}
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
NewMemoryManager.GetMem := FastGetMem;
|
|||
|
NewMemoryManager.FreeMem := FastFreeMem;
|
|||
|
NewMemoryManager.ReallocMem := FastReallocMem;
|
|||
|
{$else}
|
|||
|
NewMemoryManager.GetMem := DebugGetMem;
|
|||
|
NewMemoryManager.FreeMem := DebugFreeMem;
|
|||
|
NewMemoryManager.ReallocMem := DebugReallocMem;
|
|||
|
{$endif}
|
|||
|
{$ifdef BDS2006AndUp}
|
|||
|
{$ifndef FullDebugMode}
|
|||
|
NewMemoryManager.AllocMem := FastAllocMem;
|
|||
|
{$else}
|
|||
|
NewMemoryManager.AllocMem := DebugAllocMem;
|
|||
|
{$endif}
|
|||
|
NewMemoryManager.RegisterExpectedMemoryLeak := RegisterExpectedMemoryLeak;
|
|||
|
NewMemoryManager.UnRegisterExpectedMemoryLeak := UnRegisterExpectedMemoryLeak;
|
|||
|
{$endif}
|
|||
|
{Owns the MMWindow}
|
|||
|
IsMemoryManagerOwner := True;
|
|||
|
{$ifdef AttemptToUseSharedMM}
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
{Get the address of the shared memory manager}
|
|||
|
{$ifndef BDS2006AndUp}
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
if MMWindow <> 0 then
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
NewMemoryManager := PMemoryManager(GetWindowLong(MMWindow, GWL_USERDATA))^;
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
NewMemoryManager := PMemoryManager(GetWindowLong(MMWindowBE, GWL_USERDATA))^;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
if MMWindow <> 0 then
|
|||
|
begin
|
|||
|
{$endif}
|
|||
|
NewMemoryManager := PMemoryManagerEx(GetWindowLong(MMWindow, GWL_USERDATA))^;
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
NewMemoryManager := PMemoryManagerEx(GetWindowLong(MMWindowBE, GWL_USERDATA))^;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{The MMWindow is owned by the main program (not this DLL)}
|
|||
|
IsMemoryManagerOwner := False;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{Save the old memory manager}
|
|||
|
GetMemoryManager(OldMemoryManager);
|
|||
|
{Replace the memory manager with either this one or the shared one.}
|
|||
|
SetMemoryManager(NewMemoryManager);
|
|||
|
{FastMM is now installed}
|
|||
|
FastMMIsInstalled := True;
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
if IsMemoryManagerOwner then
|
|||
|
OutputDebugString(FastMMInstallMsg)
|
|||
|
else
|
|||
|
OutputDebugString(FastMMInstallSharedMsg);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure UninstallMemoryManager;
|
|||
|
begin
|
|||
|
{Is this the owner of the shared MM window?}
|
|||
|
if IsMemoryManagerOwner then
|
|||
|
begin
|
|||
|
{$ifdef ShareMM}
|
|||
|
{Destroy the window}
|
|||
|
if MMWindow <> 0 then
|
|||
|
begin
|
|||
|
DestroyWindow(MMWindow);
|
|||
|
MMWindow := 0;
|
|||
|
end;
|
|||
|
{$ifdef EnableSharingWithDefaultMM}
|
|||
|
if MMWindowBE <> 0 then
|
|||
|
begin
|
|||
|
DestroyWindow(MMWindowBE);
|
|||
|
MMWindowBE := 0;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{$ifdef FullDebugMode}
|
|||
|
{Release the reserved block}
|
|||
|
if ReservedBlock <> nil then
|
|||
|
begin
|
|||
|
VirtualFree(ReservedBlock, 0, MEM_RELEASE);
|
|||
|
ReservedBlock := nil;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
{$ifndef DetectMMOperationsAfterUninstall}
|
|||
|
{Restore the old memory manager}
|
|||
|
SetMemoryManager(OldMemoryManager);
|
|||
|
{$else}
|
|||
|
{Set the invalid memory manager: no more MM operations allowed}
|
|||
|
SetMemoryManager(InvalidMemoryManager);
|
|||
|
{$endif}
|
|||
|
{Memory manager has been uninstalled}
|
|||
|
FastMMIsInstalled := False;
|
|||
|
{$ifdef UseOutputDebugString}
|
|||
|
if IsMemoryManagerOwner then
|
|||
|
OutputDebugString(FastMMuninstallMsg)
|
|||
|
else
|
|||
|
OutputDebugString(FastMMUninstallSharedMsg);
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
|
|||
|
initialization
|
|||
|
{$ifndef BCB}
|
|||
|
{Initialize all the lookup tables, etc. for the memory manager}
|
|||
|
InitializeMemoryManager;
|
|||
|
{Has another MM been set, or has the Borland MM been used? If so, this file
|
|||
|
is not the first unit in the uses clause of the project's .dpr file.}
|
|||
|
if CheckCanInstallMemoryManager then
|
|||
|
begin
|
|||
|
{$ifdef ClearLogFileOnStartup}
|
|||
|
DeleteEventLog;
|
|||
|
{$endif}
|
|||
|
InstallMemoryManager;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
|
|||
|
finalization
|
|||
|
{Restore the old memory manager if FastMM has been installed}
|
|||
|
if FastMMIsInstalled then
|
|||
|
begin
|
|||
|
{$ifndef NeverUninstall}
|
|||
|
{Uninstall FastMM}
|
|||
|
UninstallMemoryManager;
|
|||
|
{$endif}
|
|||
|
{Do we own the memory manager, or are we just sharing it?}
|
|||
|
if IsMemoryManagerOwner then
|
|||
|
begin
|
|||
|
{$ifdef CheckUseOfFreedBlocksOnShutdown}
|
|||
|
CheckBlocksOnShutdown(
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
True
|
|||
|
{$ifdef RequireIDEPresenceForLeakReporting}
|
|||
|
and DelphiIsRunning
|
|||
|
{$endif}
|
|||
|
{$ifdef RequireDebuggerPresenceForLeakReporting}
|
|||
|
and (DebugHook <> 0)
|
|||
|
{$endif}
|
|||
|
{$ifdef ManualLeakReportingControl}
|
|||
|
and ReportMemoryLeaksOnShutdown
|
|||
|
{$endif}
|
|||
|
{$else}
|
|||
|
False
|
|||
|
{$endif}
|
|||
|
);
|
|||
|
{$else}
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
if True
|
|||
|
{$ifdef RequireIDEPresenceForLeakReporting}
|
|||
|
and DelphiIsRunning
|
|||
|
{$endif}
|
|||
|
{$ifdef RequireDebuggerPresenceForLeakReporting}
|
|||
|
and (DebugHook <> 0)
|
|||
|
{$endif}
|
|||
|
{$ifdef ManualLeakReportingControl}
|
|||
|
and ReportMemoryLeaksOnShutdown
|
|||
|
{$endif}
|
|||
|
then
|
|||
|
CheckBlocksOnShutdown(True);
|
|||
|
{$endif}
|
|||
|
{$endif}
|
|||
|
{$ifdef EnableMemoryLeakReporting}
|
|||
|
{Free the expected memory leaks list}
|
|||
|
if ExpectedMemoryLeaks <> nil then
|
|||
|
begin
|
|||
|
VirtualFree(ExpectedMemoryLeaks, 0, MEM_RELEASE);
|
|||
|
ExpectedMemoryLeaks := nil;
|
|||
|
end;
|
|||
|
{$endif}
|
|||
|
{$ifndef NeverUninstall}
|
|||
|
{Clean up: Free all memory. If this is a .DLL that owns its own MM, then
|
|||
|
it is necessary to prevent the main application from running out of
|
|||
|
address space.}
|
|||
|
FreeAllMemory;
|
|||
|
{$endif}
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
end.
|