Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 130 additions & 25 deletions FastMM5.pas
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,10 @@ interface
AllocatedByThread: Cardinal;
{The ID of the thread that freed the block}
FreedByThread: Cardinal;
{Reserved space for future use.}
ReservedSpace2: Byte;
{Flags indicating which optional debug features (CDebugBlockFeature* bitmask) were applied to this block. These
are tracked per block so that FastMM_SetDebugModeOptions may be called while debug mode is active without
invalidating blocks that were allocated under different settings.}
DebugFeatureFlags: Byte;
{The number of entries in the allocation and free call stacks in the debug footer.}
StackTraceEntryCount: Byte;
{The debug block signature. This will be CIsDebugBlockFlag if the debug block is in use, and (CIsDebugBlockFlag or
Expand Down Expand Up @@ -441,8 +443,12 @@ TFastMM_WalkAllocatedBlocks_BlockInfo = record
TFastMM_MinimumAddressAlignment = (maa8Bytes, maa16Bytes, maa32Bytes, maa64Bytes);
TFastMM_MinimumAddressAlignmentSet = set of TFastMM_MinimumAddressAlignment;

{Options that may be enabled in debug mode.}
TFastMM_DebugModeOption = (dmoNeverFreeSmallBlockSpans, dmoNeverMergeFreeMediumBlocks);
{Options that may be enabled in debug mode. The dmoNo* options enable a lightweight "profiling" debug mode: Debug
headers (allocation numbers, thread IDs, timestamps and stack traces) are still maintained, but the expensive fill
patterns and/or checksum calculations are skipped. This makes debug mode cheap enough for use in allocation
profiling scenarios, at the cost of reduced corruption and use-after-free detection.}
TFastMM_DebugModeOption = (dmoNeverFreeSmallBlockSpans, dmoNeverMergeFreeMediumBlocks, dmoNoBlockFillPatterns,
dmoNoBlockCheckSums);
TFastMM_DebugModeOptions = set of TFastMM_DebugModeOption;

{The formats in which text files (e.g. the event log) may be written. Controlled via the FastMM_TextFileEncoding
Expand Down Expand Up @@ -710,7 +716,15 @@ function FastMM_DebugModeActive: Boolean;
{Gets and sets the options that should apply when debug mode is active, i.e. FastMM_DebugModeActive = True. The initial
default is [dmoNeverFreeSmallBlockSpans, dmoNeverMergeFreeMediumBlocks]. If memory usage is excessive when debug mode
is enabled consider removing the dmoNeverMergeFreeMediumBlocks option, and if that is not sufficient to reduce memory
pressure then the dmoNeverFreeSmallBlockSpans option as well.}
pressure then the dmoNeverFreeSmallBlockSpans option as well.
For a lightweight "profiling" debug mode add dmoNoBlockFillPatterns and dmoNoBlockCheckSums: Debug headers and stack
traces are still maintained (so allocation profiling tools keep working), but the fill patterns and checksums are
skipped, greatly reducing the per-operation overhead. The trade-offs: without fill patterns there is no
use-after-free/uninitialized-memory detection (and FastMM_Begin/EndEraseAllocatedBlockContent/EraseFreedBlockContent
take precedence - blocks are still erased while those are active); without checksums, corruption of the debug header
fields and stack traces is no longer detected - fixed guard values in the checksum slots still catch buffer overruns
into the header/footer and protect the per-block feature flags themselves. The options are tracked per block, so they
may be changed while debug mode is active - existing blocks retain the features they were allocated with.}
function FastMM_GetDebugModeOptions: TFastMM_DebugModeOptions;
procedure FastMM_SetDebugModeOptions(ADebugModeOptions: TFastMM_DebugModeOptions);

Expand Down Expand Up @@ -1610,6 +1624,20 @@ TSimpleLock = record
CDebugBlockFooterCheckSumSize = SizeOf(Cardinal);
CDebugBlockFooterReservedSpaceForFreeMediumBlock = SizeOf(Integer);

{TFastMM_DebugBlockHeader.DebugFeatureFlags bitmask values.}
{The block carries valid header and footer checksums.}
CDebugBlockFeatureCheckSums = 1;
{The user area of the block has been filled with the debug pattern. (Only ever consulted for freed blocks, where an
intact fill pattern is used to detect writes to the block after it was freed.)}
CDebugBlockFeatureFillPattern = 2;

{The value stored in the header and footer checksum slots of blocks allocated with the dmoNoBlockCheckSums option.
The guard values still catch buffer overruns into the debug header or footer, and they also cross-check the
DebugFeatureFlags byte itself: If a corruption flips the checksums feature flag in either direction, the checksum
slot content no longer matches what the flag promises (a real checksum practically never equals the guard value, and
vice versa), so the corruption is still reported.}
CDebugBlockNoCheckSumsGuardValue = Cardinal($FA57B10C);

CSmallBlockSpanHeaderSize = SizeOf(TSmallBlockSpanHeader);
CMediumBlockSpanHeaderSize = SizeOf(TMediumBlockSpanHeader);

Expand Down Expand Up @@ -1797,6 +1825,12 @@ TSimpleLock = record
ApplyDebugModeOptions call.}
MayFreeSmallBlockSpans: Boolean = True;
MayMergeFreeMediumBlocks: Boolean = True;
{Whether newly allocated/freed debug blocks get the debug fill patterns and header/footer checksums. Disabled via
the dmoNoBlockFillPatterns and dmoNoBlockCheckSums debug mode options ("profiling mode"). These flags only apply to
new operations - the features actually applied to each block are recorded in its TFastMM_DebugBlockHeader.
DebugFeatureFlags, which is what all verification code consults.}
UseDebugBlockFillPatterns: Boolean = True;
UseDebugBlockCheckSums: Boolean = True;

{The number of entries in stack traces in debug mode.}
DebugMode_StackTrace_EntryCount: Byte;
Expand Down Expand Up @@ -4225,6 +4259,23 @@ procedure LogDebugBlockFooterInvalid(APDebugBlockHeader: PFastMM_DebugBlockHeade
(optionally) logs and/or displays the error and returns False.}
function CheckDebugBlockHeaderAndFooterCheckSumsValid(APDebugBlockHeader: PFastMM_DebugBlockHeader): Boolean;
begin
{Blocks allocated with the dmoNoBlockCheckSums option do not carry checksums, but their checksum slots hold fixed
guard values that are verified instead.}
if APDebugBlockHeader.DebugFeatureFlags and CDebugBlockFeatureCheckSums = 0 then
begin
if APDebugBlockHeader.HeaderCheckSum <> CDebugBlockNoCheckSumsGuardValue then
begin
LogDebugBlockHeaderInvalid(APDebugBlockHeader);
Exit(False);
end;
if APDebugBlockHeader.DebugFooterPtr^ <> CDebugBlockNoCheckSumsGuardValue then
begin
LogDebugBlockFooterInvalid(APDebugBlockHeader);
Exit(False);
end;
Exit(True);
end;

if APDebugBlockHeader.CalculateHeaderCheckSum <> APDebugBlockHeader.HeaderCheckSum then
begin
LogDebugBlockHeaderInvalid(APDebugBlockHeader);
Expand Down Expand Up @@ -4512,7 +4563,8 @@ function CheckFreedDebugBlockFillPatternIntact(APDebugBlockHeader: PFastMM_Debug
function CheckFreeDebugBlockIntact(APDebugBlockHeader: PFastMM_DebugBlockHeader): Boolean;
begin
Result := CheckDebugBlockHeaderAndFooterCheckSumsValid(APDebugBlockHeader)
and CheckFreedDebugBlockFillPatternIntact(APDebugBlockHeader);
and ((APDebugBlockHeader.DebugFeatureFlags and CDebugBlockFeatureFillPattern = 0)
or CheckFreedDebugBlockFillPatternIntact(APDebugBlockHeader));
end;

procedure EnsureEmergencyReserveAddressSpaceAllocated;
Expand Down Expand Up @@ -4579,14 +4631,23 @@ function FastMM_FreeMem_FreeDebugBlock(APointer: Pointer): Integer;
FastMM_GetStackTrace(LPActualBlock.DebugFooter_FreeStackTracePtr, LPActualBlock.StackTraceEntryCount, CFastMM_StackTrace_SkipFrames_FreeMem);
LPActualBlock.PreviouslyUsedByClass := PPointer(APointer)^;

{Fill the user area of the block with the debug pattern.}
FillFreedDebugBlockWithDebugPattern(LPActualBlock);
{Fill the user area of the block with the debug pattern, recording in the header whether that was done so that the
fill pattern is only verified on reuse if it was actually applied. While FastMM_BeginEraseFreedBlockContent is
active the block is always filled, even if the dmoNoBlockFillPatterns option is enabled.}
if UseDebugBlockFillPatterns or EraseFreedBlockContentActive then
begin
FillFreedDebugBlockWithDebugPattern(LPActualBlock);
LPActualBlock.DebugFeatureFlags := LPActualBlock.DebugFeatureFlags or CDebugBlockFeatureFillPattern;
end
else
LPActualBlock.DebugFeatureFlags := LPActualBlock.DebugFeatureFlags and (not CDebugBlockFeatureFillPattern);

{The block is now free.}
LPActualBlock.DebugBlockFlags := CIsDebugBlockFlag or CBlockIsFreeFlag;

{Update the header and footer checksums}
LPActualBlock.CalculateAndSetHeaderAndFooterCheckSums;
{Update the header and footer checksums, if the block carries checksums.}
if LPActualBlock.DebugFeatureFlags and CDebugBlockFeatureCheckSums <> 0 then
LPActualBlock.CalculateAndSetHeaderAndFooterCheckSums;

{Restore the debug information flag.}
SetBlockHasDebugInfo(LPActualBlock, True);
Expand Down Expand Up @@ -4623,7 +4684,8 @@ function FastMM_ReallocMem_ReallocDebugBlock(APointer: Pointer; ANewSize: Native

{Update the user block size and set the new header checksum. The footer checksum should be unchanged.}
LPActualBlock.UserSize := ANewSize;
LPActualBlock.HeaderCheckSum := LPActualBlock.CalculateHeaderCheckSum;
if LPActualBlock.DebugFeatureFlags and CDebugBlockFeatureCheckSums <> 0 then
LPActualBlock.HeaderCheckSum := LPActualBlock.CalculateHeaderCheckSum;

{Move the debug footer just beyond the new user size.}
LPNewFooter := LPActualBlock.DebugFooterPtr;
Expand All @@ -4646,7 +4708,8 @@ function FastMM_ReallocMem_ReallocDebugBlock(APointer: Pointer; ANewSize: Native

{Set the user size in the header for the new block and calculate the new header checksum.}
PFastMM_DebugBlockHeader(Result).UserSize := ANewSize;
PFastMM_DebugBlockHeader(Result).HeaderCheckSum := PFastMM_DebugBlockHeader(Result).CalculateHeaderCheckSum;
if PFastMM_DebugBlockHeader(Result).DebugFeatureFlags and CDebugBlockFeatureCheckSums <> 0 then
PFastMM_DebugBlockHeader(Result).HeaderCheckSum := PFastMM_DebugBlockHeader(Result).CalculateHeaderCheckSum;

{Move the debug footer over to the end of the user data.}
LPOldFooter := LPActualBlock.DebugFooterPtr;
Expand Down Expand Up @@ -4686,11 +4749,20 @@ procedure LogInvalidFreeMemOrReallocMem(APointer: Pointer; AIsReallocMemCall: Bo
if PBlockStatusFlags(APointer)[-1] <> (CBlockIsFreeFlag or CIsDebugBlockFlag) then
Exit;

{Check that the debug block header is intact. If it is, then a meaningful error may be returned.}
{Check that the debug block header is intact. If it is, then a meaningful error may be returned. For blocks
allocated with dmoNoBlockCheckSums the header guard value is verified instead of the checksum.}
LPDebugBlockHeader := @PFastMM_DebugBlockHeader(APointer)[-1];
LHeaderChecksum := LPDebugBlockHeader.CalculateHeaderCheckSum;
if LPDebugBlockHeader.HeaderCheckSum <> LHeaderChecksum then
Exit;
if LPDebugBlockHeader.DebugFeatureFlags and CDebugBlockFeatureCheckSums <> 0 then
begin
LHeaderChecksum := LPDebugBlockHeader.CalculateHeaderCheckSum;
if LPDebugBlockHeader.HeaderCheckSum <> LHeaderChecksum then
Exit;
end
else
begin
if LPDebugBlockHeader.HeaderCheckSum <> CDebugBlockNoCheckSumsGuardValue then
Exit;
end;

LTokenValues := Default(TEventLogTokenValues);

Expand Down Expand Up @@ -8262,10 +8334,25 @@ function FastMM_DebugGetMem_GetDebugBlock(ASize: NativeInt; AFillBlockWithDebugP
PFastMM_DebugBlockHeader(Result).AllocatedByThread := OS_GetCurrentThreadID;
PFastMM_DebugBlockHeader(Result).FreedByThread := 0;
PFastMM_DebugBlockHeader(Result).DebugBlockFlags := CIsDebugBlockFlag;
PFastMM_DebugBlockHeader(Result).CalculateAndSetHeaderAndFooterCheckSums;
{Record the debug features applied to this block (must be set before the header checksum is calculated). The fill
pattern feature flag is only relevant for freed blocks and is set when the block is freed.}
if UseDebugBlockCheckSums then
begin
PFastMM_DebugBlockHeader(Result).DebugFeatureFlags := CDebugBlockFeatureCheckSums;
PFastMM_DebugBlockHeader(Result).CalculateAndSetHeaderAndFooterCheckSums;
end
else
begin
{Without checksums the checksum slots are filled with fixed guard values: They still catch buffer overruns into
the header/footer and cross-check the DebugFeatureFlags byte (see CDebugBlockNoCheckSumsGuardValue).}
PFastMM_DebugBlockHeader(Result).DebugFeatureFlags := 0;
PFastMM_DebugBlockHeader(Result).HeaderCheckSum := CDebugBlockNoCheckSumsGuardValue;
PFastMM_DebugBlockHeader(Result).DebugFooterPtr^ := CDebugBlockNoCheckSumsGuardValue;
end;

{Fill the block with the debug pattern}
if AFillBlockWithDebugPattern then
{Fill the block with the debug pattern. While FastMM_BeginEraseAllocatedBlockContent is active the block is always
filled, even if the dmoNoBlockFillPatterns option is enabled.}
if AFillBlockWithDebugPattern and (UseDebugBlockFillPatterns or EraseAllocatedBlockContentActive) then
FillAllocatedDebugBlockWithDebugPattern(Result);

{Set the flag in the actual block header to indicate that the block contains debug information.}
Expand Down Expand Up @@ -8985,6 +9072,7 @@ procedure FastMM_ScanDebugBlocksForCorruption_CallBack(const ABlockInfo: TFastMM
CMaxCorruptionCheckAttempts = 100; //Excessively high, but insignificant relative to the cost of a false positive
var
LRemainingAttempts: Integer;
LCheckSumsOrGuardsValid: Boolean;
begin
{If it is not a debug mode block then there's nothing to check.}
if ABlockInfo.DebugInformation = nil then
Expand All @@ -9007,9 +9095,21 @@ procedure FastMM_ScanDebugBlocksForCorruption_CallBack(const ABlockInfo: TFastMM
LRemainingAttempts := CMaxCorruptionCheckAttempts;
while True do
begin
{Check whether the header and footers checksums are currently valid. If they are then break out of the loop.}
if (ABlockInfo.DebugInformation.CalculateHeaderCheckSum = ABlockInfo.DebugInformation.HeaderCheckSum)
and (ABlockInfo.DebugInformation.CalculateFooterCheckSum = ABlockInfo.DebugInformation.DebugFooterPtr^) then
{Check whether the block header and footer are currently intact: Blocks with checksums are verified against the
calculated checksums, blocks allocated with dmoNoBlockCheckSums are verified against the fixed guard values in the
checksum slots. If they are intact then break out of the loop.}
if ABlockInfo.DebugInformation.DebugFeatureFlags and CDebugBlockFeatureCheckSums <> 0 then
begin
LCheckSumsOrGuardsValid := (ABlockInfo.DebugInformation.CalculateHeaderCheckSum = ABlockInfo.DebugInformation.HeaderCheckSum)
and (ABlockInfo.DebugInformation.CalculateFooterCheckSum = ABlockInfo.DebugInformation.DebugFooterPtr^);
end
else
begin
LCheckSumsOrGuardsValid := (ABlockInfo.DebugInformation.HeaderCheckSum = CDebugBlockNoCheckSumsGuardValue)
and (ABlockInfo.DebugInformation.DebugFooterPtr^ = CDebugBlockNoCheckSumsGuardValue);
end;

if LCheckSumsOrGuardsValid then
begin
Break;
end;
Expand All @@ -9032,9 +9132,12 @@ procedure FastMM_ScanDebugBlocksForCorruption_CallBack(const ABlockInfo: TFastMM

end;

{If it is a free block, check whether it has been modified after being freed. A free debug block should never be
modified while the arena is locked, so we do not have to retry this check.}
if ABlockInfo.BlockIsFree and (not CheckFreedDebugBlockFillPatternIntact(ABlockInfo.DebugInformation)) then
{If it is a free block that was filled with the debug pattern when it was freed, check whether it has been modified
after being freed. A free debug block should never be modified while the arena is locked, so we do not have to retry
this check.}
if ABlockInfo.BlockIsFree
and (ABlockInfo.DebugInformation.DebugFeatureFlags and CDebugBlockFeatureFillPattern <> 0)
and (not CheckFreedDebugBlockFillPatternIntact(ABlockInfo.DebugInformation)) then
System.Error(reInvalidPtr);
end;

Expand Down Expand Up @@ -10844,6 +10947,8 @@ procedure ApplyDebugModeOptions;
MayFreeSmallBlockSpans := True;
MayMergeFreeMediumBlocks := True;
end;
UseDebugBlockFillPatterns := not (dmoNoBlockFillPatterns in DebugModeOptions);
UseDebugBlockCheckSums := not (dmoNoBlockCheckSums in DebugModeOptions);
end;

{Configures the appropriate entry points for each of the memory manager functions according to the current settings,
Expand Down