Atlas - SDL_test_memory.c

Home / ext / SDL / src / test Lines: 5 | Size: 15158 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 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 bool SDL_GetTrackedAllocation(void *mem, size_t *entry_size) 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 if (entry_size) { 106 *entry_size = entry->size; 107 } 108 UNLOCK_ALLOCATOR(); 109 return true; 110 } 111 } 112 UNLOCK_ALLOCATOR(); 113 return false; 114} 115 116static size_t SDL_GetTrackedAllocationSize(void *mem) 117{ 118 size_t size = 0; 119 if (!SDL_GetTrackedAllocation(mem, &size)) { 120 size = SIZE_MAX; 121 } 122 return size; 123} 124 125static bool SDL_IsAllocationTracked(void *mem) 126{ 127 return SDL_GetTrackedAllocation(mem, NULL); 128} 129 130static void SDL_TrackAllocation(void *mem, size_t size) 131{ 132 SDL_tracked_allocation *entry; 133 int index = get_allocation_bucket(mem); 134 135 if (SDL_IsAllocationTracked(mem)) { 136 return; 137 } 138 entry = (SDL_tracked_allocation *)SDL_malloc_orig(sizeof(*entry)); 139 if (!entry) { 140 return; 141 } 142 LOCK_ALLOCATOR(); 143 entry->mem = mem; 144 entry->size = size; 145 146 /* Generate the stack trace for the allocation */ 147 SDL_zeroa(entry->stack); 148#ifdef HAVE_LIBUNWIND_H 149 { 150 int stack_index; 151 unw_cursor_t cursor; 152 unw_context_t context; 153 154 unw_getcontext(&context); 155 unw_init_local(&cursor, &context); 156 157 stack_index = 0; 158 while (unw_step(&cursor) > 0) { 159 unw_word_t pc; 160#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 161 unw_word_t offset; 162 char sym[236]; 163#endif 164 165 unw_get_reg(&cursor, UNW_REG_IP, &pc); 166 entry->stack[stack_index] = pc; 167 168#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 169 if (s_unwind_symbol_names && unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) { 170 SDL_snprintf(entry->stack_names[stack_index], sizeof(entry->stack_names[stack_index]), "%s+0x%llx", sym, (unsigned long long)offset); 171 } 172#endif 173 ++stack_index; 174 175 if (stack_index == SDL_arraysize(entry->stack)) { 176 break; 177 } 178 } 179 } 180#elif defined(SDL_PLATFORM_WIN32) 181 { 182 Uint32 count; 183 PVOID frames[63]; 184 Uint32 i; 185 186 count = CaptureStackBackTrace(1, SDL_arraysize(frames), frames, NULL); 187 188 count = SDL_min(count, MAXIMUM_TRACKED_STACK_DEPTH); 189 for (i = 0; i < count; i++) { 190 entry->stack[i] = (Uint64)(uintptr_t)frames[i]; 191 } 192 } 193#endif /* HAVE_LIBUNWIND_H */ 194 195 entry->next = s_tracked_allocations[index]; 196 s_tracked_allocations[index] = entry; 197 UNLOCK_ALLOCATOR(); 198} 199 200static void SDL_UntrackAllocation(void *mem) 201{ 202 SDL_tracked_allocation *entry, **prev_next_ptr; 203 int index = get_allocation_bucket(mem); 204 205 LOCK_ALLOCATOR(); 206 prev_next_ptr = &s_tracked_allocations[index]; 207 for (entry = s_tracked_allocations[index]; entry; entry = entry->next) { 208 if (mem == entry->mem) { 209 *prev_next_ptr = entry->next; 210 SDL_free_orig(entry); 211 UNLOCK_ALLOCATOR(); 212 return; 213 } 214 prev_next_ptr = &entry->next; 215 } 216 s_unknown_frees += 1; 217 UNLOCK_ALLOCATOR(); 218} 219 220static void rand_fill_memory(void *ptr, size_t start, size_t end) 221{ 222 Uint8 *mem = (Uint8 *)ptr; 223 size_t i; 224 225 if (!s_randfill_allocations) 226 return; 227 228 for (i = start; i < end; ++i) { 229 mem[i] = SDLTest_RandomUint8(); 230 } 231} 232 233static void * SDLCALL SDLTest_TrackedMalloc(size_t size) 234{ 235 void *mem; 236 237 mem = SDL_malloc_orig(size); 238 if (mem) { 239 SDL_TrackAllocation(mem, size); 240 rand_fill_memory(mem, 0, size); 241 } 242 return mem; 243} 244 245static void * SDLCALL SDLTest_TrackedCalloc(size_t nmemb, size_t size) 246{ 247 void *mem; 248 249 mem = SDL_calloc_orig(nmemb, size); 250 if (mem) { 251 SDL_TrackAllocation(mem, nmemb * size); 252 } 253 return mem; 254} 255 256static void * SDLCALL SDLTest_TrackedRealloc(void *ptr, size_t size) 257{ 258 void *mem; 259 size_t old_size = 0; 260 if (ptr) { 261 old_size = SDL_GetTrackedAllocationSize(ptr); 262 SDL_assert(old_size != SIZE_MAX); 263 } 264 mem = SDL_realloc_orig(ptr, size); 265 if (ptr) { 266 SDL_UntrackAllocation(ptr); 267 } 268 if (mem) { 269 SDL_TrackAllocation(mem, size); 270 if (size > old_size) { 271 rand_fill_memory(mem, old_size, size); 272 } 273 } 274 return mem; 275} 276 277static void SDLCALL SDLTest_TrackedFree(void *ptr) 278{ 279 if (!ptr) { 280 return; 281 } 282 283 if (s_previous_allocations == 0) { 284 SDL_assert(SDL_IsAllocationTracked(ptr)); 285 } 286 SDL_UntrackAllocation(ptr); 287 SDL_free_orig(ptr); 288} 289 290void SDLTest_TrackAllocations(void) 291{ 292 if (SDL_malloc_orig) { 293 return; 294 } 295 296 SDLTest_Crc32Init(&s_crc32_context); 297 298 s_previous_allocations = SDL_GetNumAllocations(); 299#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 300 do { 301 /* Don't use SDL_GetHint: SDL_malloc is off limits. */ 302 const char *env_trackmem = SDL_getenv_unsafe("SDL_TRACKMEM_SYMBOL_NAMES"); 303 if (env_trackmem) { 304 if (SDL_strcasecmp(env_trackmem, "1") == 0 || SDL_strcasecmp(env_trackmem, "yes") == 0 || SDL_strcasecmp(env_trackmem, "true") == 0) { 305 s_unwind_symbol_names = true; 306 } else if (SDL_strcasecmp(env_trackmem, "0") == 0 || SDL_strcasecmp(env_trackmem, "no") == 0 || SDL_strcasecmp(env_trackmem, "false") == 0) { 307 s_unwind_symbol_names = false; 308 } 309 } 310 } while (0); 311 312#elif defined(SDL_PLATFORM_WIN32) 313 do { 314 dyn_dbghelp.module = SDL_LoadObject("dbghelp.dll"); 315 if (!dyn_dbghelp.module) { 316 goto dbghelp_failed; 317 } 318 dyn_dbghelp.pSymInitialize = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymInitialize"); 319 dyn_dbghelp.pSymFromAddr = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymFromAddr"); 320 dyn_dbghelp.pSymGetLineFromAddr64 = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymGetLineFromAddr64"); 321 if (!dyn_dbghelp.pSymInitialize || !dyn_dbghelp.pSymFromAddr || !dyn_dbghelp.pSymGetLineFromAddr64) { 322 goto dbghelp_failed; 323 } 324 if (!dyn_dbghelp.pSymInitialize(GetCurrentProcess(), NULL, TRUE)) { 325 goto dbghelp_failed; 326 } 327 break; 328dbghelp_failed: 329 if (dyn_dbghelp.module) { 330 SDL_UnloadObject(dyn_dbghelp.module); 331 dyn_dbghelp.module = NULL; 332 } 333 } while (0); 334#endif 335 336 SDL_GetMemoryFunctions(&SDL_malloc_orig, 337 &SDL_calloc_orig, 338 &SDL_realloc_orig, 339 &SDL_free_orig); 340 341 SDL_SetMemoryFunctions(SDLTest_TrackedMalloc, 342 SDLTest_TrackedCalloc, 343 SDLTest_TrackedRealloc, 344 SDLTest_TrackedFree); 345 346 if (s_previous_allocations < 0) { 347 SDL_Log("SDL was built without allocation count support, disabling free() validation"); 348 } else if (s_previous_allocations != 0) { 349 SDL_Log("SDLTest_TrackAllocations(): There are %d previous allocations, disabling free() validation", s_previous_allocations); 350 } 351} 352 353void SDLTest_RandFillAllocations(void) 354{ 355 SDLTest_TrackAllocations(); 356 357 s_randfill_allocations = true; 358} 359 360void SDLTest_LogAllocations(void) 361{ 362 char *message = NULL; 363 size_t message_size = 0; 364 char line[256], *tmp; 365 SDL_tracked_allocation *entry; 366 int index, count, stack_index; 367 Uint64 total_allocated; 368 369 if (!SDL_malloc_orig) { 370 return; 371 } 372 373 message = SDL_realloc_orig(NULL, 1); 374 if (!message) { 375 return; 376 } 377 *message = 0; 378 379#define ADD_LINE() \ 380 message_size += (SDL_strlen(line) + 1); \ 381 tmp = (char *)SDL_realloc_orig(message, message_size); \ 382 if (!tmp) { \ 383 return; \ 384 } \ 385 message = tmp; \ 386 SDL_strlcat(message, line, message_size) 387 388 SDL_strlcpy(line, "Memory allocations:\n", sizeof(line)); 389 ADD_LINE(); 390 391 count = 0; 392 total_allocated = 0; 393 for (index = 0; index < SDL_arraysize(s_tracked_allocations); ++index) { 394 for (entry = s_tracked_allocations[index]; entry; entry = entry->next) { 395 (void)SDL_snprintf(line, sizeof(line), "Allocation %d: %d bytes\n", count, (int)entry->size); 396 ADD_LINE(); 397 /* Start at stack index 1 to skip our tracking functions */ 398 for (stack_index = 1; stack_index < SDL_arraysize(entry->stack); ++stack_index) { 399 char stack_entry_description[256] = "???"; 400 401 if (!entry->stack[stack_index]) { 402 break; 403 } 404#ifdef HAVE_LIBUNWIND_H 405 { 406#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP 407 if (s_unwind_symbol_names) { 408 (void)SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s", entry->stack_names[stack_index]); 409 } 410#else 411 char name[256] = "???"; 412 unw_word_t offset = 0; 413 unw_get_proc_name_by_ip(unw_local_addr_space, entry->stack[stack_index], name, sizeof(name), &offset, NULL); 414 (void)SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s+0x%llx", name, (long long unsigned int)offset); 415#endif 416 } 417#elif defined(SDL_PLATFORM_WIN32) 418 { 419 DWORD64 dwDisplacement = 0; 420 char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; 421 PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbol_buffer; 422 DWORD lineColumn = 0; 423 pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); 424 pSymbol->MaxNameLen = MAX_SYM_NAME; 425 IMAGEHLP_LINE64 dbg_line; 426 dbg_line.SizeOfStruct = sizeof(dbg_line); 427 dbg_line.FileName = ""; 428 dbg_line.LineNumber = 0; 429 430 if (dyn_dbghelp.module) { 431 if (!dyn_dbghelp.pSymFromAddr(GetCurrentProcess(), entry->stack[stack_index], &dwDisplacement, pSymbol)) { 432 SDL_strlcpy(pSymbol->Name, "???", MAX_SYM_NAME); 433 dwDisplacement = 0; 434 } 435 dyn_dbghelp.pSymGetLineFromAddr64(GetCurrentProcess(), (DWORD64)entry->stack[stack_index], &lineColumn, &dbg_line); 436 } 437 SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s+0x%I64x %s:%u", pSymbol->Name, dwDisplacement, dbg_line.FileName, (Uint32)dbg_line.LineNumber); 438 } 439#endif 440 (void)SDL_snprintf(line, sizeof(line), "\t0x%" SDL_PRIx64 ": %s\n", entry->stack[stack_index], stack_entry_description); 441 442 ADD_LINE(); 443 } 444 total_allocated += entry->size; 445 ++count; 446 } 447 } 448 (void)SDL_snprintf(line, sizeof(line), "Total: %.2f Kb in %d allocations", total_allocated / 1024.0, count); 449 ADD_LINE(); 450 if (s_unknown_frees != 0) { 451 (void)SDL_snprintf(line, sizeof(line), ", %d unknown frees", s_unknown_frees); 452 ADD_LINE(); 453 } 454 (void)SDL_snprintf(line, sizeof(line), "\n"); 455 ADD_LINE(); 456#undef ADD_LINE 457 458 SDL_Log("%s", message); 459 SDL_free_orig(message); 460} 461
[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.