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