1/************************************************************************** 2 * 3 * Copyright 2008 VMware, Inc. 4 * All Rights Reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sub license, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice (including the 15 * next paragraph) shall be included in all copies or substantial portions 16 * of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 21 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 22 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 * 26 **************************************************************************/ 27 28/** 29 * @file 30 * Memory debugging. 31 * 32 * @author José Fonseca <jfonseca@vmware.com> 33 */ 34 35#include "pipe/p_config.h" 36 37#define DEBUG_MEMORY_IMPLEMENTATION 38 39#include "os/os_thread.h" 40 41#include "util/u_debug.h" 42#include "util/u_debug_gallium.h" 43#include "util/u_debug_stack.h" 44#include "util/list.h" 45#include "util/os_memory.h" 46#include "util/os_memory_debug.h" 47 48 49#define DEBUG_MEMORY_MAGIC 0x6e34090aU 50#define DEBUG_MEMORY_STACK 0 /* XXX: disabled until we have symbol lookup */ 51 52/** 53 * Set to 1 to enable checking of freed blocks of memory. 54 * Basically, don't really deallocate freed memory; keep it in the list 55 * but mark it as freed and do extra checking in debug_memory_check(). 56 * This can detect some cases of use-after-free. But note that since we 57 * never really free anything this will use a lot of memory. 58 */ 59#define DEBUG_FREED_MEMORY 0 60#define DEBUG_FREED_BYTE 0x33 61 62 63struct debug_memory_header 64{ 65 struct list_head head; 66 67 unsigned long no; 68 const char *file; 69 unsigned line; 70 const char *function; 71#if DEBUG_MEMORY_STACK 72 struct debug_stack_frame backtrace[DEBUG_MEMORY_STACK]; 73#endif 74 size_t size; 75#if DEBUG_FREED_MEMORY 76 boolean freed; /**< Is this a freed block? */ 77#endif 78 79 unsigned magic; 80 unsigned tag; 81}; 82 83struct debug_memory_footer 84{ 85 unsigned magic; 86}; 87 88 89static struct list_head list = { &list, &list }; 90 91static mtx_t list_mutex = _MTX_INITIALIZER_NP; 92 93static unsigned long last_no = 0; 94 95 96static inline struct debug_memory_header * 97header_from_data(void *data) 98{ 99 if (data) 100 return (struct debug_memory_header *)((char *)data - sizeof(struct debug_memory_header)); 101 else 102 return NULL; 103} 104 105static inline void * 106data_from_header(struct debug_memory_header *hdr) 107{ 108 if (hdr) 109 return (void *)((char *)hdr + sizeof(struct debug_memory_header)); 110 else 111 return NULL; 112} 113 114static inline struct debug_memory_footer * 115footer_from_header(struct debug_memory_header *hdr) 116{ 117 if (hdr) 118 return (struct debug_memory_footer *)((char *)hdr + sizeof(struct debug_memory_header) + hdr->size); 119 else 120 return NULL; 121} 122 123 124void * 125debug_malloc(const char *file, unsigned line, const char *function, 126 size_t size) 127{ 128 struct debug_memory_header *hdr; 129 struct debug_memory_footer *ftr; 130 131 hdr = os_malloc(sizeof(*hdr) + size + sizeof(*ftr)); 132 if (!hdr) { 133 debug_printf("%s:%u:%s: out of memory when trying to allocate %lu bytes\n", 134 file, line, function, 135 (long unsigned)size); 136 return NULL; 137 } 138 139 hdr->no = last_no++; 140 hdr->file = file; 141 hdr->line = line; 142 hdr->function = function; 143 hdr->size = size; 144 hdr->magic = DEBUG_MEMORY_MAGIC; 145 hdr->tag = 0; 146#if DEBUG_FREED_MEMORY 147 hdr->freed = FALSE; 148#endif 149 150#if DEBUG_MEMORY_STACK 151 debug_backtrace_capture(hdr->backtrace, 0, DEBUG_MEMORY_STACK); 152#endif 153 154 ftr = footer_from_header(hdr); 155 ftr->magic = DEBUG_MEMORY_MAGIC; 156 157 mtx_lock(&list_mutex); 158 LIST_ADDTAIL(&hdr->head, &list); 159 mtx_unlock(&list_mutex); 160 161 return data_from_header(hdr); 162} 163 164void 165debug_free(const char *file, unsigned line, const char *function, 166 void *ptr) 167{ 168 struct debug_memory_header *hdr; 169 struct debug_memory_footer *ftr; 170 171 if (!ptr) 172 return; 173 174 hdr = header_from_data(ptr); 175 if (hdr->magic != DEBUG_MEMORY_MAGIC) { 176 debug_printf("%s:%u:%s: freeing bad or corrupted memory %p\n", 177 file, line, function, 178 ptr); 179 debug_assert(0); 180 return; 181 } 182 183 ftr = footer_from_header(hdr); 184 if (ftr->magic != DEBUG_MEMORY_MAGIC) { 185 debug_printf("%s:%u:%s: buffer overflow %p\n", 186 hdr->file, hdr->line, hdr->function, 187 ptr); 188 debug_assert(0); 189 } 190 191#if DEBUG_FREED_MEMORY 192 /* Check for double-free */ 193 assert(!hdr->freed); 194 /* Mark the block as freed but don't really free it */ 195 hdr->freed = TRUE; 196 /* Save file/line where freed */ 197 hdr->file = file; 198 hdr->line = line; 199 /* set freed memory to special value */ 200 memset(ptr, DEBUG_FREED_BYTE, hdr->size); 201#else 202 mtx_lock(&list_mutex); 203 LIST_DEL(&hdr->head); 204 mtx_unlock(&list_mutex); 205 hdr->magic = 0; 206 ftr->magic = 0; 207 208 os_free(hdr); 209#endif 210} 211 212void * 213debug_calloc(const char *file, unsigned line, const char *function, 214 size_t count, size_t size ) 215{ 216 void *ptr = debug_malloc( file, line, function, count * size ); 217 if (ptr) 218 memset( ptr, 0, count * size ); 219 return ptr; 220} 221 222void * 223debug_realloc(const char *file, unsigned line, const char *function, 224 void *old_ptr, size_t old_size, size_t new_size ) 225{ 226 struct debug_memory_header *old_hdr, *new_hdr; 227 struct debug_memory_footer *old_ftr, *new_ftr; 228 void *new_ptr; 229 230 if (!old_ptr) 231 return debug_malloc( file, line, function, new_size ); 232 233 if (!new_size) { 234 debug_free( file, line, function, old_ptr ); 235 return NULL; 236 } 237 238 old_hdr = header_from_data(old_ptr); 239 if (old_hdr->magic != DEBUG_MEMORY_MAGIC) { 240 debug_printf("%s:%u:%s: reallocating bad or corrupted memory %p\n", 241 file, line, function, 242 old_ptr); 243 debug_assert(0); 244 return NULL; 245 } 246 247 old_ftr = footer_from_header(old_hdr); 248 if (old_ftr->magic != DEBUG_MEMORY_MAGIC) { 249 debug_printf("%s:%u:%s: buffer overflow %p\n", 250 old_hdr->file, old_hdr->line, old_hdr->function, 251 old_ptr); 252 debug_assert(0); 253 } 254 255 /* alloc new */ 256 new_hdr = os_malloc(sizeof(*new_hdr) + new_size + sizeof(*new_ftr)); 257 if (!new_hdr) { 258 debug_printf("%s:%u:%s: out of memory when trying to allocate %lu bytes\n", 259 file, line, function, 260 (long unsigned)new_size); 261 return NULL; 262 } 263 new_hdr->no = old_hdr->no; 264 new_hdr->file = old_hdr->file; 265 new_hdr->line = old_hdr->line; 266 new_hdr->function = old_hdr->function; 267 new_hdr->size = new_size; 268 new_hdr->magic = DEBUG_MEMORY_MAGIC; 269 new_hdr->tag = 0; 270#if DEBUG_FREED_MEMORY 271 new_hdr->freed = FALSE; 272#endif 273 274 new_ftr = footer_from_header(new_hdr); 275 new_ftr->magic = DEBUG_MEMORY_MAGIC; 276 277 mtx_lock(&list_mutex); 278 LIST_REPLACE(&old_hdr->head, &new_hdr->head); 279 mtx_unlock(&list_mutex); 280 281 /* copy data */ 282 new_ptr = data_from_header(new_hdr); 283 memcpy( new_ptr, old_ptr, old_size < new_size ? old_size : new_size ); 284 285 /* free old */ 286 old_hdr->magic = 0; 287 old_ftr->magic = 0; 288 os_free(old_hdr); 289 290 return new_ptr; 291} 292 293unsigned long 294debug_memory_begin(void) 295{ 296 return last_no; 297} 298 299void 300debug_memory_end(unsigned long start_no) 301{ 302 size_t total_size = 0; 303 struct list_head *entry; 304 305 if (start_no == last_no) 306 return; 307 308 entry = list.prev; 309 for (; entry != &list; entry = entry->prev) { 310 struct debug_memory_header *hdr; 311 void *ptr; 312 struct debug_memory_footer *ftr; 313 314 hdr = LIST_ENTRY(struct debug_memory_header, entry, head); 315 ptr = data_from_header(hdr); 316 ftr = footer_from_header(hdr); 317 318 if (hdr->magic != DEBUG_MEMORY_MAGIC) { 319 debug_printf("%s:%u:%s: bad or corrupted memory %p\n", 320 hdr->file, hdr->line, hdr->function, 321 ptr); 322 debug_assert(0); 323 } 324 325 if ((start_no <= hdr->no && hdr->no < last_no) || 326 (last_no < start_no && (hdr->no < last_no || start_no <= hdr->no))) { 327 debug_printf("%s:%u:%s: %lu bytes at %p not freed\n", 328 hdr->file, hdr->line, hdr->function, 329 (unsigned long) hdr->size, ptr); 330#if DEBUG_MEMORY_STACK 331 debug_backtrace_dump(hdr->backtrace, DEBUG_MEMORY_STACK); 332#endif 333 total_size += hdr->size; 334 } 335 336 if (ftr->magic != DEBUG_MEMORY_MAGIC) { 337 debug_printf("%s:%u:%s: buffer overflow %p\n", 338 hdr->file, hdr->line, hdr->function, 339 ptr); 340 debug_assert(0); 341 } 342 } 343 344 if (total_size) { 345 debug_printf("Total of %lu KB of system memory apparently leaked\n", 346 (unsigned long) (total_size + 1023)/1024); 347 } 348 else { 349 debug_printf("No memory leaks detected.\n"); 350 } 351} 352 353 354/** 355 * Put a tag (arbitrary integer) on a memory block. 356 * Can be useful for debugging. 357 */ 358void 359debug_memory_tag(void *ptr, unsigned tag) 360{ 361 struct debug_memory_header *hdr; 362 363 if (!ptr) 364 return; 365 366 hdr = header_from_data(ptr); 367 if (hdr->magic != DEBUG_MEMORY_MAGIC) { 368 debug_printf("%s corrupted memory at %p\n", __FUNCTION__, ptr); 369 debug_assert(0); 370 } 371 372 hdr->tag = tag; 373} 374 375 376/** 377 * Check the given block of memory for validity/corruption. 378 */ 379void 380debug_memory_check_block(void *ptr) 381{ 382 struct debug_memory_header *hdr; 383 struct debug_memory_footer *ftr; 384 385 if (!ptr) 386 return; 387 388 hdr = header_from_data(ptr); 389 ftr = footer_from_header(hdr); 390 391 if (hdr->magic != DEBUG_MEMORY_MAGIC) { 392 debug_printf("%s:%u:%s: bad or corrupted memory %p\n", 393 hdr->file, hdr->line, hdr->function, ptr); 394 debug_assert(0); 395 } 396 397 if (ftr->magic != DEBUG_MEMORY_MAGIC) { 398 debug_printf("%s:%u:%s: buffer overflow %p\n", 399 hdr->file, hdr->line, hdr->function, ptr); 400 debug_assert(0); 401 } 402} 403 404 405 406/** 407 * We can periodically call this from elsewhere to do a basic sanity 408 * check of the heap memory we've allocated. 409 */ 410void 411debug_memory_check(void) 412{ 413 struct list_head *entry; 414 415 entry = list.prev; 416 for (; entry != &list; entry = entry->prev) { 417 struct debug_memory_header *hdr; 418 struct debug_memory_footer *ftr; 419 const char *ptr; 420 421 hdr = LIST_ENTRY(struct debug_memory_header, entry, head); 422 ftr = footer_from_header(hdr); 423 ptr = (const char *) data_from_header(hdr); 424 425 if (hdr->magic != DEBUG_MEMORY_MAGIC) { 426 debug_printf("%s:%u:%s: bad or corrupted memory %p\n", 427 hdr->file, hdr->line, hdr->function, ptr); 428 debug_assert(0); 429 } 430 431 if (ftr->magic != DEBUG_MEMORY_MAGIC) { 432 debug_printf("%s:%u:%s: buffer overflow %p\n", 433 hdr->file, hdr->line, hdr->function, ptr); 434 debug_assert(0); 435 } 436 437#if DEBUG_FREED_MEMORY 438 /* If this block is marked as freed, check that it hasn't been touched */ 439 if (hdr->freed) { 440 int i; 441 for (i = 0; i < hdr->size; i++) { 442 if (ptr[i] != DEBUG_FREED_BYTE) { 443 debug_printf("Memory error: byte %d of block at %p of size %d is 0x%x\n", 444 i, ptr, hdr->size, ptr[i]); 445 debug_printf("Block was freed at %s:%d\n", hdr->file, hdr->line); 446 } 447 assert(ptr[i] == DEBUG_FREED_BYTE); 448 } 449 } 450#endif 451 } 452} 453