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