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