Skip to content

Instantly share code, notes, and snippets.

@zeux
Created February 12, 2016 08:26
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeux/9e05771f7edca8165a3e to your computer and use it in GitHub Desktop.
Save zeux/9e05771f7edca8165a3e to your computer and use it in GitHub Desktop.
Overriding CRT heap functions to use a custom allocator.
// User-defined global heap prototypes
extern void mem_global_init();
extern void mem_global_term();
extern void* mem_global_allocate(size_t size, size_t align);
extern void mem_global_deallocate(void* ptr);
extern size_t mem_global_get_size(void* ptr);
// Actual code
#define BREAK() __debugbreak()
#include <stdio.h>
#include <stdlib.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
static inline void patch_with_jump(void* dest, void* address)
{
// get offset for relative jmp
unsigned int offset = (unsigned int)((char*)address - (char*)dest - 5);
// unprotect memory
unsigned long old_protect;
VirtualProtect(dest, 5, PAGE_READWRITE, &old_protect);
// write jmp
*(unsigned char*)dest = 0xe9;
*(unsigned int*)((char*)dest + 1) = offset;
// protect memory
VirtualProtect(dest, 5, old_protect, &old_protect);
}
static void break_stub()
{
BREAK();
}
#define PATCH(target, stub) void target(); patch_with_jump((void*)(target), (void*)(stub))
#define PATCH_STUB(target) PATCH(target, target ## _stub)
#define PATCH_BREAK(target) PATCH(target, break_stub)
#ifdef _DEBUG
#define PATCH_DEBUG(target, stub) PATCH(target, stub)
#define PATCH_BREAK_DEBUG(target) PATCH_BREAK(target)
#else
#define PATCH_DEBUG(target, stub) (void)0
#define PATCH_BREAK_DEBUG(target) (void)0
#endif
#define PATCH_BREAK_ALL(target) PATCH_BREAK(target); PATCH_BREAK_DEBUG(target ## _base); PATCH_BREAK_DEBUG(target ## _dbg)
extern "C"
{
const size_t malloc_alignment = 8;
// This is a copy of __freeCrtMemory (we can't use it directly since it's not in CRT in release)
static void freeCrtMemory()
{
// code removed to avoid EULA issues (I hate licenses)
}
static void cleanup_crt_leaks()
{
freeCrtMemory();
// turn off buffering for stdout/stderr and release buffers
extern void* _stdbuf[2];
setvbuf(stdout, 0, _IONBF, 0);
free(_stdbuf[0]);
_stdbuf[0] = NULL;
setvbuf(stderr, 0, _IONBF, 0);
free(_stdbuf[1]);
_stdbuf[1] = NULL;
// free ptmcbinfo (allocated in _setmbcp)
extern pthreadmbcinfo __ptmbcinfo;
free(__ptmbcinfo);
__ptmbcinfo = NULL;
// free ioinfo (allocated in _ioinit)
extern void* __pioinfo[];
free(__pioinfo[0]);
__pioinfo[0] = NULL;
// free ptd (allocated in _mtinit)
void* _getptd_noexit();
free(_getptd_noexit());
// free TLS indices (allocated in _mtinit) and critical sections (allocated in _mtinitlocknum)
void _mtterm();
_mtterm();
}
static int heap_init_stub(int)
{
// call user init function
mem_global_init();
// pretend that we initialized CRT heap (needed for _mtinitlocknum)
extern HANDLE _crtheap;
_crtheap = (HANDLE)-1;
return 1;
}
static void heap_term_stub()
{
// cleanup memory that CRT writers are too lazy to free themselves (bastards!)
cleanup_crt_leaks();
// call user init function
mem_global_term();
}
static void* malloc_stub(size_t size)
{
return mem_global_allocate(size, malloc_alignment);
}
static void* calloc_stub(size_t count, size_t size)
{
void* ptr = mem_global_allocate(count * size, malloc_alignment);
if (ptr) memset(ptr, 0, mem_global_get_size(ptr));
return ptr;
}
static void* realloc_stub(void* ptr, size_t size)
{
if (!ptr)
{
return mem_global_allocate(size, malloc_alignment);
}
if (!size)
{
mem_global_deallocate(ptr);
return 0;
}
size_t old_size = mem_global_get_size(ptr);
void* result = mem_global_allocate(size, malloc_alignment);
if (result)
{
memcpy(result, ptr, min(size, old_size));
mem_global_deallocate(ptr);
}
return result;
}
// Note: this function has a bug - since it calculates size to clear using
// mem_global_get_size, and it can return inaccurate values (i.e. more than
// allocated size), it's possible that not everything is cleared (i.e. malloc(0),
// followed by recalloc(100) will not clear first 12 bytes for current implementation).
// However, there is no problem if only calloc/_recalloc were used for the block in question,
// and this is the case in CRT, so I won't worry about it.
static void* recalloc_stub(void* ptr, size_t count, size_t size)
{
if (!ptr)
{
return calloc_stub(count, size);
}
if (!size)
{
mem_global_deallocate(ptr);
return 0;
}
size_t old_size = mem_global_get_size(ptr);
void* result = calloc_stub(count, size);
if (result)
{
memcpy(result, ptr, min(size, old_size));
mem_global_deallocate(ptr);
}
return result;
}
static void free_stub(void* ptr)
{
mem_global_deallocate(ptr);
}
static size_t msize_stub(void* ptr)
{
return mem_global_get_size(ptr);
}
static void patch_memory_management_functions()
{
// heap init/term
// note: we patch __crtCorExitProcess because _heap_term is called only in DLL CRT
PATCH(_heap_init, heap_init_stub);
PATCH(__crtCorExitProcess, heap_term_stub);
// malloc
PATCH(malloc, malloc_stub);
PATCH(_malloc_crt, malloc_stub);
PATCH_DEBUG(_malloc_base, malloc_stub);
PATCH_DEBUG(_malloc_dbg, malloc_stub);
// calloc
PATCH(calloc, calloc_stub);
PATCH(_calloc_crt, calloc_stub);
PATCH_DEBUG(_calloc_base, calloc_stub);
PATCH_DEBUG(_calloc_dbg, calloc_stub);
// realloc
PATCH(realloc, realloc_stub);
PATCH(_realloc_crt, realloc_stub);
PATCH_DEBUG(_realloc_base, realloc_stub);
PATCH_DEBUG(_realloc_dbg, realloc_stub);
// recalloc
PATCH(_recalloc, recalloc_stub);
PATCH(_recalloc_crt, recalloc_stub);
PATCH_DEBUG(_recalloc_base, recalloc_stub);
PATCH_DEBUG(_recalloc_dbg, recalloc_stub);
// free
PATCH(free, free_stub);
PATCH_DEBUG(_free_nolock, free_stub);
PATCH_DEBUG(_free_dbg_nolock, free_stub);
PATCH_DEBUG(_free_base, free_stub);
PATCH_DEBUG(_free_dbg, free_stub);
// msize
PATCH(_msize, msize_stub);
PATCH_DEBUG(_msize_base, msize_stub);
PATCH_DEBUG(_msize_dbg, msize_stub);
// aligned functions
PATCH_BREAK_ALL(_aligned_malloc);
PATCH_BREAK_ALL(_aligned_offset_malloc);
PATCH_BREAK_ALL(_aligned_realloc);
PATCH_BREAK_ALL(_aligned_recalloc);
PATCH_BREAK_ALL(_aligned_offset_realloc);
PATCH_BREAK_ALL(_aligned_msize);
PATCH_BREAK_ALL(_aligned_offset_recalloc);
PATCH_BREAK_ALL(_aligned_free);
PATCH_BREAK_ALL(_aligned_malloc);
// supporting functions
PATCH_BREAK_DEBUG(_nh_malloc);
PATCH_BREAK_DEBUG(_nh_malloc_dbg);
PATCH_BREAK_ALL(_heap_alloc);
PATCH_BREAK_ALL(_expand);
}
int entrypoint()
{
int mainCRTStartup();
patch_memory_management_functions();
return mainCRTStartup();
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment