reactos/sdk/lib/ucrt/heap/debug_heap.cpp

1980 lines
64 KiB
C++
Raw Normal View History

//
// debug_heap.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The implementation of the CRT Debug Heap.
//
#ifndef _DEBUG
#error This file is supported only in debug builds
#define _DEBUG // For design-time support, when editing/viewing CRT sources
#endif
#include <corecrt_internal.h>
#include <malloc.h>
#include <minmax.h>
#include <new.h>
#include <stdio.h>
#include <stdlib.h>
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Constant Data
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#define _ALLOCATION_FILE_LINENUM "\nMemory allocated at %hs(%d).\n"
#if _FREE_BLOCK != 0 || _NORMAL_BLOCK != 1 || _CRT_BLOCK != 2 || _IGNORE_BLOCK != 3 || _CLIENT_BLOCK != 4
#error Block numbers have changed!
#endif
static char const* const block_use_names[_MAX_BLOCKS]
{
"Free",
"Normal",
"CRT",
"Ignore",
"Client",
};
// The following values are non-zero, constant, odd, large, and atypical.
// * Non-zero values help find bugs that assume zero-filled data
// * Constant values are good so that memory filling is deterministic (to help
// make bugs reproducible). Of course, it is bad if the constant filling of
// weird values masks a bug.
// * Mathematically odd numbers are good for finding bugs assuming a cleared
// lower bit (e.g. properly aligned pointers to types other than char are not
// odd).
// * Large byte values are less typical and are useful for finding bad addresses.
// * Atypical values (i.e., not too often) are good because they typically cause
// early detection in code.
// * For the case of the no-man's land and free blocks, if you store to any of
// these locations, the memory integrity checker will detect it.
//
// The align_land_fill was changed from 0xBD to 0xED to ensure that four bytes of
// that value (0xEDEDEDED) would form an inaccessible address outside of the lower
// 3GB of a 32-bit process address space.
static unsigned char const no_mans_land_fill{0xFD}; // Fill unaligned no-man's land
static unsigned char const align_land_fill {0xED}; // Fill aligned no-man's land
static unsigned char const dead_land_fill {0xDD}; // Fill free objects with this
static unsigned char const clean_land_fill {0xCD}; // Fill new objects with this
// The size of the no-man's land used in unaligned and aligned allocations:
static size_t const no_mans_land_size = 4;
static size_t const align_gap_size = sizeof(void *);
// For _IGNORE_BLOCK blocks, we use these request and line numbers as sentinels.
static long const request_number_for_ignore_blocks{0};
static int const line_number_for_ignore_blocks {static_cast<int>(0xFEDCBABC)};
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Types
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// For diagnostic purpose, blocks are allocated in the debug heap with extra
// information and stored in a doubly-linked list. This makes all blocks
// registered with how big they are, when they were allocated, and what they are
// used for.
struct _CrtMemBlockHeader
{
_CrtMemBlockHeader* _block_header_next;
_CrtMemBlockHeader* _block_header_prev;
char const* _file_name;
int _line_number;
int _block_use;
size_t _data_size;
long _request_number;
unsigned char _gap[no_mans_land_size];
// Followed by:
// unsigned char _data[_data_size];
// unsigned char _another_gap[no_mans_land_size];
};
static_assert(
sizeof(_CrtMemBlockHeader) % MEMORY_ALLOCATION_ALIGNMENT == 0,
"Incorrect debug heap block alignment");
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Global Mutable State (Synchronized by the AppCRT Heap Lock)
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// These are pointers to the first and last nodes in the debug heap's doubly
// linked list of allocation nodes.
static _CrtMemBlockHeader* __acrt_first_block{nullptr};
static _CrtMemBlockHeader* __acrt_last_block {nullptr};
// These are the statistics for the current state of the debug heap, storing the
// total number of bytes allocated over the life of the process, the total number
// of bytes currently allocated (but not freed), and the maximum number of bytes
// that were ever allocated at once.
static size_t __acrt_total_allocations {0};
static size_t __acrt_current_allocations{0};
static size_t __acrt_max_allocations {0};
// These states control the frequency with which _CrtCheckMemory. The units are
// "calls to allocation functions."
static unsigned __acrt_check_frequency{0};
static unsigned __acrt_check_counter {0};
// This is the current request number, which is incremented each time a new
// allocation request is made.
static long __acrt_current_request_number{1};
// These three globals had external linkage in older versions of the CRT and may
// be referenced by name in client code. The first stores the current debug
// heap options (flags). The second stores the next allocation number on which
// to break. The third stores the pointer to the dump client to be used when
// dumping heap objects.
#undef _crtDbgFlag
#undef _crtBreakAlloc
extern "C" int _crtDbgFlag{_CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_DEFAULT_DF};
extern "C" long _crtBreakAlloc{-1};
extern "C" _CRT_DUMP_CLIENT _pfnDumpClient{nullptr};
extern "C" int* __p__crtDbgFlag()
{
return &_crtDbgFlag;
}
extern "C" long* __p__crtBreakAlloc()
{
return &_crtBreakAlloc;
}
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Internal Utilities
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
static unsigned char* __cdecl block_from_header(_CrtMemBlockHeader* const header) throw()
{
return reinterpret_cast<unsigned char*>(header + 1);
}
static _CrtMemBlockHeader* __cdecl header_from_block(void const* const block) throw()
{
return static_cast<_CrtMemBlockHeader*>(const_cast<void*>(block)) - 1;
}
static bool __cdecl is_block_type_valid(int const block_use) throw()
{
return _BLOCK_TYPE(block_use) == _CLIENT_BLOCK
|| _BLOCK_TYPE(block_use) == _CRT_BLOCK
|| block_use == _NORMAL_BLOCK
|| block_use == _IGNORE_BLOCK;
}
// Tests the array of size bytes starting at first. Returns true if all of the
// bytes in the array have the given value; returns false otherwise.
static bool __cdecl check_bytes(
unsigned char const* const first,
unsigned char const value,
size_t const size
) throw()
{
unsigned char const* const last{first + size};
for (unsigned char const* it{first}; it != last; ++it)
{
if (*it != value)
return false;
}
return true;
}
// Tests whether the size bytes of memory starting at address p can be read from.
// The functionality is similar to that of the Windows API IsBadReadPtr(), which
// is now deprecated.
static bool __cdecl is_bad_read_pointer(void const* const p, size_t const size) throw()
{
SYSTEM_INFO system_info{};
GetSystemInfo(&system_info);
DWORD const page_size{system_info.dwPageSize};
// If the structure has zero length, then do not probe the structure for
// read accessibility or alignment.
if (size == 0)
return false;
// A null pointer can never be read from:
if (!p)
return true;
char const* start_address{static_cast<char const*>(p)};
char const* end_address {start_address + size - 1 };
if (end_address < start_address)
return true;
__try
{
*(volatile char*)start_address;
long const mask{~(static_cast<long>(page_size) - 1)};
start_address = reinterpret_cast<char*>(reinterpret_cast<uintptr_t>(start_address) & mask);
end_address = reinterpret_cast<char*>(reinterpret_cast<uintptr_t>(end_address) & mask);
while (start_address != end_address)
{
start_address = start_address + page_size;
*reinterpret_cast<const volatile char*>(start_address);
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return true;
}
return false;
}
static bool __cdecl is_block_an_aligned_allocation(void const* const block) throw()
{
unsigned char const* const possible_alignment_gap{reinterpret_cast<unsigned char const*>(
reinterpret_cast<uintptr_t>(block) & (~sizeof(uintptr_t) - 1)) - align_gap_size};
return check_bytes(possible_alignment_gap, align_land_fill, align_gap_size);
}
// The debug heap can be configured to validate the consistency of the heap at
// regular intervals. If this behavior is configured, this function controls
// that validation.
static bool heap_validation_pending{false};
static void __cdecl validate_heap_if_required_nolock() throw()
{
if (__acrt_check_frequency == 0)
{
return;
}
if (__acrt_check_counter != __acrt_check_frequency - 1)
{
++__acrt_check_counter;
return;
}
if (heap_validation_pending)
{
return;
}
heap_validation_pending = true;
__try
{
_ASSERTE(_CrtCheckMemory());
}
__finally
{
heap_validation_pending = false;
}
__acrt_check_counter = 0;
}
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Internal Debug Heap APIs
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Attempts to allocate a block of size bytes from the debug heap. Returns null
// on failure.
static void* __cdecl heap_alloc_dbg_internal(
size_t const size,
int const block_use,
char const* const file_name,
int const line_number
) throw()
{
void* block{nullptr};
__acrt_lock(__acrt_heap_lock);
__try
{
validate_heap_if_required_nolock();
long const request_number{__acrt_current_request_number};
// Handle break-on-request and forced failure:
if (_crtBreakAlloc != -1 && request_number == _crtBreakAlloc)
{
_CrtDbgBreak();
}
if (_pfnAllocHook && !_pfnAllocHook(
_HOOK_ALLOC,
nullptr,
size,
block_use,
request_number,
reinterpret_cast<unsigned char const*>(file_name),
line_number))
{
if (file_name)
_RPTN(_CRT_WARN, "Client hook allocation failure at file %hs line %d.\n", file_name, line_number);
else
_RPT0(_CRT_WARN, "Client hook allocation failure.\n");
__leave;
}
#pragma warning(suppress:__WARNING_UNUSED_ASSIGNMENT) // 28931
bool const ignore_block{_BLOCK_TYPE(block_use) != _CRT_BLOCK && !(_crtDbgFlag & _CRTDBG_ALLOC_MEM_DF)};
// Diagnostic memory allocation from this point on...
if (size > static_cast<size_t>(_HEAP_MAXREQ - no_mans_land_size - sizeof(_CrtMemBlockHeader)))
{
errno_t* const global_errno{_errno()};
if (global_errno)
*global_errno = ENOMEM;
__leave;
}
if (!is_block_type_valid(block_use))
{
_RPT0(_CRT_ERROR, "Error: memory allocation: bad memory block type.\n");
}
size_t const block_size{sizeof(_CrtMemBlockHeader) + size + no_mans_land_size};
_CrtMemBlockHeader* const header{static_cast<_CrtMemBlockHeader*>(HeapAlloc(__acrt_heap, 0, block_size))};
if (!header)
{
errno_t* const global_errno{_errno()};
if (global_errno)
*global_errno = ENOMEM;
__leave;
}
// Commit the allocation by linking the block into the global list:
++__acrt_current_request_number;
if (ignore_block)
{
header->_block_header_next = nullptr;
header->_block_header_prev = nullptr;
header->_file_name = nullptr;
header->_line_number = line_number_for_ignore_blocks;
header->_data_size = size;
header->_block_use = _IGNORE_BLOCK;
header->_request_number = request_number_for_ignore_blocks;
}
else
{
// Keep track of total amount of memory allocated:
if (SIZE_MAX - __acrt_total_allocations > size)
{
__acrt_total_allocations += size;
}
else
{
__acrt_total_allocations = SIZE_MAX;
}
__acrt_current_allocations += size;
if (__acrt_current_allocations > __acrt_max_allocations)
__acrt_max_allocations = __acrt_current_allocations;
if (__acrt_first_block)
{
__acrt_first_block->_block_header_prev = header;
}
else
{
__acrt_last_block = header;
}
header->_block_header_next = __acrt_first_block;
header->_block_header_prev = nullptr;
header->_file_name = file_name;
header->_line_number = line_number;
header->_data_size = size;
header->_block_use = block_use;
header->_request_number = request_number;
__acrt_first_block = header;
}
// Fill the gap before and after the data block:
memset(header->_gap, no_mans_land_fill, no_mans_land_size);
memset(block_from_header(header) + size, no_mans_land_fill, no_mans_land_size);
// Fill the data block with a silly (but non-zero) value:
memset(block_from_header(header), clean_land_fill, size);
block = block_from_header(header);
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
return block;
}
// Allocates a block of size bytes from the debug heap, using the new handler if
// it is configured for use with malloc.
static void* __cdecl heap_alloc_dbg(
size_t const size,
int const block_use,
char const* const file_name,
int const line_number
) throw()
{
bool const should_call_new_handler{_query_new_mode() != 0};
for (;;)
{
void* const block{heap_alloc_dbg_internal(size, block_use, file_name, line_number)};
if (block)
return block;
if (!should_call_new_handler || !_callnewh(size))
{
errno_t* const global_errno{_errno()};
if (global_errno)
*global_errno = ENOMEM;
return nullptr;
}
// The new handler was successful -- try to allocate again
}
}
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Public Debug Heap Allocation APIs
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// These are the allocation functions that allocate, manipulate, and free blocks
// from the debug heap. They are equivalent to the main allocation functions,
// which deal with blocks from the process heap. Most of the debug allocation
// functions accept a block use, file name, and/or line number which are used to
// track where allocations originated.
//
// Documentation comments for these functions describe only the material
// differences between them and the corresponding main allocation functions.
// This function must be marked noinline, otherwise malloc and
// _malloc_dbg will have identical COMDATs, and the linker will fold
// them when calling one from the CRT. This is necessary because malloc
// needs to support users patching in custom implementations.
extern "C" __declspec(noinline) void* __cdecl _malloc_dbg(
size_t const size,
int const block_use,
char const* const file_name,
int const line_number
)
{
return heap_alloc_dbg(size, block_use, file_name, line_number);
}
// This function must be marked noinline, otherwise calloc and
// _calloc_dbg will have identical COMDATs, and the linker will fold
// them when calling one from the CRT. This is necessary because calloc
// needs to support users patching in custom implementations.
extern "C" __declspec(noinline) void* __cdecl _calloc_dbg(
size_t const count,
size_t const element_size,
int const block_use,
char const* const file_name,
int const line_number
)
{
_VALIDATE_RETURN_NOEXC(count == 0 || (_HEAP_MAXREQ / count) >= element_size, ENOMEM, nullptr);
size_t const allocation_size{element_size * count};
// Note that we zero exactly allocation_size bytes. The _calloc_base
// function for the main heap may zero more bytes if a larger block is
// allocated.
void* const block{heap_alloc_dbg(allocation_size, block_use, file_name, line_number)};
if (block)
memset(block, 0, allocation_size);
return block;
}
// Common debug realloc implementation shared by _realloc_dbg, _recalloc_dbg,
// and _expand_dbg. If reallocation_is_allowed is true, the expand behavior
// is used; otherwise the realloc behavior is used.
static void * __cdecl realloc_dbg_nolock(
void* const block,
size_t* const new_size,
int const block_use,
char const* const file_name,
int const line_number,
bool const reallocation_is_allowed
) throw()
{
// realloc(nullptr, size) is equivalent to malloc(size):
if (!block)
{
return _malloc_dbg(*new_size, block_use, file_name, line_number);
}
// realloc(block, 0) is equivalent to free(block):
if (reallocation_is_allowed && *new_size == 0)
{
_free_dbg(block, block_use);
return nullptr;
}
validate_heap_if_required_nolock();
// Handle break-on-request and forced failure:
long const request_number{__acrt_current_request_number};
if (_crtBreakAlloc != -1 && request_number == _crtBreakAlloc)
{
_CrtDbgBreak();
}
if (_pfnAllocHook && !_pfnAllocHook(
_HOOK_REALLOC,
block,
*new_size,
block_use,
request_number,
reinterpret_cast<unsigned char const*>(file_name),
line_number))
{
if (file_name)
_RPTN(_CRT_WARN, "Client hook re-allocation failure at file %hs line %d.\n", file_name, line_number);
else
_RPT0(_CRT_WARN, "Client hook re-allocation failure.\n");
return nullptr;
}
// Ensure the block type matches what is expected and isn't an aligned allocation:
if (block_use != _NORMAL_BLOCK && _BLOCK_TYPE(block_use) != _CLIENT_BLOCK && _BLOCK_TYPE(block_use) != _CRT_BLOCK)
{
if (file_name)
{
_RPTN(_CRT_ERROR,
"Error: memory allocation: bad memory block type.\n" _ALLOCATION_FILE_LINENUM,
file_name, line_number);
}
else
{
_RPT0(_CRT_ERROR, "Error: memory allocation: bad memory block type.\n");
}
}
else if (is_block_an_aligned_allocation(block))
{
// We don't know (yet) where (file, linenum) block was allocated
_RPTN(_CRT_ERROR, "The Block at 0x%p was allocated by aligned routines, use _aligned_realloc()", block);
errno = EINVAL;
return nullptr;
}
// If this assertion fails, a bad pointer has been passed in. It may be
// totally bogus, or it may have been allocated from another heap. The
// pointer must have been allocated from the debug heap.
_ASSERTE(_CrtIsValidHeapPointer(block));
_CrtMemBlockHeader* const old_head{header_from_block(block)};
bool const is_ignore_block{old_head->_block_use == _IGNORE_BLOCK};
if (is_ignore_block)
{
_ASSERTE(old_head->_line_number == line_number_for_ignore_blocks && old_head->_request_number == request_number_for_ignore_blocks);
}
else if (__acrt_total_allocations < old_head->_data_size)
{
_RPTN(_CRT_ERROR, "Error: possible heap corruption at or near 0x%p", block);
errno = EINVAL;
return nullptr;
}
// Ensure the new requested size is not too large:
if (*new_size > static_cast<size_t>(_HEAP_MAXREQ - no_mans_land_size - sizeof(_CrtMemBlockHeader)))
{
errno = ENOMEM;
return nullptr;
}
// Note that all header values will remain valid and the minimum of the old
// size and the new size of data will remain valid.
size_t const new_internal_size{sizeof(_CrtMemBlockHeader) + *new_size + no_mans_land_size};
_CrtMemBlockHeader* new_head{nullptr};
if (reallocation_is_allowed)
{
new_head = static_cast<_CrtMemBlockHeader*>(_realloc_base(old_head, new_internal_size));
if (!new_head)
return nullptr;
}
else
{
new_head = static_cast<_CrtMemBlockHeader*>(_expand_base(old_head, new_internal_size));
if (!new_head)
return nullptr;
// On Win64, the heap does not try to resize the block if it is shrinking
// because of the use of the low-fragmentation heap. It just returns the
// original block. We make sure that our own header tracks that properly:
#ifdef _WIN64
*new_size = static_cast<size_t>(HeapSize(__acrt_heap, 0, new_head)
- sizeof(_CrtMemBlockHeader)
- no_mans_land_size);
#endif
}
_Analysis_assume_(new_head->_data_size == old_head->_data_size);
// Account for the current allocation and track the total amount of memory
// that is currently allocated:
++__acrt_current_request_number;
if (!is_ignore_block)
{
if (__acrt_total_allocations < SIZE_MAX)
{
__acrt_total_allocations -= new_head->_data_size;
__acrt_total_allocations += SIZE_MAX - __acrt_total_allocations > *new_size
? *new_size
: SIZE_MAX;
}
__acrt_current_allocations -= new_head->_data_size;
__acrt_current_allocations += *new_size;
if (__acrt_current_allocations > __acrt_max_allocations)
__acrt_max_allocations = __acrt_current_allocations;
}
unsigned char* const new_block{block_from_header(new_head)};
// If the block grew, fill the "extension" with the land fill value:
if (*new_size > new_head->_data_size)
{
memset(new_block + new_head->_data_size, clean_land_fill, *new_size - new_head->_data_size);
}
// Fill in the gap after the client block:
memset(new_block + *new_size, no_mans_land_fill, no_mans_land_size);
if (!is_ignore_block)
{
new_head->_file_name = file_name;
new_head->_line_number = line_number;
new_head->_request_number = request_number;
}
new_head->_data_size = *new_size;
_ASSERTE(reallocation_is_allowed || (!reallocation_is_allowed && new_head == old_head));
// If the block did not move or is ignored, we are done:
if (new_head == old_head || is_ignore_block)
return new_block;
// Swap out the old block from the linked list and link in the new block:
if (new_head->_block_header_next)
{
new_head->_block_header_next->_block_header_prev = new_head->_block_header_prev;
}
else
{
_ASSERTE(__acrt_last_block == old_head);
__acrt_last_block = new_head->_block_header_prev;
}
if (new_head->_block_header_prev)
{
new_head->_block_header_prev->_block_header_next = new_head->_block_header_next;
}
else
{
_ASSERTE(__acrt_first_block == old_head);
__acrt_first_block = new_head->_block_header_next;
}
if (__acrt_first_block)
{
__acrt_first_block->_block_header_prev = new_head;
}
else
{
__acrt_last_block = new_head;
}
new_head->_block_header_next = __acrt_first_block;
new_head->_block_header_prev = nullptr;
__acrt_first_block = new_head;
return new_block;
}
// This function must be marked noinline, otherwise realloc and
// _realloc_dbg will have identical COMDATs, and the linker will fold
// them when calling one from the CRT. This is necessary because realloc
// needs to support users patching in custom implementations.
extern "C" __declspec(noinline) void* __cdecl _realloc_dbg(
void* const block,
size_t const requested_size,
int const block_use,
char const* const file_name,
int const line_number
)
{
void* new_block{nullptr};
__acrt_lock(__acrt_heap_lock);
__try
{
size_t new_size{requested_size};
new_block = realloc_dbg_nolock(block, &new_size, block_use, file_name, line_number, true);
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
return new_block;
}
// This function must be marked noinline, otherwise recalloc and
// _recalloc_dbg will have identical COMDATs, and the linker will fold
// them when calling one from the CRT. This is necessary because recalloc
// needs to support users patching in custom implementations.
extern "C" __declspec(noinline) void* __cdecl _recalloc_dbg(
void* const block,
size_t const count,
size_t const element_size,
int const block_use,
char const* const file_name,
int const line_number
)
{
_VALIDATE_RETURN_NOEXC(count == 0 || (_HEAP_MAXREQ / count) >= element_size, ENOMEM, nullptr);
size_t const old_allocation_size{block ? _msize_dbg(block, block_use) : 0};
size_t const new_allocation_size{element_size * count };
void* const new_block{_realloc_dbg(block, new_allocation_size, block_use, file_name, line_number)};
if (!new_block)
return nullptr;
// Zero the "expansion," if the block was expanded:
if (old_allocation_size < new_allocation_size)
{
memset(
static_cast<unsigned char*>(new_block) + old_allocation_size,
0,
new_allocation_size - old_allocation_size);
}
return new_block;
}
// This function must be marked noinline, otherwise _expand and
// _expand_dbg will have identical COMDATs, and the linker will fold
// them when calling one from the CRT. This is necessary because _expand
// needs to support users patching in custom implementations.
extern "C" __declspec(noinline) void* __cdecl _expand_dbg(
void* const block,
size_t const requested_size,
int const block_use,
char const* const file_name,
int const line_number
)
{
_VALIDATE_RETURN(block != nullptr, EINVAL, nullptr);
if (requested_size > static_cast<size_t>(_HEAP_MAXREQ - no_mans_land_size - sizeof(_CrtMemBlockHeader)))
{
errno = ENOMEM;
return nullptr;
}
void* new_block{nullptr};
__acrt_lock(__acrt_heap_lock);
__try
{
size_t new_size{requested_size};
new_block = realloc_dbg_nolock(block, &new_size, block_use, file_name, line_number, false);
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
return new_block;
}
static void __cdecl free_dbg_nolock(
void* const block,
int const block_use
) throw()
{
validate_heap_if_required_nolock();
if (block == nullptr)
return;
#if _UCRT_HEAP_MISMATCH_DETECTION && (defined _M_IX86 || defined _M_AMD64)
if (!_CrtIsValidHeapPointer(block))
{
HANDLE const msvcrt_heap_handle = __acrt_get_msvcrt_heap_handle();
if (msvcrt_heap_handle)
{
if (HeapValidate(msvcrt_heap_handle, 0, block))
{
_RPT1(_CRT_WARN, "CRTHEAP: ucrt: Attempt to free a pointer (0x%p) that belongs to MSVCRT's private heap, not the process heap.\n", block);
#if _UCRT_HEAP_MISMATCH_BREAK
_CrtDbgBreak();
#endif // _UCRT_HEAP_MISMATCH_BREAK
#if _UCRT_HEAP_MISMATCH_RECOVERY
if (HeapFree(msvcrt_heap_handle, 0, block))
{
_RPT1(_CRT_WARN, "CRTHEAP: ucrt: Successfully free'd 0x%p\n", block);
return;
}
else
{
_RPT1(_CRT_ERROR, "CRTHEAP: ucrt: Unable to free 0x%p\n", block);
_CrtDbgBreak(); // Force break.
}
#endif // _UCRT_HEAP_MISMATCH_RECOVERY
}
}
}
#endif // _UCRT_HEAP_MISMATCH_DETECTION && (defined _M_IX86 || defined _M_AMD64)
// Check to ensure that the block was not allocated by _aligned routines
if (block_use == _NORMAL_BLOCK && is_block_an_aligned_allocation(block))
{
// We don't know (yet) where (file, linenum) block was allocated
_RPTN(_CRT_ERROR, "The Block at 0x%p was allocated by aligned routines, use _aligned_free()", block);
errno = EINVAL;
return;
}
// Forced failure handling
if (_pfnAllocHook && !_pfnAllocHook(_HOOK_FREE, block, 0, block_use, 0, nullptr, 0))
{
_RPT0(_CRT_WARN, "Client hook free failure.\n");
return;
}
// If this assertion fails, a bad pointer has been passed in. It may be
// totally bogus, or it may have been allocated from another heap. The
// pointer must have been allocated from the CRT heap.
_ASSERTE(_CrtIsValidHeapPointer(block));
// Get a pointer to memory block header:
_CrtMemBlockHeader* const header = header_from_block(block);
_ASSERTE(is_block_type_valid(header->_block_use));
// If we didn't already check entire heap, at least check this object by
// verifying that its no-man's land areas have not been trashed:
if (!(_crtDbgFlag & _CRTDBG_CHECK_ALWAYS_DF))
{
if (!check_bytes(header->_gap, no_mans_land_fill, no_mans_land_size))
{
if (header->_file_name)
{
_RPTN(_CRT_ERROR, "HEAP CORRUPTION DETECTED: before %hs block (#%d) at 0x%p.\n"
"CRT detected that the application wrote to memory before start of heap buffer.\n"
_ALLOCATION_FILE_LINENUM,
block_use_names[_BLOCK_TYPE(header->_block_use)],
header->_request_number,
block_from_header(header),
header->_file_name,
header->_line_number);
}
else
{
_RPTN(_CRT_ERROR, "HEAP CORRUPTION DETECTED: before %hs block (#%d) at 0x%p.\n"
"CRT detected that the application wrote to memory before start of heap buffer.\n",
block_use_names[_BLOCK_TYPE(header->_block_use)],
header->_request_number,
block_from_header(header));
}
}
if (!check_bytes(block_from_header(header) + header->_data_size, no_mans_land_fill, no_mans_land_size))
{
if (header->_file_name)
{
_RPTN(_CRT_ERROR, "HEAP CORRUPTION DETECTED: after %hs block (#%d) at 0x%p.\n"
"CRT detected that the application wrote to memory after end of heap buffer.\n"
_ALLOCATION_FILE_LINENUM,
block_use_names[_BLOCK_TYPE(header->_block_use)],
header->_request_number,
block_from_header(header),
header->_file_name,
header->_line_number);
}
else
{
_RPTN(_CRT_ERROR, "HEAP CORRUPTION DETECTED: after %hs block (#%d) at 0x%p.\n"
"CRT detected that the application wrote to memory after end of heap buffer.\n",
block_use_names[_BLOCK_TYPE(header->_block_use)],
header->_request_number,
block_from_header(header));
}
}
}
// If this block was ignored when it was allocated, we can just free it:
if (header->_block_use == _IGNORE_BLOCK)
{
_ASSERTE(header->_line_number == line_number_for_ignore_blocks && header->_request_number == request_number_for_ignore_blocks);
memset(header, dead_land_fill, sizeof(_CrtMemBlockHeader) + header->_data_size + no_mans_land_size);
_free_base(header);
return;
}
// Ensure that we were called with the right block use. CRT blocks can be
// freed as NORMAL blocks.
_ASSERTE(header->_block_use == block_use || header->_block_use == _CRT_BLOCK && block_use == _NORMAL_BLOCK);
__acrt_current_allocations -= header->_data_size;
// Optionally reclaim memory:
if ((_crtDbgFlag & _CRTDBG_DELAY_FREE_MEM_DF) == 0)
{
// Unlink this allocation from the global linked list:
if (header->_block_header_next)
{
header->_block_header_next->_block_header_prev = header->_block_header_prev;
}
else
{
_ASSERTE(__acrt_last_block == header);
__acrt_last_block = header->_block_header_prev;
}
if (header->_block_header_prev)
{
header->_block_header_prev->_block_header_next = header->_block_header_next;
}
else
{
_ASSERTE(__acrt_first_block == header);
__acrt_first_block = header->_block_header_next;
}
memset(header, dead_land_fill, sizeof(_CrtMemBlockHeader) + header->_data_size + no_mans_land_size);
_free_base(header);
}
else
{
header->_block_use = _FREE_BLOCK;
// Keep memory around as dead space:
memset(block_from_header(header), dead_land_fill, header->_data_size);
}
}
// This function must be marked noinline, otherwise free and
// _free_dbg will have identical COMDATs, and the linker will fold
// them when calling one from the CRT. This is necessary because free
// needs to support users patching in custom implementations.
extern "C" __declspec(noinline) void __cdecl _free_dbg(void* const block, int const block_use)
{
__acrt_lock(__acrt_heap_lock);
__try
{
// If a block use was provided, use it; if the block use was not known,
// use the block use stored in the header. (For example, the block use
// is not known when this function is called by operator delete because
// the heap lock must be acquired to access the block header.)
int const actual_use{block_use == _UNKNOWN_BLOCK && block != nullptr
? header_from_block(block)->_block_use
: block_use};
free_dbg_nolock(block, actual_use);
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
}
// This function must be marked noinline, otherwise _msize and
// _msize_dbg will have identical COMDATs, and the linker will fold
// them when calling one from the CRT. This is necessary because _msize
// needs to support users patching in custom implementations.
extern "C" __declspec(noinline) size_t __cdecl _msize_dbg(void* const block, int const block_use)
{
UNREFERENCED_PARAMETER(block_use);
_VALIDATE_RETURN(block != nullptr, EINVAL, static_cast<size_t>(-1));
size_t size{0};
__acrt_lock(__acrt_heap_lock);
__try
{
validate_heap_if_required_nolock();
// If this assert fails, a bad pointer has been passed in. It may be
// totally bogus, or it may have been allocated from another heap. The
// pointer must have been allocated from the CRT heap.
_ASSERTE(_CrtIsValidHeapPointer(block));
_CrtMemBlockHeader* const header{header_from_block(block)};
_ASSERTE(is_block_type_valid(header->_block_use));
size = header->_data_size;
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
return size;
}
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Public Debug Heap Control and Status APIs
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Configures the CRT to break on allocation operation number new_break_alloc.
// Returns the previous break allocation value.
extern "C" long __cdecl _CrtSetBreakAlloc(long const new_break_alloc)
{
long const old_break_alloc{_crtBreakAlloc};
_crtBreakAlloc = new_break_alloc;
return old_break_alloc;
}
// Changes the block use for a block allocated on the debug heap.
extern "C" void __cdecl _CrtSetDbgBlockType(
void* const block,
int const block_use
)
{
__acrt_lock(__acrt_heap_lock);
__try
{
if (!_CrtIsValidHeapPointer(block))
__leave;
_CrtMemBlockHeader* const header{header_from_block(block)};
_ASSERTE(is_block_type_valid(header->_block_use));
header->_block_use = block_use;
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
}
// These get and set the allocation hook function, which is called for debug
// heap allocation operations.
extern "C" _CRT_ALLOC_HOOK __cdecl _CrtGetAllocHook()
{
return _pfnAllocHook;
}
extern "C" _CRT_ALLOC_HOOK __cdecl _CrtSetAllocHook(_CRT_ALLOC_HOOK const new_hook)
{
_CRT_ALLOC_HOOK const old_hook{_pfnAllocHook};
_pfnAllocHook = new_hook;
return old_hook;
}
// Checks the integrity of the debug heap. Returns TRUE if the debug heap (and
// the underlying Windows heap) appears valid; returns FALSE and asserts if the
// heap appears corrupted or otherwise invalid.
static bool __cdecl check_block(_CrtMemBlockHeader* const header) throw()
{
bool this_block_okay{true};
char const* block_use{nullptr};
if (is_block_type_valid(header->_block_use))
{
block_use = block_use_names[_BLOCK_TYPE(header->_block_use)];
}
else
{
block_use = "DAMAGED";
}
// Check the no-man's-land gaps:
if (!check_bytes(header->_gap, no_mans_land_fill, no_mans_land_size))
{
if (header->_file_name)
{
_RPTN(_CRT_WARN, "HEAP CORRUPTION DETECTED: before %hs block (#%d) at 0x%p.\n"
"CRT detected that the application wrote to memory before start of heap buffer.\n"
_ALLOCATION_FILE_LINENUM,
block_use,
header->_request_number,
block_from_header(header),
header->_file_name,
header->_line_number);
}
else
{
_RPTN(_CRT_WARN, "HEAP CORRUPTION DETECTED: before %hs block (#%d) at 0x%p.\n"
"CRT detected that the application wrote to memory before start of heap buffer.\n",
block_use, header->_request_number, block_from_header(header));
}
this_block_okay = false;
}
if (!check_bytes(block_from_header(header) + header->_data_size, no_mans_land_fill, no_mans_land_size))
{
if (header->_file_name)
{
_RPTN(_CRT_WARN, "HEAP CORRUPTION DETECTED: after %hs block (#%d) at 0x%p.\n"
"CRT detected that the application wrote to memory after end of heap buffer.\n"
_ALLOCATION_FILE_LINENUM,
block_use,
header->_request_number,
block_from_header(header),
header->_file_name,
header->_line_number);
}
else
{
_RPTN(_CRT_WARN, "HEAP CORRUPTION DETECTED: after %hs block (#%d) at 0x%p.\n"
"CRT detected that the application wrote to memory after end of heap buffer.\n",
block_use, header->_request_number, block_from_header(header));
}
this_block_okay = false;
}
// Free blocks should remain undisturbed:
if (header->_block_use == _FREE_BLOCK && !check_bytes(block_from_header(header), dead_land_fill, header->_data_size))
{
if (header->_file_name)
{
_RPTN(_CRT_WARN, "HEAP CORRUPTION DETECTED: on top of Free block at 0x%p.\n"
"CRT detected that the application wrote to a heap buffer that was freed.\n"
_ALLOCATION_FILE_LINENUM,
block_from_header(header),
header->_file_name,
header->_line_number);
}
else
{
_RPTN(_CRT_WARN, "HEAP CORRUPTION DETECTED: on top of Free block at 0x%p.\n"
"CRT detected that the application wrote to a heap buffer that was freed.\n",
block_from_header(header));
}
this_block_okay = false;
}
if (!this_block_okay)
{
// Report statistics about the broken object:
if (header->_file_name)
{
_RPTN(_CRT_WARN,
"%hs located at 0x%p is %Iu bytes long.\n"
_ALLOCATION_FILE_LINENUM,
block_use,
block_from_header(header),
header->_data_size,
header->_file_name,
header->_line_number);
}
else
{
_RPTN(_CRT_WARN, "%hs located at 0x%p is %Iu bytes long.\n",
block_use, block_from_header(header), header->_data_size);
}
}
return this_block_okay;
}
extern "C" int __cdecl _CrtCheckMemory()
{
if ((_crtDbgFlag & _CRTDBG_ALLOC_MEM_DF) == 0)
{
return TRUE;
}
bool all_okay{true};
__acrt_lock(__acrt_heap_lock);
__try
{
// First walk our allocated heap blocks and verify consistency of our
// internal data structures. We do this first because we can give
// better diagnostics to client code than the underlying Windows heap
// can (we have source file names and line numbers and other metadata).
// We use Floyd's cycle finding algorithm to detect cycles in the block
// list.
_CrtMemBlockHeader* trail_it{__acrt_first_block};
_CrtMemBlockHeader* lead_it {__acrt_first_block == nullptr ? nullptr : __acrt_first_block->_block_header_next};
while (trail_it != nullptr)
{
all_okay &= check_block(trail_it);
if (trail_it == lead_it)
{
_RPTN(_CRT_WARN,
"Cycle in block list detected while processing block located at 0x%p.\n",
trail_it);
all_okay = false;
break;
}
trail_it = trail_it->_block_header_next;
// Advance the lead iterator twice as fast as the trail iterator:
if (lead_it != nullptr)
{
lead_it = lead_it->_block_header_next == nullptr
? nullptr
: lead_it->_block_header_next->_block_header_next;
}
}
// Then check the underlying Windows heap:
if (!HeapValidate(__acrt_heap, 0, nullptr))
{
_RPT0(_CRT_WARN, "Heap validation failed.\n");
all_okay = false;
__leave;
}
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
return all_okay ? TRUE : FALSE;
}
// Configures the debug heap behavior flags. Note that only the flags listed at
// the top of the function definition are valid; any other flags will cause the
// invalid parameter handler to be invoked. Returns the previous flag state.
extern "C" int __cdecl _CrtSetDbgFlag(int const new_bits)
{
int const valid_flags{
_CRTDBG_ALLOC_MEM_DF |
_CRTDBG_DELAY_FREE_MEM_DF |
_CRTDBG_CHECK_ALWAYS_DF |
_CRTDBG_CHECK_CRT_DF |
_CRTDBG_LEAK_CHECK_DF};
bool const new_bits_have_only_valid_flags = (new_bits & 0xffff & ~valid_flags) == 0;
_VALIDATE_RETURN(new_bits == _CRTDBG_REPORT_FLAG || new_bits_have_only_valid_flags, EINVAL, _crtDbgFlag);
int old_bits{0};
__acrt_lock(__acrt_heap_lock);
__try
{
old_bits = _crtDbgFlag;
if (new_bits == _CRTDBG_REPORT_FLAG)
__leave;
if (new_bits & _CRTDBG_CHECK_ALWAYS_DF)
__acrt_check_frequency = 1;
else
__acrt_check_frequency = (new_bits >> 16) & 0x0ffff;
__acrt_check_counter = 0;
_crtDbgFlag = new_bits;
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
return old_bits;
}
// Calls a caller-provided function for each client object in the heap.
extern "C" void __cdecl _CrtDoForAllClientObjects(
_CrtDoForAllClientObjectsCallback const callback,
void* const context
)
{
_VALIDATE_RETURN_VOID(callback != nullptr, EINVAL);
if ((_crtDbgFlag & _CRTDBG_ALLOC_MEM_DF) == 0)
return;
__acrt_lock(__acrt_heap_lock);
__try
{
for (_CrtMemBlockHeader* header{__acrt_first_block}; header != nullptr; header = header->_block_header_next)
{
if (_BLOCK_TYPE(header->_block_use) == _CLIENT_BLOCK)
callback(block_from_header(header), context);
}
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
}
// This function is provided for backwards compatibility only. It just tests
// whether the provided pointer is null.
extern "C" int __cdecl _CrtIsValidPointer(
void const* const p,
unsigned int const size_in_bytes,
int const read_write
)
{
UNREFERENCED_PARAMETER(size_in_bytes);
UNREFERENCED_PARAMETER(read_write);
return p != nullptr;
}
// This function is provided for backwards compatibility only. It returns TRUE
// if the given block points to an allocation from the OS heap that underlies
// this CRT debug heap. Back when the CRT used its own OS heap (prior to Dev10),
// this function would thus also tell you whether the block was allocated by this
// debug heap. Now, it just tells you whether the block was allocated by some
// debug heap.
extern "C" int __cdecl _CrtIsValidHeapPointer(void const* const block)
{
if (!block)
return FALSE;
return HeapValidate(__acrt_heap, 0, header_from_block(block));
}
// Verifies that the provided memory block is a block allocated by this debug
// heap. Returns TRUE if it is; FALSE otherwise. If the request_number,
// file_name, and line_number arguments are non-null, and if the block was
// allocated by this debug heap, this function fills in those argments with the
// actual values for the block.
extern "C" int __cdecl _CrtIsMemoryBlock(
void const* const block,
unsigned const size,
long* const request_number,
char** const file_name,
int* const line_number
)
{
if (request_number)
*request_number = 0;
if (file_name)
*file_name = nullptr;
if (line_number)
*line_number = 0;
if (!block)
return FALSE;
int result{FALSE};
__acrt_lock(__acrt_heap_lock);
__try
{
_CrtMemBlockHeader* const header{header_from_block(block)};
if (!is_block_type_valid(header->_block_use))
__leave;
if (!_CrtIsValidPointer(block, size, TRUE))
__leave;
if (header->_data_size != size)
__leave;
if (header->_request_number > __acrt_current_request_number)
__leave;
// The block is valid
if (request_number)
*request_number = header->_request_number;
if (file_name)
*file_name = const_cast<char*>(header->_file_name);
if (line_number)
*line_number = header->_line_number;
result = TRUE;
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
return result;
}
// Tests whether the block was allocated by this heap and, if it was, returns
// its block use. Returns -1 if this block was not allocated by this heap.
extern "C" int _CrtReportBlockType(void const* const block)
{
if (!_CrtIsValidHeapPointer(block))
return -1;
return header_from_block(block)->_block_use;
}
// These get and set the active dump client, which is used when dumping the state
// of the debug heap.
extern "C" _CRT_DUMP_CLIENT __cdecl _CrtGetDumpClient()
{
return _pfnDumpClient;
}
extern "C" _CRT_DUMP_CLIENT __cdecl _CrtSetDumpClient(_CRT_DUMP_CLIENT const new_client)
{
_CRT_DUMP_CLIENT const old_client{_pfnDumpClient};
_pfnDumpClient = new_client;
return old_client;
}
// Creates a checkpoint for the current state of the debug heap. Fills in the
// object pointed to by state; state must be non-null.
extern "C" void __cdecl _CrtMemCheckpoint(_CrtMemState* const state)
{
_VALIDATE_RETURN_VOID(state != nullptr, EINVAL);
__acrt_lock(__acrt_heap_lock);
__try
{
state->pBlockHeader = __acrt_first_block;
for (unsigned use{0}; use < _MAX_BLOCKS; ++use)
{
state->lCounts[use] = 0;
state->lSizes [use] = 0;
}
for (_CrtMemBlockHeader* header{__acrt_first_block}; header != nullptr; header = header->_block_header_next)
{
if (_BLOCK_TYPE(header->_block_use) >= 0 && _BLOCK_TYPE(header->_block_use) < _MAX_BLOCKS)
{
++state->lCounts[_BLOCK_TYPE(header->_block_use)];
state->lSizes[_BLOCK_TYPE(header->_block_use)] += header->_data_size;
}
else if (header->_file_name)
{
_RPTN(_CRT_WARN, "Bad memory block found at 0x%p.\n" _ALLOCATION_FILE_LINENUM,
header,
header->_file_name,
header->_line_number);
}
else
{
_RPTN(_CRT_WARN, "Bad memory block found at 0x%p.\n", header);
}
}
state->lHighWaterCount = __acrt_max_allocations;
state->lTotalCount = __acrt_total_allocations;
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
}
// Computes the difference between two memory states. The difference between
// the old_state and new_state is stored in the object pointed to by state.
// Returns TRUE if there is a difference; FALSE otherwise. All three state
// pointers must be valid and non-null.
extern "C" int __cdecl _CrtMemDifference(
_CrtMemState* const state,
_CrtMemState const* const old_state,
_CrtMemState const* const new_state
)
{
_VALIDATE_RETURN(state != nullptr, EINVAL, FALSE);
_VALIDATE_RETURN(old_state != nullptr, EINVAL, FALSE);
_VALIDATE_RETURN(new_state != nullptr, EINVAL, FALSE);
bool significant_difference_found{false};
for (int use{0}; use < _MAX_BLOCKS; ++use)
{
state->lSizes[use] = new_state->lSizes[use] - old_state->lSizes[use];
state->lCounts[use] = new_state->lCounts[use] - old_state->lCounts[use];
if (state->lSizes[use] == 0 && state->lCounts[use] == 0)
continue;
if (use == _FREE_BLOCK)
continue;
if (use == _CRT_BLOCK && (_crtDbgFlag & _CRTDBG_CHECK_CRT_DF) == 0)
continue;
significant_difference_found = true;
}
state->lHighWaterCount = new_state->lHighWaterCount - old_state->lHighWaterCount;
state->lTotalCount = new_state->lTotalCount - old_state->lTotalCount;
state->pBlockHeader = nullptr;
return significant_difference_found ? 1 : 0;
}
// Prints metadata for a block of memory.
static void __cdecl print_block_data(
_locale_t const locale,
_CrtMemBlockHeader* const header
) throw()
{
_LocaleUpdate locale_update{locale};
static size_t const max_print = 16;
char print_buffer[max_print + 1];
char value_buffer[max_print * 3 + 1];
size_t i{0};
for (; i < min(header->_data_size, max_print); ++i)
{
unsigned char const c{block_from_header(header)[i]};
print_buffer[i] = _isprint_l(c, locale_update.GetLocaleT()) ? c : ' ';
_ERRCHECK_SPRINTF(sprintf_s(value_buffer + i * 3, _countof(value_buffer) - (i * 3), "%.2X ", c));
}
print_buffer[i] = '\0';
_RPTN(_CRT_WARN, " Data: <%s> %s\n", print_buffer, value_buffer);
}
// Prints metadata for all blocks allocated since the provided state was taken.
static void __cdecl dump_all_object_since_nolock(_CrtMemState const* const state) throw()
{
_LocaleUpdate locale_update{nullptr};
_locale_t locale{locale_update.GetLocaleT()};
_RPT0(_CRT_WARN, "Dumping objects ->\n");
_CrtMemBlockHeader* const stop_block{state ? state->pBlockHeader : nullptr};
for (_CrtMemBlockHeader* header{__acrt_first_block}; header != nullptr && header != stop_block; header = header->_block_header_next)
{
if (_BLOCK_TYPE(header->_block_use) == _IGNORE_BLOCK)
continue;
if (_BLOCK_TYPE(header->_block_use) == _FREE_BLOCK)
continue;
if (_BLOCK_TYPE(header->_block_use) == _CRT_BLOCK && (_crtDbgFlag & _CRTDBG_CHECK_CRT_DF) == 0)
continue;
if (header->_file_name != nullptr)
{
if (!_CrtIsValidPointer(header->_file_name, 1, FALSE) || is_bad_read_pointer(header->_file_name, 1))
{
_RPTN(_CRT_WARN, "#File Error#(%d) : ", header->_line_number);
}
else
{
_RPTN(_CRT_WARN, "%hs(%d) : ", header->_file_name, header->_line_number);
}
}
_RPTN(_CRT_WARN, "{%ld} ", header->_request_number);
if (_BLOCK_TYPE(header->_block_use) == _CLIENT_BLOCK)
{
_RPTN(_CRT_WARN, "client block at 0x%p, subtype %x, %Iu bytes long.\n",
block_from_header(header),
_BLOCK_SUBTYPE(header->_block_use),
header->_data_size);
if (_pfnDumpClient && !is_bad_read_pointer(block_from_header(header), header->_data_size))
{
_pfnDumpClient(block_from_header(header), header->_data_size);
}
else
{
print_block_data(locale, header);
}
}
else if (header->_block_use == _NORMAL_BLOCK)
{
_RPTN(_CRT_WARN, "normal block at 0x%p, %Iu bytes long.\n",
block_from_header(header),
header->_data_size);
print_block_data(locale, header);
}
else if (_BLOCK_TYPE(header->_block_use) == _CRT_BLOCK)
{
_RPTN(_CRT_WARN, "crt block at 0x%p, subtype %x, %Iu bytes long.\n",
block_from_header(header),
_BLOCK_SUBTYPE(header->_block_use),
header->_data_size);
print_block_data(locale, header);
}
}
}
// Prints metadata for all blocks allocated since the provided state was taken.
extern "C" void __cdecl _CrtMemDumpAllObjectsSince(_CrtMemState const* const state)
{
__acrt_lock(__acrt_heap_lock);
__try
{
dump_all_object_since_nolock(state);
}
__finally
{
__acrt_unlock(__acrt_heap_lock);
}
_RPT0(_CRT_WARN, "Object dump complete.\n");
}
// Dumps all currently active allocations in this heap. Returns TRUE if there
// are memory leaks; false otherwise. It is assumed that all client allocations
// remaining in the heap are memory leaks.
extern "C" int __cdecl _CrtDumpMemoryLeaks()
{
_CrtMemState state;
_CrtMemCheckpoint(&state);
if (state.lCounts[_CLIENT_BLOCK] != 0 ||
state.lCounts[_NORMAL_BLOCK] != 0 ||
(_crtDbgFlag & _CRTDBG_CHECK_CRT_DF && state.lCounts[_CRT_BLOCK] != 0))
{
_RPT0(_CRT_WARN, "Detected memory leaks!\n");
_CrtMemDumpAllObjectsSince(nullptr);
return TRUE;
}
return FALSE;
}
// Prints some brief information about the provided state of the heap.
extern "C" void __cdecl _CrtMemDumpStatistics(_CrtMemState const* const state)
{
_VALIDATE_RETURN_VOID(state != nullptr, EINVAL);
for (unsigned use{0}; use < _MAX_BLOCKS; ++use)
{
_RPTN(_CRT_WARN, "%Id bytes in %Id %hs Blocks.\n",
state->lSizes[use],
state->lCounts[use],
block_use_names[use]);
}
_RPTN(_CRT_WARN, "Largest number used: %Id bytes.\n", state->lHighWaterCount);
_RPTN(_CRT_WARN, "Total allocations: %Id bytes.\n", state->lTotalCount);
}
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Aligned Allocation
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// These functions are equivalent to the normal aligned allocation functions in
// alignment.cpp, but these functions (suffixed with _dbg instead of _base) utilize
// the debug heap and accept the file name and line number as arguments. Consult
// alignment.cpp for more information on the behavior of these functions.
struct _AlignMemBlockHdr
{
void* _head;
unsigned char _gap[align_gap_size];
};
#define IS_2_POW_N(X) ((X) != 0 && ((X) & ((X) - 1)) == 0)
extern "C" void* __cdecl _aligned_malloc_dbg(
size_t const size,
size_t const alignment,
char const* const file_name,
int const line_number
)
{
return _aligned_offset_malloc_dbg(size, alignment, 0, file_name, line_number);
}
extern "C" void* __cdecl _aligned_offset_malloc_dbg(
size_t const size,
size_t alignment,
size_t const offset,
char const* const file_name,
int const line_number
)
{
_VALIDATE_RETURN(IS_2_POW_N(alignment), EINVAL, nullptr);
_VALIDATE_RETURN(offset == 0 || offset < size, EINVAL, nullptr);
alignment = (alignment > sizeof(uintptr_t) ? alignment : sizeof(uintptr_t)) - 1;
uintptr_t const t_ptr = (0 -offset) & (sizeof(uintptr_t) -1);
size_t const nonuser_size = t_ptr + alignment + sizeof(_AlignMemBlockHdr); // Cannot overflow
size_t const block_size = size + nonuser_size;
_VALIDATE_RETURN_NOEXC(size <= block_size, ENOMEM, nullptr);
uintptr_t const ptr = reinterpret_cast<uintptr_t>(_malloc_dbg(block_size, _NORMAL_BLOCK, file_name, line_number));
if (ptr == reinterpret_cast<uintptr_t>(nullptr))
return nullptr;
uintptr_t const r_ptr = ((ptr +nonuser_size +offset)&~alignment)-offset;
_AlignMemBlockHdr* const header_from_block = reinterpret_cast<_AlignMemBlockHdr*>(r_ptr - t_ptr) - 1;
memset(header_from_block->_gap, align_land_fill, align_gap_size);
header_from_block->_head = reinterpret_cast<void*>(ptr);
return reinterpret_cast<void*>(r_ptr);
}
extern "C" void* __cdecl _aligned_realloc_dbg(
void* const block,
size_t const size,
size_t const alignment,
char const* const file_name,
int const line_number
)
{
return _aligned_offset_realloc_dbg(block, size, alignment, 0, file_name, line_number);
}
extern "C" void* __cdecl _aligned_recalloc_dbg(
void* const block,
size_t const count,
size_t const size,
size_t const alignment,
char const* const file_name,
int const line_number
)
{
return _aligned_offset_recalloc_dbg(block, count, size, alignment, 0, file_name, line_number);
}
extern "C" void* __cdecl _aligned_offset_realloc_dbg(
void * block,
size_t size,
size_t alignment,
size_t offset,
const char * file_name,
int line_number
)
{
uintptr_t ptr, r_ptr, t_ptr, mov_sz;
_AlignMemBlockHdr *header_from_block, *s_header_from_block;
size_t nonuser_size, block_size;
if (block == nullptr)
{
return _aligned_offset_malloc_dbg(size, alignment, offset, file_name, line_number);
}
if (size == 0)
{
_aligned_free_dbg(block);
return nullptr;
}
s_header_from_block = (_AlignMemBlockHdr *)((uintptr_t)block & ~(sizeof(uintptr_t) -1)) -1;
if (check_bytes((unsigned char *)block -no_mans_land_size, no_mans_land_fill, no_mans_land_size))
{
// We don't know where (file, linenum) block was allocated
_RPTN(_CRT_ERROR, "The block at 0x%p was not allocated by _aligned routines, use realloc()", block);
errno = EINVAL;
return nullptr;
}
if(!check_bytes(s_header_from_block->_gap, align_land_fill, align_gap_size))
{
// We don't know where (file, linenum) block was allocated
_RPTN(_CRT_ERROR, "Damage before 0x%p which was allocated by aligned routine\n", block);
}
/* validation section */
_VALIDATE_RETURN(IS_2_POW_N(alignment), EINVAL, nullptr);
_VALIDATE_RETURN(offset == 0 || offset < size, EINVAL, nullptr);
mov_sz = _msize_dbg(s_header_from_block->_head, _NORMAL_BLOCK) - ((uintptr_t)block - (uintptr_t)s_header_from_block->_head);
alignment = (alignment > sizeof(uintptr_t) ? alignment : sizeof(uintptr_t)) -1;
t_ptr = (0 -offset) & (sizeof(uintptr_t) - 1);
nonuser_size = t_ptr + alignment + sizeof(_AlignMemBlockHdr); // Cannot overflow
block_size = size + nonuser_size;
_VALIDATE_RETURN_NOEXC(size <= block_size, ENOMEM, nullptr);
if ((ptr = (uintptr_t)_malloc_dbg(block_size, _NORMAL_BLOCK, file_name, line_number)) == (uintptr_t)nullptr)
return nullptr;
r_ptr = ((ptr + nonuser_size + offset) & ~alignment) - offset;
header_from_block = (_AlignMemBlockHdr*)(r_ptr - t_ptr) - 1;
memset(header_from_block->_gap, align_land_fill, align_gap_size);
header_from_block->_head = reinterpret_cast<void*>(ptr);
memcpy(reinterpret_cast<void*>(r_ptr), block, mov_sz > size ? size : mov_sz);
_free_dbg(s_header_from_block->_head, _NORMAL_BLOCK);
return (void *) r_ptr;
}
extern "C" size_t __cdecl _aligned_msize_dbg(
void* const block,
size_t alignment,
size_t const offset
)
{
size_t header_size = 0; // Size of the header block
size_t footer_size = 0; // Size of the footer block
size_t total_size = 0; // total size of the allocated block
size_t user_size = 0; // size of the user block
uintptr_t gap = 0; // keep the alignment of the data block
// after the sizeof(void*) aligned pointer
// to the beginning of the allocated block
// HEADER SIZE + FOOTER SIZE = GAP + ALIGN + SIZE OF A POINTER
// HEADER SIZE + USER SIZE + FOOTER SIZE = TOTAL SIZE
_VALIDATE_RETURN (block != nullptr, EINVAL, static_cast<size_t>(-1));
_AlignMemBlockHdr* header_from_block = nullptr; // points to the beginning of the allocated block
header_from_block = (_AlignMemBlockHdr*)((uintptr_t)block & ~(sizeof(uintptr_t) - 1)) - 1;
total_size = _msize_dbg(header_from_block->_head, _NORMAL_BLOCK);
header_size = (uintptr_t)block - (uintptr_t)header_from_block->_head;
gap = (0 - offset) & (sizeof(uintptr_t) - 1);
// The alignment cannot be smaller than the sizeof(uintptr_t)
alignment = (alignment > sizeof(uintptr_t) ? alignment : sizeof(uintptr_t)) - 1;
footer_size = gap + alignment + sizeof(_AlignMemBlockHdr) - header_size;
user_size = total_size - header_size - footer_size;
return user_size;
}
extern "C" void* __cdecl _aligned_offset_recalloc_dbg(
void* const block,
size_t const count,
size_t const element_size,
size_t const alignment,
size_t const offset,
char const* const file_name,
int const line_number
)
{
_VALIDATE_RETURN_NOEXC(count == 0 || _HEAP_MAXREQ / count >= element_size, ENOMEM, nullptr);
size_t const old_allocation_size{block ? _aligned_msize_dbg(block, alignment, offset) : 0};
size_t const new_allocation_size{element_size * count};
void* const new_block{_aligned_offset_realloc_dbg(block, new_allocation_size, alignment, offset, file_name, line_number)};
if (!new_block)
return nullptr;
if (old_allocation_size < new_allocation_size)
memset(static_cast<unsigned char*>(new_block) + old_allocation_size, 0, new_allocation_size - old_allocation_size);
return new_block;
}
extern "C" void __cdecl _aligned_free_dbg(void* const block)
{
if (!block)
return;
_AlignMemBlockHdr* const header{reinterpret_cast<_AlignMemBlockHdr*>(
reinterpret_cast<uintptr_t>(block) & ~(sizeof(uintptr_t) - 1)
) -1};
if (check_bytes(static_cast<unsigned char*>(block) - no_mans_land_size, no_mans_land_fill, no_mans_land_size))
{
// We don't know where (file, linenum) block was allocated
_RPTN(_CRT_ERROR, "The block at 0x%p was not allocated by _aligned routines, use free()", block);
return;
}
if (!check_bytes(header->_gap, align_land_fill, align_gap_size))
{
// We don't know where (file, linenum) block was allocated
_RPTN(_CRT_ERROR, "Damage before 0x%p which was allocated by aligned routine\n", block);
}
_free_dbg(header->_head, _NORMAL_BLOCK);
}