memtrack.c revision 324685
1/* 2 This software is available to you under a choice of one of two 3 licenses. You may choose to be licensed under the terms of the GNU 4 General Public License (GPL) Version 2, available at 5 <http://www.fsf.org/copyleft/gpl.html>, or the OpenIB.org BSD 6 license, available in the LICENSE.TXT file accompanying this 7 software. These details are also available at 8 <http://openib.org/license.html>. 9 10 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 11 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 12 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 13 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 14 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 15 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 SOFTWARE. 18 19 Copyright (c) 2004 Mellanox Technologies Ltd. All rights reserved. 20*/ 21 22#define LINUXKPI_PARAM_PREFIX memtrack_ 23 24#define C_MEMTRACK_C 25 26#ifdef kmalloc 27 #undef kmalloc 28#endif 29#ifdef kfree 30 #undef kfree 31#endif 32#ifdef vmalloc 33 #undef vmalloc 34#endif 35#ifdef vfree 36 #undef vfree 37#endif 38#ifdef kmem_cache_alloc 39 #undef kmem_cache_alloc 40#endif 41#ifdef kmem_cache_free 42 #undef kmem_cache_free 43#endif 44 45#include <linux/module.h> 46#include <linux/kernel.h> 47#include <linux/slab.h> 48#include <linux/interrupt.h> 49#include <linux/vmalloc.h> 50#include <linux/version.h> 51#include <asm/uaccess.h> 52#include <linux/proc_fs.h> 53#include <memtrack.h> 54 55#include <linux/moduleparam.h> 56 57 58MODULE_AUTHOR("Mellanox Technologies LTD."); 59MODULE_DESCRIPTION("Memory allocations tracking"); 60MODULE_LICENSE("GPL"); 61 62#define MEMTRACK_HASH_SZ ((1<<15)-19) /* prime: http://www.utm.edu/research/primes/lists/2small/0bit.html */ 63#define MAX_FILENAME_LEN 31 64 65#define memtrack_spin_lock(spl, flags) spin_lock_irqsave(spl, flags) 66#define memtrack_spin_unlock(spl, flags) spin_unlock_irqrestore(spl, flags) 67 68/* if a bit is set then the corresponding allocation is tracked. 69 bit0 corresponds to MEMTRACK_KMALLOC, bit1 corresponds to MEMTRACK_VMALLOC etc. */ 70static unsigned long track_mask = -1; /* effectively everything */ 71module_param(track_mask, ulong, 0444); 72MODULE_PARM_DESC(track_mask, "bitmask definenig what is tracked"); 73 74/* if a bit is set then the corresponding allocation is strictly tracked. 75 That is, before inserting the whole range is checked to not overlap any 76 of the allocations already in the database */ 77static unsigned long strict_track_mask = 0; /* no strict tracking */ 78module_param(strict_track_mask, ulong, 0444); 79MODULE_PARM_DESC(strict_track_mask, "bitmask which allocation requires strict tracking"); 80 81typedef struct memtrack_meminfo_st { 82 unsigned long addr; 83 unsigned long size; 84 unsigned long line_num; 85 struct memtrack_meminfo_st *next; 86 struct list_head list; /* used to link all items from a certain type together */ 87 char filename[MAX_FILENAME_LEN + 1]; /* putting the char array last is better for struct. packing */ 88} memtrack_meminfo_t; 89 90static struct kmem_cache *meminfo_cache; 91 92typedef struct { 93 memtrack_meminfo_t *mem_hash[MEMTRACK_HASH_SZ]; 94 spinlock_t hash_lock; 95 unsigned long count; /* size of memory tracked (*malloc) or number of objects tracked */ 96 struct list_head tracked_objs_head; /* head of list of all objects */ 97 int strict_track; /* if 1 then for each object inserted check if it overlaps any of the objects already in the list */ 98} tracked_obj_desc_t; 99 100static tracked_obj_desc_t *tracked_objs_arr[MEMTRACK_NUM_OF_MEMTYPES]; 101 102static const char *rsc_names[MEMTRACK_NUM_OF_MEMTYPES] = { 103 "kmalloc", 104 "vmalloc", 105 "kmem_cache_alloc" 106}; 107 108 109static const char *rsc_free_names[MEMTRACK_NUM_OF_MEMTYPES] = { 110 "kfree", 111 "vfree", 112 "kmem_cache_free" 113}; 114 115 116static inline const char *memtype_alloc_str(memtrack_memtype_t memtype) 117{ 118 switch (memtype) { 119 case MEMTRACK_KMALLOC: 120 case MEMTRACK_VMALLOC: 121 case MEMTRACK_KMEM_OBJ: 122 return rsc_names[memtype]; 123 default: 124 return "(Unknown allocation type)"; 125 } 126} 127 128static inline const char *memtype_free_str(memtrack_memtype_t memtype) 129{ 130 switch (memtype) { 131 case MEMTRACK_KMALLOC: 132 case MEMTRACK_VMALLOC: 133 case MEMTRACK_KMEM_OBJ: 134 return rsc_free_names[memtype]; 135 default: 136 return "(Unknown allocation type)"; 137 } 138} 139 140/* 141 * overlap_a_b 142 */ 143static int overlap_a_b(unsigned long a_start, unsigned long a_end, 144 unsigned long b_start, unsigned long b_end) 145{ 146 if ((b_start > a_end) || (a_start > b_end)) { 147 return 0; 148 } 149 return 1; 150} 151 152/* 153 * check_overlap 154 */ 155static void check_overlap(memtrack_memtype_t memtype, 156 memtrack_meminfo_t * mem_info_p, 157 tracked_obj_desc_t * obj_desc_p) 158{ 159 struct list_head *pos, *next; 160 memtrack_meminfo_t *cur; 161 unsigned long start_a, end_a, start_b, end_b; 162 163 list_for_each_safe(pos, next, &obj_desc_p->tracked_objs_head) { 164 cur = list_entry(pos, memtrack_meminfo_t, list); 165 166 start_a = mem_info_p->addr; 167 end_a = mem_info_p->addr + mem_info_p->size - 1; 168 start_b = cur->addr; 169 end_b = cur->addr + cur->size - 1; 170 171 if (overlap_a_b(start_a, end_a, start_b, end_b)) { 172 printk 173 ("%s overlaps! new_start=0x%lx, new_end=0x%lx, item_start=0x%lx, item_end=0x%lx\n", 174 memtype_alloc_str(memtype), mem_info_p->addr, 175 mem_info_p->addr + mem_info_p->size - 1, cur->addr, 176 cur->addr + cur->size - 1); 177 } 178 } 179} 180 181/* Invoke on memory allocation */ 182void memtrack_alloc(memtrack_memtype_t memtype, unsigned long addr, 183 unsigned long size, const char *filename, 184 const unsigned long line_num, int alloc_flags) 185{ 186 unsigned long hash_val; 187 memtrack_meminfo_t *cur_mem_info_p, *new_mem_info_p; 188 tracked_obj_desc_t *obj_desc_p; 189 unsigned long flags; 190 191 if (memtype >= MEMTRACK_NUM_OF_MEMTYPES) { 192 printk("%s: Invalid memory type (%d)\n", __func__, memtype); 193 return; 194 } 195 196 if (!tracked_objs_arr[memtype]) { 197 /* object is not tracked */ 198 return; 199 } 200 obj_desc_p = tracked_objs_arr[memtype]; 201 202 hash_val = addr % MEMTRACK_HASH_SZ; 203 204 new_mem_info_p = (memtrack_meminfo_t *) 205 kmem_cache_alloc(meminfo_cache, alloc_flags); 206 if (new_mem_info_p == NULL) { 207 printk 208 ("%s: Failed allocating kmem_cache item for new mem_info. " 209 "Lost tracking on allocation at %s:%lu...\n", __func__, 210 filename, line_num); 211 return; 212 } 213 /* save allocation properties */ 214 new_mem_info_p->addr = addr; 215 new_mem_info_p->size = size; 216 new_mem_info_p->line_num = line_num; 217 /* Make sure that we will print out the path tail if the given filename is longer 218 * than MAX_FILENAME_LEN. (otherwise, we will not see the name of the actual file 219 * in the printout -- only the path head! 220 */ 221 if (strlen(filename) > MAX_FILENAME_LEN) { 222 strncpy(new_mem_info_p->filename, filename + strlen(filename) - MAX_FILENAME_LEN, MAX_FILENAME_LEN); 223 } else { 224 strncpy(new_mem_info_p->filename, filename, MAX_FILENAME_LEN); 225 } 226 new_mem_info_p->filename[MAX_FILENAME_LEN] = 0; /* NULL terminate anyway */ 227 228 memtrack_spin_lock(&obj_desc_p->hash_lock, flags); 229 /* make sure given memory location is not already allocated */ 230 cur_mem_info_p = obj_desc_p->mem_hash[hash_val]; 231 while (cur_mem_info_p != NULL) { 232 if (cur_mem_info_p->addr == addr) { 233 /* Found given address in the database */ 234 printk 235 ("mtl rsc inconsistency: %s: %s::%lu: %s @ addr=0x%lX which is already known from %s:%lu\n", 236 __func__, filename, line_num, 237 memtype_alloc_str(memtype), addr, 238 cur_mem_info_p->filename, 239 cur_mem_info_p->line_num); 240 memtrack_spin_unlock(&obj_desc_p->hash_lock, flags); 241 kmem_cache_free(meminfo_cache, new_mem_info_p); 242 return; 243 } 244 cur_mem_info_p = cur_mem_info_p->next; 245 } 246 /* not found - we can put in the hash bucket */ 247 /* link as first */ 248 new_mem_info_p->next = obj_desc_p->mem_hash[hash_val]; 249 obj_desc_p->mem_hash[hash_val] = new_mem_info_p; 250 if (obj_desc_p->strict_track) { 251 check_overlap(memtype, new_mem_info_p, obj_desc_p); 252 } 253 obj_desc_p->count += size; 254 list_add(&new_mem_info_p->list, &obj_desc_p->tracked_objs_head); 255 256 memtrack_spin_unlock(&obj_desc_p->hash_lock, flags); 257 return; 258} 259 260/* Invoke on memory free */ 261void memtrack_free(memtrack_memtype_t memtype, unsigned long addr, 262 const char *filename, const unsigned long line_num) 263{ 264 unsigned long hash_val; 265 memtrack_meminfo_t *cur_mem_info_p, *prev_mem_info_p; 266 tracked_obj_desc_t *obj_desc_p; 267 unsigned long flags; 268 269 if (memtype >= MEMTRACK_NUM_OF_MEMTYPES) { 270 printk("%s: Invalid memory type (%d)\n", __func__, memtype); 271 return; 272 } 273 274 if (!tracked_objs_arr[memtype]) { 275 /* object is not tracked */ 276 return; 277 } 278 obj_desc_p = tracked_objs_arr[memtype]; 279 280 hash_val = addr % MEMTRACK_HASH_SZ; 281 282 memtrack_spin_lock(&obj_desc_p->hash_lock, flags); 283 /* find mem_info of given memory location */ 284 prev_mem_info_p = NULL; 285 cur_mem_info_p = obj_desc_p->mem_hash[hash_val]; 286 while (cur_mem_info_p != NULL) { 287 if (cur_mem_info_p->addr == addr) { 288 /* Found given address in the database - remove from the bucket/list */ 289 if (prev_mem_info_p == NULL) { 290 obj_desc_p->mem_hash[hash_val] = cur_mem_info_p->next; /* removing first */ 291 } else { 292 prev_mem_info_p->next = cur_mem_info_p->next; /* "crossover" */ 293 } 294 list_del(&cur_mem_info_p->list); 295 296 obj_desc_p->count -= cur_mem_info_p->size; 297 memtrack_spin_unlock(&obj_desc_p->hash_lock, flags); 298 kmem_cache_free(meminfo_cache, cur_mem_info_p); 299 return; 300 } 301 prev_mem_info_p = cur_mem_info_p; 302 cur_mem_info_p = cur_mem_info_p->next; 303 } 304 305 /* not found */ 306 printk 307 ("mtl rsc inconsistency: %s: %s::%lu: %s for unknown address=0x%lX\n", 308 __func__, filename, line_num, memtype_free_str(memtype), addr); 309 memtrack_spin_unlock(&obj_desc_p->hash_lock, flags); 310 return; 311} 312 313/* Report current allocations status (for all memory types) */ 314static void memtrack_report(void) 315{ 316 memtrack_memtype_t memtype; 317 unsigned long cur_bucket; 318 memtrack_meminfo_t *cur_mem_info_p; 319 int serial = 1; 320 tracked_obj_desc_t *obj_desc_p; 321 unsigned long flags; 322 323 printk("%s: Currently known allocations:\n", __func__); 324 for (memtype = 0; memtype < MEMTRACK_NUM_OF_MEMTYPES; memtype++) { 325 if (tracked_objs_arr[memtype]) { 326 printk("%d) %s:\n", serial, memtype_alloc_str(memtype)); 327 obj_desc_p = tracked_objs_arr[memtype]; 328 /* Scan all buckets to find existing allocations */ 329 /* TBD: this may be optimized by holding a linked list of all hash items */ 330 for (cur_bucket = 0; cur_bucket < MEMTRACK_HASH_SZ; 331 cur_bucket++) { 332 memtrack_spin_lock(&obj_desc_p->hash_lock, flags); /* protect per bucket/list */ 333 cur_mem_info_p = 334 obj_desc_p->mem_hash[cur_bucket]; 335 while (cur_mem_info_p != NULL) { /* scan bucket */ 336 printk("%s::%lu: %s(%lu)==%lX\n", 337 cur_mem_info_p->filename, 338 cur_mem_info_p->line_num, 339 memtype_alloc_str(memtype), 340 cur_mem_info_p->size, 341 cur_mem_info_p->addr); 342 cur_mem_info_p = cur_mem_info_p->next; 343 } /* while cur_mem_info_p */ 344 memtrack_spin_unlock(&obj_desc_p->hash_lock, flags); 345 } /* for cur_bucket */ 346 serial++; 347 } 348 } /* for memtype */ 349} 350 351 352 353static struct proc_dir_entry *memtrack_tree; 354 355static memtrack_memtype_t get_rsc_by_name(const char *name) 356{ 357 memtrack_memtype_t i; 358 359 for (i=0; i<MEMTRACK_NUM_OF_MEMTYPES; ++i) { 360 if (strcmp(name, rsc_names[i]) == 0) { 361 return i; 362 } 363 } 364 365 return i; 366} 367 368 369static ssize_t memtrack_read(struct file *filp, 370 char __user *buf, 371 size_t size, 372 loff_t *offset) 373{ 374 unsigned long cur, flags; 375 loff_t pos = *offset; 376 static char kbuf[20]; 377 static int file_len; 378 int _read, to_ret, left; 379 const char *fname; 380 memtrack_memtype_t memtype; 381 382 if (pos < 0) 383 return -EINVAL; 384 385 fname= filp->f_dentry->d_name.name; 386 387 memtype= get_rsc_by_name(fname); 388 if (memtype >= MEMTRACK_NUM_OF_MEMTYPES) { 389 printk("invalid file name\n"); 390 return -EINVAL; 391 } 392 393 if ( pos == 0 ) { 394 memtrack_spin_lock(&tracked_objs_arr[memtype]->hash_lock, flags); 395 cur= tracked_objs_arr[memtype]->count; 396 memtrack_spin_unlock(&tracked_objs_arr[memtype]->hash_lock, flags); 397 _read = sprintf(kbuf, "%lu\n", cur); 398 if ( _read < 0 ) { 399 return _read; 400 } 401 else { 402 file_len = _read; 403 } 404 } 405 406 left = file_len - pos; 407 to_ret = (left < size) ? left : size; 408 if ( copy_to_user(buf, kbuf+pos, to_ret) ) { 409 return -EFAULT; 410 } 411 else { 412 *offset = pos + to_ret; 413 return to_ret; 414 } 415} 416 417static struct file_operations memtrack_proc_fops = { 418 .read = memtrack_read, 419}; 420 421static const char *memtrack_proc_entry_name = "mt_memtrack"; 422 423static int create_procfs_tree(void) 424{ 425 struct proc_dir_entry *dir_ent; 426 struct proc_dir_entry *proc_ent; 427 int i, j; 428 unsigned long bit_mask; 429 430 dir_ent = proc_mkdir(memtrack_proc_entry_name, NULL); 431 if ( !dir_ent ) { 432 return -1; 433 } 434 435 memtrack_tree = dir_ent; 436 437 for (i=0, bit_mask=1; i<MEMTRACK_NUM_OF_MEMTYPES; ++i, bit_mask<<=1) { 438 if (bit_mask & track_mask) { 439 proc_ent = create_proc_entry(rsc_names[i], S_IRUGO, memtrack_tree); 440 if ( !proc_ent ) 441 goto undo_create_root; 442 443 proc_ent->proc_fops = &memtrack_proc_fops; 444 } 445 } 446 447 goto exit_ok; 448 449undo_create_root: 450 for (j=0, bit_mask=1; j<i; ++j, bit_mask<<=1) { 451 if (bit_mask & track_mask) { 452 remove_proc_entry(rsc_names[j], memtrack_tree); 453 } 454 } 455 remove_proc_entry(memtrack_proc_entry_name, NULL); 456 return -1; 457 458exit_ok: 459 return 0; 460} 461 462 463static void destroy_procfs_tree(void) 464{ 465 int i; 466 unsigned long bit_mask; 467 468 for (i=0, bit_mask=1; i<MEMTRACK_NUM_OF_MEMTYPES; ++i, bit_mask<<=1) { 469 if (bit_mask & track_mask) { 470 remove_proc_entry(rsc_names[i], memtrack_tree); 471 } 472 } 473 remove_proc_entry(memtrack_proc_entry_name, NULL); 474} 475 476 477/* module entry points */ 478 479int init_module(void) 480{ 481 memtrack_memtype_t i; 482 int j; 483 unsigned long bit_mask; 484 485 486 /* create a cache for the memtrack_meminfo_t strcutures */ 487 meminfo_cache = kmem_cache_create("memtrack_meminfo_t", 488 sizeof(memtrack_meminfo_t), 0, 489 SLAB_HWCACHE_ALIGN, NULL); 490 if (!meminfo_cache) { 491 printk("memtrack::%s: failed to allocate meminfo cache\n", __func__); 492 return -1; 493 } 494 495 /* initialize array of descriptors */ 496 memset(tracked_objs_arr, 0, sizeof(tracked_objs_arr)); 497 498 /* create a tracking object descriptor for all required objects */ 499 for (i = 0, bit_mask = 1; i < MEMTRACK_NUM_OF_MEMTYPES; 500 ++i, bit_mask <<= 1) { 501 if (bit_mask & track_mask) { 502 tracked_objs_arr[i] = 503 vmalloc(sizeof(tracked_obj_desc_t)); 504 if (!tracked_objs_arr[i]) { 505 printk("memtrack: failed to allocate tracking object\n"); 506 goto undo_cache_create; 507 } 508 509 memset(tracked_objs_arr[i], 0, sizeof(tracked_obj_desc_t)); 510 spin_lock_init(&tracked_objs_arr[i]->hash_lock); 511 INIT_LIST_HEAD(&tracked_objs_arr[i]->tracked_objs_head); 512 if (bit_mask & strict_track_mask) { 513 tracked_objs_arr[i]->strict_track = 1; 514 } else { 515 tracked_objs_arr[i]->strict_track = 0; 516 } 517 } 518 } 519 520 521 if ( create_procfs_tree() ) { 522 printk("%s: create_procfs_tree() failed\n", __FILE__); 523 goto undo_cache_create; 524 } 525 526 527 printk("memtrack::%s done.\n", __func__); 528 529 return 0; 530 531undo_cache_create: 532 for (j=0; j<i; ++j) { 533 if (tracked_objs_arr[j]) { 534 vfree(tracked_objs_arr[j]); 535 } 536 } 537 538#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) 539 if (kmem_cache_destroy(meminfo_cache) != 0) { 540 printk("Failed on kmem_cache_destroy !\n"); 541 } 542#else 543 kmem_cache_destroy(meminfo_cache); 544#endif 545 return -1; 546} 547 548 549void cleanup_module(void) 550{ 551 memtrack_memtype_t memtype; 552 unsigned long cur_bucket; 553 memtrack_meminfo_t *cur_mem_info_p, *next_mem_info_p; 554 tracked_obj_desc_t *obj_desc_p; 555 unsigned long flags; 556 557 558 memtrack_report(); 559 560 561 destroy_procfs_tree(); 562 563 /* clean up any hash table left-overs */ 564 for (memtype = 0; memtype < MEMTRACK_NUM_OF_MEMTYPES; memtype++) { 565 /* Scan all buckets to find existing allocations */ 566 /* TBD: this may be optimized by holding a linked list of all hash items */ 567 if (tracked_objs_arr[memtype]) { 568 obj_desc_p = tracked_objs_arr[memtype]; 569 for (cur_bucket = 0; cur_bucket < MEMTRACK_HASH_SZ; 570 cur_bucket++) { 571 memtrack_spin_lock(&obj_desc_p->hash_lock, flags); /* protect per bucket/list */ 572 cur_mem_info_p = 573 obj_desc_p->mem_hash[cur_bucket]; 574 while (cur_mem_info_p != NULL) { /* scan bucket */ 575 next_mem_info_p = cur_mem_info_p->next; /* save "next" pointer before the "free" */ 576 kmem_cache_free(meminfo_cache, 577 cur_mem_info_p); 578 cur_mem_info_p = next_mem_info_p; 579 } /* while cur_mem_info_p */ 580 memtrack_spin_unlock(&obj_desc_p->hash_lock, flags); 581 } /* for cur_bucket */ 582 vfree(obj_desc_p); 583 } 584 } /* for memtype */ 585 586#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) 587 if (kmem_cache_destroy(meminfo_cache) != 0) { 588 printk 589 ("memtrack::cleanup_module: Failed on kmem_cache_destroy !\n"); 590 } 591#else 592 kmem_cache_destroy(meminfo_cache); 593#endif 594 printk("memtrack::cleanup_module done.\n"); 595} 596 597EXPORT_SYMBOL(memtrack_alloc); 598EXPORT_SYMBOL(memtrack_free); 599 600//module_init(memtrack_init) 601//module_exit(memtrack_exit) 602 603