//============================================================
//
// winalloc.c - Win32 memory allocation routines
//
// Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
// Visit http://mamedev.org for licensing and usage restrictions.
//
//============================================================
#define _WIN32_WINNT 0x0400
// standard windows headers
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// MAME headers
#include "osdcore.h"
// undefine any redefines we have in the prefix
#undef malloc
#undef calloc
#undef realloc
//============================================================
// CONSTANTS
//============================================================
#define PAGE_SIZE 4096
#define COOKIE_VAL 0x11335577
// set this to 0 to not use guard pages
#define USE_GUARD_PAGES 1
// set this to 1 to align memory blocks to the start of a page;
// otherwise, they are aligned to the end, thus catching array
// overruns
#define ALIGN_START 0
// set this to 1 to record all mallocs and frees in the logfile
#define LOG_CALLS 0
//============================================================
// TYPEDEFS
//============================================================
typedef struct _memory_entry memory_entry;
struct _memory_entry
{
memory_entry * next;
memory_entry * prev;
size_t size;
void * base;
const char * file;
int line;
int id;
};
//============================================================
// GLOBAL VARIABLES
//============================================================
int winalloc_in_main_code = FALSE;
//============================================================
// LOCAL VARIABLES
//============================================================
static memory_entry *alloc_list;
static memory_entry *free_list;
static int current_id;
static CRITICAL_SECTION memory_lock;
static UINT8 memory_lock_initialized = FALSE;
//============================================================
// INLINES
//============================================================
INLINE void memory_lock_acquire(void)
{
if (!memory_lock_initialized)
{
memory_lock_initialized = TRUE;
InitializeCriticalSection(&memory_lock);
}
EnterCriticalSection(&memory_lock);
}
INLINE void memory_lock_release(void)
{
LeaveCriticalSection(&memory_lock);
}
INLINE memory_entry *allocate_entry(void)
{
memory_entry *entry;
memory_lock_acquire();
// if we're out of entries, allocate some more
if (free_list == NULL)
{
int entries_per_page = PAGE_SIZE / sizeof(memory_entry);
// allocate a new pages' worth of entry
entry = (memory_entry *)VirtualAlloc(NULL, PAGE_SIZE, MEM_COMMIT, PAGE_READWRITE);
if (entry == NULL)
{
fprintf(stderr, "Out of memory for malloc tracking!\n");
exit(1);
}
// add all the entries to the list
while (entries_per_page--)
{
entry->next = free_list;
free_list = entry;
entry++;
}
}
// grab a free list entry
entry = free_list;
free_list = free_list->next;
// add ourselves to the alloc list
entry->next = alloc_list;
if (entry->next)
entry->next->prev = entry;
entry->prev = NULL;
alloc_list = entry;
memory_lock_release();
return entry;
}
INLINE memory_entry *find_entry(void *pointer)
{
memory_entry *entry;
// scan the list looking for a matching base
if (pointer)
{
memory_lock_acquire();
for (entry = alloc_list; entry; entry = entry->next)
if (entry->base == pointer)
break;
memory_lock_release();
return entry;
}
return NULL;
}
INLINE void free_entry(memory_entry *entry)
{
memory_lock_acquire();
// remove ourselves from the alloc list
if (entry->prev)
entry->prev->next = entry->next;
else
alloc_list = entry->next;
if (entry->next)
entry->next->prev = entry->prev;
// add ourself to the free list
entry->next = free_list;
free_list = entry;
memory_lock_release();
}
//============================================================
// IMPLEMENTATION
//============================================================
void *malloc_file_line(size_t size, const char *file, int line)
{
UINT8 *page_base, *block_base;
size_t rounded_size;
memory_entry *entry;
if (USE_GUARD_PAGES)
{
// round the size up to a page boundary
rounded_size = ((size + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE;
// reserve that much memory, plus two guard pages
page_base = VirtualAlloc(NULL, rounded_size + 2 * PAGE_SIZE, MEM_RESERVE, PAGE_NOACCESS);
if (page_base == NULL)
return NULL;
// now allow access to everything but the first and last pages
page_base = VirtualAlloc(page_base + PAGE_SIZE, rounded_size, MEM_COMMIT, PAGE_READWRITE);
if (page_base == NULL)
return NULL;
// work backwards from the page base to get to the block base
if (ALIGN_START)
block_base = page_base;
else
block_base = page_base + rounded_size - size;
}
else
{
block_base = (UINT8 *)GlobalAlloc(GMEM_FIXED, size);
}
// fill in the entry
entry = allocate_entry();
entry->size = size;
entry->base = block_base;
entry->file = file;
entry->line = line;
entry->id = current_id++;
//if (entry->size == 72 && IsDebuggerPresent()) DebugBreak();
#if LOG_CALLS
// logging
if (entry->file)
logerror("malloc #%06d size = %d (%s:%d)\n", entry->size, entry->file, entry->line);
else
logerror("malloc #%06d size = %d\n", entry->id, entry->size);
#endif
return block_base;
}
void *CLIB_DECL malloc(size_t size)
{
return malloc_file_line(size, NULL, 0);
}
void *calloc_file_line(size_t size, size_t count, const char *file, int line)
{
// first allocate the memory
void *memory = malloc_file_line(size * count, file, line);
if (memory == NULL)
return NULL;
// then memset it
memset(memory, 0, size * count);
return memory;
}
void *CLIB_DECL calloc(size_t size, size_t count)
{
return calloc_file_line(size, count, NULL, 0);
}
// this function is called by beginthreadex
void *CLIB_DECL _calloc_crt(size_t size, size_t count)
{
return calloc_file_line(size, count, NULL, 0);
}
void *realloc_file_line(void *memory, size_t size, const char *file, int line)
{
void *newmemory = NULL;
// if size is non-zero, we need to reallocate memory
if (size != 0)
{
// allocate space for the new amount
newmemory = malloc_file_line(size, file, line);
if (newmemory == NULL)
return NULL;
// if we have an old pointer, copy it
if (memory != NULL)
{
memory_entry *entry = find_entry(memory);
if (entry == NULL)
{
if (winalloc_in_main_code)
{
fprintf(stderr, "Error: realloc a non-existant block (%s:%d)\n", file, line);
osd_break_into_debugger("Error: realloc a non-existant block\n");
}
}
else
memcpy(newmemory, memory, (size < entry->size) ? size : entry->size);
}
}
// if we have an original pointer, free it
if (memory != NULL)
free(memory);
return newmemory;
}
void *CLIB_DECL realloc(void *memory, size_t size)
{
return realloc_file_line(memory, size, NULL, 0);
}
void CLIB_DECL free(void *memory)
{
memory_entry *entry;
// allow NULL frees
if (memory == NULL)
return;
// error if no entry found
entry = find_entry(memory);
if (entry == NULL)
{
if (winalloc_in_main_code)
{
fprintf(stderr, "Error: free a non-existant block\n");
osd_break_into_debugger("Error: free a non-existant block");
}
return;
}
free_entry(entry);
// free the memory
if (USE_GUARD_PAGES)
VirtualFree((UINT8 *)memory - ((size_t)memory & (PAGE_SIZE-1)) - PAGE_SIZE, 0, MEM_RELEASE);
else
GlobalFree(memory);
#if LOG_CALLS
logerror("free #%06d size = %d\n", entry->id, entry->size);
#endif
}
size_t CLIB_DECL _msize(void *memory)
{
memory_entry *entry = find_entry(memory);
if (entry == NULL)
{
if (winalloc_in_main_code)
{
fprintf(stderr, "Error: msize a non-existant block\n");
osd_break_into_debugger("Error: msize a non-existant block");
}
return 0;
}
return entry->size;
}
void check_unfreed_mem(void)
{
memory_entry *entry;
int total = 0;
memory_lock_acquire();
// check for leaked memory
for (entry = alloc_list; entry; entry = entry->next)
if (entry->file != NULL)
{
if (total == 0)
fprintf(stderr, "--- memory leak warning ---\n");
total += entry->size;
fprintf(stderr, "allocation #%06d, %d bytes (%s:%d)\n", entry->id, entry->size, entry->file, entry->line);
}
memory_lock_release();
if (total > 0)
fprintf(stderr, "a total of %d bytes were not free()'d\n", total);
}