Atlas - SDL_test_memory.c
Home / ext / SDL / src / test Lines: 5 | Size: 15109 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include <SDL3/SDL_test.h> 22 23#ifdef HAVE_LIBUNWIND_H 24#define UNW_LOCAL_ONLY 25#include <libunwind.h> 26#ifndef unw_get_proc_name_by_ip 27#define SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 28static bool s_unwind_symbol_names = true; 29#endif 30#endif 31 32#ifdef SDL_PLATFORM_WIN32 33#include <windows.h> 34#include <dbghelp.h> 35 36static struct { 37 SDL_SharedObject *module; 38 BOOL (WINAPI *pSymInitialize)(HANDLE hProcess, PCSTR UserSearchPath, BOOL fInvadeProcess); 39 BOOL (WINAPI *pSymFromAddr)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFO Symbol); 40 BOOL (WINAPI *pSymGetLineFromAddr64)(HANDLE hProcess, DWORD64 qwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line); 41} dyn_dbghelp; 42 43/* older SDKs might not have this: */ 44__declspec(dllimport) USHORT WINAPI RtlCaptureStackBackTrace(ULONG FramesToSkip, ULONG FramesToCapture, PVOID* BackTrace, PULONG BackTraceHash); 45#define CaptureStackBackTrace RtlCaptureStackBackTrace 46 47#endif 48 49/* This is a simple tracking allocator to demonstrate the use of SDL's 50 memory allocation replacement functionality. 51 52 It gets slow with large numbers of allocations and shouldn't be used 53 for production code. 54*/ 55 56#define MAXIMUM_TRACKED_STACK_DEPTH 32 57 58typedef struct SDL_tracked_allocation 59{ 60 void *mem; 61 size_t size; 62 Uint64 stack[MAXIMUM_TRACKED_STACK_DEPTH]; 63 struct SDL_tracked_allocation *next; 64#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 65 char stack_names[MAXIMUM_TRACKED_STACK_DEPTH][256]; 66#endif 67} SDL_tracked_allocation; 68 69static SDLTest_Crc32Context s_crc32_context; 70static SDL_malloc_func SDL_malloc_orig = NULL; 71static SDL_calloc_func SDL_calloc_orig = NULL; 72static SDL_realloc_func SDL_realloc_orig = NULL; 73static SDL_free_func SDL_free_orig = NULL; 74static int s_previous_allocations = 0; 75static int s_unknown_frees = 0; 76static SDL_tracked_allocation *s_tracked_allocations[256]; 77static bool s_randfill_allocations = false; 78static SDL_AtomicInt s_lock; 79 80#define LOCK_ALLOCATOR() \ 81 do { \ 82 if (SDL_CompareAndSwapAtomicInt(&s_lock, 0, 1)) { \ 83 break; \ 84 } \ 85 SDL_CPUPauseInstruction(); \ 86 } while (true) 87#define UNLOCK_ALLOCATOR() do { SDL_SetAtomicInt(&s_lock, 0); } while (0) 88 89static unsigned int get_allocation_bucket(void *mem) 90{ 91 CrcUint32 crc_value; 92 unsigned int index; 93 SDLTest_Crc32Calc(&s_crc32_context, (CrcUint8 *)&mem, sizeof(mem), &crc_value); 94 index = (crc_value & (SDL_arraysize(s_tracked_allocations) - 1)); 95 return index; 96} 97 98static SDL_tracked_allocation *SDL_GetTrackedAllocation(void *mem) 99{ 100 SDL_tracked_allocation *entry; 101 LOCK_ALLOCATOR(); 102 int index = get_allocation_bucket(mem); 103 for (entry = s_tracked_allocations[index]; entry; entry = entry->next) { 104 if (mem == entry->mem) { 105 UNLOCK_ALLOCATOR(); 106 return entry; 107 } 108 } 109 UNLOCK_ALLOCATOR(); 110 return NULL; 111} 112 113static size_t SDL_GetTrackedAllocationSize(void *mem) 114{ 115 SDL_tracked_allocation *entry = SDL_GetTrackedAllocation(mem); 116 117 return entry ? entry->size : SIZE_MAX; 118} 119 120static bool SDL_IsAllocationTracked(void *mem) 121{ 122 return SDL_GetTrackedAllocation(mem) != NULL; 123} 124 125static void SDL_TrackAllocation(void *mem, size_t size) 126{ 127 SDL_tracked_allocation *entry; 128 int index = get_allocation_bucket(mem); 129 130 if (SDL_IsAllocationTracked(mem)) { 131 return; 132 } 133 entry = (SDL_tracked_allocation *)SDL_malloc_orig(sizeof(*entry)); 134 if (!entry) { 135 return; 136 } 137 LOCK_ALLOCATOR(); 138 entry->mem = mem; 139 entry->size = size; 140 141 /* Generate the stack trace for the allocation */ 142 SDL_zeroa(entry->stack); 143#ifdef HAVE_LIBUNWIND_H 144 { 145 int stack_index; 146 unw_cursor_t cursor; 147 unw_context_t context; 148 149 unw_getcontext(&context); 150 unw_init_local(&cursor, &context); 151 152 stack_index = 0; 153 while (unw_step(&cursor) > 0) { 154 unw_word_t pc; 155#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 156 unw_word_t offset; 157 char sym[236]; 158#endif 159 160 unw_get_reg(&cursor, UNW_REG_IP, &pc); 161 entry->stack[stack_index] = pc; 162 163#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 164 if (s_unwind_symbol_names && unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) { 165 SDL_snprintf(entry->stack_names[stack_index], sizeof(entry->stack_names[stack_index]), "%s+0x%llx", sym, (unsigned long long)offset); 166 } 167#endif 168 ++stack_index; 169 170 if (stack_index == SDL_arraysize(entry->stack)) { 171 break; 172 } 173 } 174 } 175#elif defined(SDL_PLATFORM_WIN32) 176 { 177 Uint32 count; 178 PVOID frames[63]; 179 Uint32 i; 180 181 count = CaptureStackBackTrace(1, SDL_arraysize(frames), frames, NULL); 182 183 count = SDL_min(count, MAXIMUM_TRACKED_STACK_DEPTH); 184 for (i = 0; i < count; i++) { 185 entry->stack[i] = (Uint64)(uintptr_t)frames[i]; 186 } 187 } 188#endif /* HAVE_LIBUNWIND_H */ 189 190 entry->next = s_tracked_allocations[index]; 191 s_tracked_allocations[index] = entry; 192 UNLOCK_ALLOCATOR(); 193} 194 195static void SDL_UntrackAllocation(void *mem) 196{ 197 SDL_tracked_allocation *entry, *prev; 198 int index = get_allocation_bucket(mem); 199 200 LOCK_ALLOCATOR(); 201 prev = NULL; 202 for (entry = s_tracked_allocations[index]; entry; entry = entry->next) { 203 if (mem == entry->mem) { 204 if (prev) { 205 prev->next = entry->next; 206 } else { 207 s_tracked_allocations[index] = entry->next; 208 } 209 SDL_free_orig(entry); 210 UNLOCK_ALLOCATOR(); 211 return; 212 } 213 prev = entry; 214 } 215 s_unknown_frees += 1; 216 UNLOCK_ALLOCATOR(); 217} 218 219static void rand_fill_memory(void *ptr, size_t start, size_t end) 220{ 221 Uint8 *mem = (Uint8 *)ptr; 222 size_t i; 223 224 if (!s_randfill_allocations) 225 return; 226 227 for (i = start; i < end; ++i) { 228 mem[i] = SDLTest_RandomUint8(); 229 } 230} 231 232static void * SDLCALL SDLTest_TrackedMalloc(size_t size) 233{ 234 void *mem; 235 236 mem = SDL_malloc_orig(size); 237 if (mem) { 238 SDL_TrackAllocation(mem, size); 239 rand_fill_memory(mem, 0, size); 240 } 241 return mem; 242} 243 244static void * SDLCALL SDLTest_TrackedCalloc(size_t nmemb, size_t size) 245{ 246 void *mem; 247 248 mem = SDL_calloc_orig(nmemb, size); 249 if (mem) { 250 SDL_TrackAllocation(mem, nmemb * size); 251 } 252 return mem; 253} 254 255static void * SDLCALL SDLTest_TrackedRealloc(void *ptr, size_t size) 256{ 257 void *mem; 258 size_t old_size = 0; 259 if (ptr) { 260 old_size = SDL_GetTrackedAllocationSize(ptr); 261 SDL_assert(old_size != SIZE_MAX); 262 } 263 mem = SDL_realloc_orig(ptr, size); 264 if (ptr) { 265 SDL_UntrackAllocation(ptr); 266 } 267 if (mem) { 268 SDL_TrackAllocation(mem, size); 269 if (size > old_size) { 270 rand_fill_memory(mem, old_size, size); 271 } 272 } 273 return mem; 274} 275 276static void SDLCALL SDLTest_TrackedFree(void *ptr) 277{ 278 if (!ptr) { 279 return; 280 } 281 282 if (s_previous_allocations == 0) { 283 SDL_assert(SDL_IsAllocationTracked(ptr)); 284 } 285 SDL_UntrackAllocation(ptr); 286 SDL_free_orig(ptr); 287} 288 289void SDLTest_TrackAllocations(void) 290{ 291 if (SDL_malloc_orig) { 292 return; 293 } 294 295 SDLTest_Crc32Init(&s_crc32_context); 296 297 s_previous_allocations = SDL_GetNumAllocations(); 298#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 299 do { 300 /* Don't use SDL_GetHint: SDL_malloc is off limits. */ 301 const char *env_trackmem = SDL_getenv_unsafe("SDL_TRACKMEM_SYMBOL_NAMES"); 302 if (env_trackmem) { 303 if (SDL_strcasecmp(env_trackmem, "1") == 0 || SDL_strcasecmp(env_trackmem, "yes") == 0 || SDL_strcasecmp(env_trackmem, "true") == 0) { 304 s_unwind_symbol_names = true; 305 } else if (SDL_strcasecmp(env_trackmem, "0") == 0 || SDL_strcasecmp(env_trackmem, "no") == 0 || SDL_strcasecmp(env_trackmem, "false") == 0) { 306 s_unwind_symbol_names = false; 307 } 308 } 309 } while (0); 310 311#elif defined(SDL_PLATFORM_WIN32) 312 do { 313 dyn_dbghelp.module = SDL_LoadObject("dbghelp.dll"); 314 if (!dyn_dbghelp.module) { 315 goto dbghelp_failed; 316 } 317 dyn_dbghelp.pSymInitialize = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymInitialize"); 318 dyn_dbghelp.pSymFromAddr = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymFromAddr"); 319 dyn_dbghelp.pSymGetLineFromAddr64 = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymGetLineFromAddr64"); 320 if (!dyn_dbghelp.pSymInitialize || !dyn_dbghelp.pSymFromAddr || !dyn_dbghelp.pSymGetLineFromAddr64) { 321 goto dbghelp_failed; 322 } 323 if (!dyn_dbghelp.pSymInitialize(GetCurrentProcess(), NULL, TRUE)) { 324 goto dbghelp_failed; 325 } 326 break; 327dbghelp_failed: 328 if (dyn_dbghelp.module) { 329 SDL_UnloadObject(dyn_dbghelp.module); 330 dyn_dbghelp.module = NULL; 331 } 332 } while (0); 333#endif 334 335 SDL_GetMemoryFunctions(&SDL_malloc_orig, 336 &SDL_calloc_orig, 337 &SDL_realloc_orig, 338 &SDL_free_orig); 339 340 SDL_SetMemoryFunctions(SDLTest_TrackedMalloc, 341 SDLTest_TrackedCalloc, 342 SDLTest_TrackedRealloc, 343 SDLTest_TrackedFree); 344 345 if (s_previous_allocations < 0) { 346 SDL_Log("SDL was built without allocation count support, disabling free() validation"); 347 } else if (s_previous_allocations != 0) { 348 SDL_Log("SDLTest_TrackAllocations(): There are %d previous allocations, disabling free() validation", s_previous_allocations); 349 } 350} 351 352void SDLTest_RandFillAllocations(void) 353{ 354 SDLTest_TrackAllocations(); 355 356 s_randfill_allocations = true; 357} 358 359void SDLTest_LogAllocations(void) 360{ 361 char *message = NULL; 362 size_t message_size = 0; 363 char line[256], *tmp; 364 SDL_tracked_allocation *entry; 365 int index, count, stack_index; 366 Uint64 total_allocated; 367 368 if (!SDL_malloc_orig) { 369 return; 370 } 371 372 message = SDL_realloc_orig(NULL, 1); 373 if (!message) { 374 return; 375 } 376 *message = 0; 377 378#define ADD_LINE() \ 379 message_size += (SDL_strlen(line) + 1); \ 380 tmp = (char *)SDL_realloc_orig(message, message_size); \ 381 if (!tmp) { \ 382 return; \ 383 } \ 384 message = tmp; \ 385 SDL_strlcat(message, line, message_size) 386 387 SDL_strlcpy(line, "Memory allocations:\n", sizeof(line)); 388 ADD_LINE(); 389 390 count = 0; 391 total_allocated = 0; 392 for (index = 0; index < SDL_arraysize(s_tracked_allocations); ++index) { 393 for (entry = s_tracked_allocations[index]; entry; entry = entry->next) { 394 (void)SDL_snprintf(line, sizeof(line), "Allocation %d: %d bytes\n", count, (int)entry->size); 395 ADD_LINE(); 396 /* Start at stack index 1 to skip our tracking functions */ 397 for (stack_index = 1; stack_index < SDL_arraysize(entry->stack); ++stack_index) { 398 char stack_entry_description[256] = "???"; 399 400 if (!entry->stack[stack_index]) { 401 break; 402 } 403#ifdef HAVE_LIBUNWIND_H 404 { 405#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 406 if (s_unwind_symbol_names) { 407 (void)SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s", entry->stack_names[stack_index]); 408 } 409#else 410 char name[256] = "???"; 411 unw_word_t offset = 0; 412 unw_get_proc_name_by_ip(unw_local_addr_space, entry->stack[stack_index], name, sizeof(name), &offset, NULL); 413 (void)SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s+0x%llx", name, (long long unsigned int)offset); 414#endif 415 } 416#elif defined(SDL_PLATFORM_WIN32) 417 { 418 DWORD64 dwDisplacement = 0; 419 char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; 420 PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbol_buffer; 421 DWORD lineColumn = 0; 422 pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); 423 pSymbol->MaxNameLen = MAX_SYM_NAME; 424 IMAGEHLP_LINE64 dbg_line; 425 dbg_line.SizeOfStruct = sizeof(dbg_line); 426 dbg_line.FileName = ""; 427 dbg_line.LineNumber = 0; 428 429 if (dyn_dbghelp.module) { 430 if (!dyn_dbghelp.pSymFromAddr(GetCurrentProcess(), entry->stack[stack_index], &dwDisplacement, pSymbol)) { 431 SDL_strlcpy(pSymbol->Name, "???", MAX_SYM_NAME); 432 dwDisplacement = 0; 433 } 434 dyn_dbghelp.pSymGetLineFromAddr64(GetCurrentProcess(), (DWORD64)entry->stack[stack_index], &lineColumn, &dbg_line); 435 } 436 SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s+0x%I64x %s:%u", pSymbol->Name, dwDisplacement, dbg_line.FileName, (Uint32)dbg_line.LineNumber); 437 } 438#endif 439 (void)SDL_snprintf(line, sizeof(line), "\t0x%" SDL_PRIx64 ": %s\n", entry->stack[stack_index], stack_entry_description); 440 441 ADD_LINE(); 442 } 443 total_allocated += entry->size; 444 ++count; 445 } 446 } 447 (void)SDL_snprintf(line, sizeof(line), "Total: %.2f Kb in %d allocations", total_allocated / 1024.0, count); 448 ADD_LINE(); 449 if (s_unknown_frees != 0) { 450 (void)SDL_snprintf(line, sizeof(line), ", %d unknown frees", s_unknown_frees); 451 ADD_LINE(); 452 } 453 (void)SDL_snprintf(line, sizeof(line), "\n"); 454 ADD_LINE(); 455#undef ADD_LINE 456 457 SDL_Log("%s", message); 458 SDL_free_orig(message); 459} 460[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.