You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
10 KiB
263 lines
10 KiB
#include "debug_config.h" |
|
#include <stdlib.h> |
|
#include <stdint.h> |
|
#include <string.h> |
|
#include <stdio.h> |
|
#include <stdbool.h> |
|
#include "mem.h" |
|
|
|
#ifndef BOUNDARY_CHECK_SIZE |
|
#define BOUNDARY_CHECK_SIZE 256 |
|
#endif |
|
|
|
#define CANARY 0xDEADBEEF |
|
#define PADDING_FILL 0xAA |
|
#define METADATA_SIZE 128 |
|
#define POINTER_SIZE sizeof(void*) |
|
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE * 2) |
|
#define PREV_OFFSET LOCATION_MAX |
|
#define NEXT_OFFSET (LOCATION_MAX + POINTER_SIZE) |
|
|
|
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) |
|
#include <execinfo.h> |
|
#include <pthread.h> |
|
#define BACKTRACE_ENABLED 0 |
|
#elif defined(_WIN32) |
|
#include <windows.h> |
|
#include <dbghelp.h> |
|
#define BACKTRACE_ENABLED 1 |
|
#else |
|
#define BACKTRACE_ENABLED 0 |
|
#endif |
|
|
|
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) |
|
static pthread_mutex_t alloc_mutex = PTHREAD_MUTEX_INITIALIZER; |
|
#define LOCK() pthread_mutex_lock(&alloc_mutex) |
|
#define UNLOCK() pthread_mutex_unlock(&alloc_mutex) |
|
#elif defined(_WIN32) |
|
static CRITICAL_SECTION alloc_mutex; |
|
static bool mutex_initialized = false; |
|
static void init_mutex() { |
|
if (!mutex_initialized) { |
|
InitializeCriticalSection(&alloc_mutex); |
|
mutex_initialized = true; |
|
} |
|
} |
|
#define LOCK() init_mutex(); EnterCriticalSection(&alloc_mutex) |
|
#define UNLOCK() LeaveCriticalSection(&alloc_mutex) |
|
#else |
|
#error "Unsupported platform for thread safety" |
|
#endif |
|
|
|
static void* allocated_head = NULL; |
|
static size_t allocated_count = 0; |
|
static size_t allocations_count = 0; |
|
static size_t frees_count = 0; |
|
|
|
static void hex_dump(const void* mem, size_t len) { |
|
const uint8_t* data = (const uint8_t*)mem; |
|
char line[128]; |
|
for (size_t i = 0; i < len; i += 16) { |
|
int off = snprintf(line, sizeof(line), "%08zx: ", i); |
|
for (size_t j = 0; j < 16; j++) { |
|
if (i + j < len) { |
|
off += snprintf(line + off, sizeof(line) - off, "%02x ", data[i + j]); |
|
} else { |
|
off += snprintf(line + off, sizeof(line) - off, " "); |
|
} |
|
} |
|
off += snprintf(line + off, sizeof(line) - off, " "); |
|
for (size_t j = 0; j < 16; j++) { |
|
if (i + j < len) { |
|
uint8_t c = data[i + j]; |
|
off += snprintf(line + off, sizeof(line) - off, "%c", (c >= 32 && c <= 126) ? c : '.'); |
|
} else { |
|
off += snprintf(line + off, sizeof(line) - off, " "); |
|
} |
|
} |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s", line); |
|
} |
|
} |
|
|
|
void u_check(void* ptr, const char* text, const char* location) { |
|
if (!ptr) return; |
|
uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE; |
|
char* alloc_loc = (char*)base; |
|
uint8_t* prefix_start = base + METADATA_SIZE; |
|
uint32_t* pre_canary = (uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE); |
|
uint32_t size = *(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4); |
|
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE; |
|
uint8_t* user = (uint8_t*)ptr; |
|
bool error = false; |
|
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) { |
|
if (prefix_start[i] != PADDING_FILL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)", location, text, ptr, i, PADDING_FILL, prefix_start[i]); |
|
error = true; |
|
} |
|
} |
|
if (*pre_canary != CANARY) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *pre_canary); |
|
error = true; |
|
} |
|
uint8_t* post_start = user + size + 4; |
|
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) { |
|
if (post_start[i] != PADDING_FILL) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)", location, text, ptr, i, PADDING_FILL, post_start[i]); |
|
error = true; |
|
} |
|
} |
|
uint32_t* post_canary = (uint32_t*)(user + size); |
|
if (*post_canary != CANARY) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *post_canary); |
|
error = true; |
|
} |
|
if (error) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Allocated at: %s", location, text, alloc_loc); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes", location, text, size, total); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Full memory dump at base %p:", location, text, base); |
|
hex_dump(base, total); |
|
exit(EXIT_FAILURE); |
|
} |
|
} |
|
|
|
void* u_malloc_impl(uint32_t size, const char* location) { |
|
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE; |
|
uint8_t* base = (uint8_t*)malloc(total); |
|
if (!base && size > 0) return NULL; |
|
memset(base, PADDING_FILL, total); |
|
// Set metadata |
|
size_t loc_len = strlen(location); |
|
size_t max_copy = (loc_len >= LOCATION_MAX) ? LOCATION_MAX - 1 : loc_len; |
|
memcpy(base, location, max_copy); |
|
base[max_copy] = 0; |
|
// Set allocation data |
|
uint8_t* prefix_start = base + METADATA_SIZE; |
|
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE) = CANARY; |
|
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4) = size; |
|
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 8 + size) = CANARY; |
|
// Add to list (thread-safe) |
|
LOCK(); |
|
void** prev_ptr = (void**)(base + PREV_OFFSET); |
|
void** next_ptr = (void**)(base + NEXT_OFFSET); |
|
*prev_ptr = NULL; |
|
*next_ptr = allocated_head; |
|
if (allocated_head) |
|
*(void**)((uint8_t*)allocated_head + PREV_OFFSET) = base; |
|
allocated_head = base; |
|
allocated_count++; |
|
allocations_count++; |
|
UNLOCK(); |
|
return prefix_start + BOUNDARY_CHECK_SIZE + 8; |
|
} |
|
|
|
void* u_calloc_impl(uint32_t nmemb, uint32_t size, const char* location) { |
|
uint32_t total_size = nmemb * size; |
|
void* ptr = u_malloc_impl(total_size, location); |
|
if (ptr) memset(ptr, 0, total_size); |
|
return ptr; |
|
} |
|
|
|
void* u_realloc_impl(void* ptr, uint32_t size, const char* location) { |
|
if (!ptr) return u_malloc_impl(size, location); |
|
if (size == 0) { |
|
u_free_impl(ptr, location); |
|
return NULL; |
|
} |
|
u_check(ptr, "u_realloc", location); |
|
uint8_t* old_base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE; |
|
uint32_t old_size = *(uint32_t*)(old_base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4); |
|
void* new_ptr = u_malloc_impl(size, location); |
|
if (!new_ptr) return NULL; |
|
uint32_t copy_size = (old_size < size) ? old_size : size; |
|
memcpy(new_ptr, ptr, copy_size); |
|
u_free_impl(ptr, location); |
|
return new_ptr; |
|
} |
|
|
|
void u_free_impl(void* ptr, const char* location) { |
|
if (!ptr) return; |
|
u_check(ptr, "u_free", location); |
|
uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE; |
|
char* alloc_loc = (char*)base; |
|
uint32_t size = *(uint32_t*)(base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4); |
|
// Remove from doubly-linked list (O(1)) |
|
LOCK(); |
|
void* prev = *(void**)(base + PREV_OFFSET); |
|
void* next = *(void**)(base + NEXT_OFFSET); |
|
size_t active_count = allocated_count; |
|
|
|
if (prev == NULL && allocated_head != base) { |
|
UNLOCK(); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================"); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "DOUBLE FREE DETECTED"); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================"); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Free attempt at: %s", location); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Pointer: %p", ptr); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Base: %p", (void*)base); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Allocated at: %s", alloc_loc); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "User size: %u bytes", size); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Total allocated: %u bytes", size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Active allocs: %zu", active_count); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Metadata dump at %p (%u bytes) ---", (void*)base, (unsigned)(METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8)); |
|
hex_dump(base, METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8); |
|
if (size > 0 && size <= 1024 * 1024) { |
|
uint32_t dump_size = (size > 256) ? 256 : size; |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- User data dump at %p (%u of %u bytes) ---", ptr, dump_size, size); |
|
hex_dump(ptr, dump_size); |
|
} |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
if (prev) |
|
*(void**)((uint8_t*)prev + NEXT_OFFSET) = next; |
|
else |
|
allocated_head = next; |
|
if (next) |
|
*(void**)((uint8_t*)next + PREV_OFFSET) = prev; |
|
|
|
allocated_count--; |
|
frees_count++; |
|
UNLOCK(); |
|
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE; |
|
memset(base, 0, total); |
|
free(base); |
|
} |
|
|
|
char* u_strdup_impl(const char* s, const char* location) { |
|
if (!s) return NULL; |
|
size_t len = strlen(s) + 1; |
|
char* copy = (char*)u_malloc_impl((uint32_t)len, location); |
|
if (copy) memcpy(copy, s, len); |
|
return copy; |
|
} |
|
|
|
void u_report_unfreed_blocks(void) { |
|
LOCK(); |
|
size_t count = allocated_count; |
|
void* head = allocated_head; |
|
size_t alloc_count = allocations_count; |
|
size_t free_count = frees_count; |
|
UNLOCK(); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory stats: allocations=%zu, frees=%zu, current=%zu", alloc_count, free_count, count); |
|
if (count == 0) { |
|
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "No unfreed memory blocks."); |
|
return; |
|
} |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Unfreed memory blocks: %zu, allocations: %zu frees: %zu", count, allocations_count, frees_count); |
|
void* curr = head; |
|
while (curr) { |
|
char* alloc_loc = (char*)curr; |
|
uint8_t* prefix_start = (uint8_t*)curr + METADATA_SIZE; |
|
uint32_t size = *(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4); |
|
void* user_ptr = prefix_start + BOUNDARY_CHECK_SIZE + 8; |
|
u_check(user_ptr, "unfreed_check", "u_report_unfreed_blocks"); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " - Allocated at: %s, size: %u bytes", alloc_loc, size); |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " Full memory dump at %p:", user_ptr); |
|
hex_dump(user_ptr, size); |
|
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET); |
|
} |
|
} |
|
|
|
size_t u_get_allocated_count(void) { |
|
return allocated_count; |
|
}
|
|
|