Update License
[platform/framework/web/heap-monitor.git] / src / heap-monitor.c
1 /*
2  * Copyright 2013  Samsung Electronics Co., Ltd
3  *
4  * Licensed under the Flora License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://floralicense.org
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #define _GNU_SOURCE
18 #include <libgen.h>
19 #include <stdio.h>
20 #include <malloc.h>
21 #include <errno.h>
22 #include <dlfcn.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <sys/time.h>
27 #include <pthread.h>
28 #include <link.h>
29 #include <elf.h>
30 #include <mcheck.h>
31
32 #include <dlog.h>
33
34 #include "dlist.h"
35 #include "heap-monitor.h"
36
37 #if !defined(FLOG)
38 #define DbgPrint(format, arg...)        LOGD("[\e[32m%s/%s\e[0m:%d] " format, basename(__FILE__), __func__, __LINE__, ##arg)
39 #define ErrPrint(format, arg...)        LOGE("[\e[32m%s/%s\e[0m:%d] " format, basename(__FILE__), __func__, __LINE__, ##arg)
40 #else
41 extern FILE *__file_log_fp;
42 #define DbgPrint(format, arg...) do { fprintf(__file_log_fp, "[LOG] [\e[32m%s/%s\e[0m:%d] " format, basename(__FILE__), __func__, __LINE__, ##arg); fflush(__file_log_fp); } while (0)
43
44 #define ErrPrint(format, arg...) do { fprintf(__file_log_fp, "[ERR] [\e[32m%s/%s\e[0m:%d] " format, basename(__FILE__), __func__, __LINE__, ##arg); fflush(__file_log_fp); } while (0)
45 #endif
46 /* End of a file */
47
48 #define container_of(ptr, type, member) \
49         ({ const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
50         (type *)( (char *)__mptr - offsetof(type,member) );})
51
52 #define API __attribute__((visibility("default")))
53 #define DUMP_DEPTH 80000
54
55 #if defined(_ESTIMATE_PERFORMANCE)
56 #define ESTIMATE_START() \
57 do { \
58         struct timeval stv; \
59         struct timeval etv; \
60         struct timeval rtv; \
61         static struct timeval ttv; \
62         static int initialized = 0; \
63         if (!initialized) { \
64                 timerclear(&ttv); \
65                 initialized = 1; \
66         } \
67         gettimeofday(&stv, NULL)
68
69 #define ESTIMATE_END() \
70         gettimeofday(&etv, NULL); \
71         gettimeofday(&etv, NULL); \
72         timersub(&etv, &stv, &rtv); \
73         timeradd(&rtv, &ttv, &ttv); \
74         DbgPrint("Elapsed time: %lu.%lf\n", rtv.tv_sec, (double)rtv.tv_usec / 1000000.0f); \
75         DbgPrint("Total: %lu.%lf\n", ttv.tv_sec, (double)ttv.tv_usec / 1000000.0f); \
76 } while (0)
77 #else
78 #define ESTIMATE_START() // pthread_mutex_lock(&s_info.mutex)
79 #define ESTIMATE_END() // pthread_mutex_unlock(&s_info.mutex)
80 #endif
81
82 #if defined(_THREAD_SAFETY)
83 #define LOCK()  pthread_mutex_lock(&s_info.mutex)
84 #define UNLOCK() pthread_mutex_unlock(&s_info.mutex)
85 #else
86 #define LOCK()
87 #define UNLOCK()
88 #endif
89
90 typedef void *(*malloc_t)(size_t size, const void *caller);
91 typedef void *(*realloc_t)(void *ptr, size_t size, const void *caller);
92 typedef void (*free_t)(void *ptr, const void *caller);
93 typedef void *(*memalign_t)(size_t align, size_t size, const void *caller);
94
95 int errno;
96
97 struct chunk {
98         struct target *info;
99         int size;
100         int pad;
101         enum {
102                 VALID = 0xbeefbeef,
103                 INVALID = 0xdeaddead,
104         } state;
105         char data[];
106 };
107
108 #define SET_PAD(ptr, pad) (*(((int *)ptr) - 2) = (pad))
109 #define SET_STATE(ptr, state) (*(((int *)ptr) - 1) = (state))
110 #define PAD(ptr) (*(((int *)ptr) - 2))
111 #define STATE(ptr) (*(((int *)ptr) - 1))
112
113 struct target {
114         char *name;
115         int usage;
116         unsigned long begin;
117         unsigned long end;
118 };
119
120 static struct {
121         malloc_t _malloc;
122         realloc_t _realloc;
123         free_t _free;
124         memalign_t _memalign;
125         struct dlist *target_list;
126         int hook;
127         int dump_depth;
128         unsigned long stack_boundary;
129         unsigned long stack_base;
130         size_t stack_size;
131         pthread_mutex_t mutex;
132         int debugger;
133         int target_cnt;
134         pthread_t main_tid;
135         int initialized;
136         int m_check;
137 } s_info = {
138         ._malloc = NULL,
139         ._realloc = NULL,
140         ._free = NULL,
141         ._memalign = NULL,
142         .target_list = NULL,
143         .hook = 0,
144         .dump_depth = DUMP_DEPTH,
145         .stack_boundary = 0,
146         .mutex = PTHREAD_MUTEX_INITIALIZER,
147         .debugger = 0,
148         .target_cnt = 0,
149         .initialized = 0,
150         .m_check = 0,
151 };
152
153 static void unhook(void);
154 static void hook(void);
155
156 static inline struct target *find_target_by_name(const char *name)
157 {
158         struct dlist *l;
159         struct target *target;
160
161         dlist_foreach(s_info.target_list, l, target) {
162                 if (!strcmp(target->name, name))
163                         return target;
164         }
165
166         return NULL;
167 }
168
169 static inline struct target *find_target_by_addr(unsigned long addr)
170 {
171         struct dlist *l;
172         struct target *target;
173
174         dlist_foreach(s_info.target_list, l, target) {
175                 if (target->begin <= addr && addr < target->end)
176                         return target;
177         }
178
179         return NULL;
180 }
181
182 /*!
183  * \node deprecated
184    Dl_info info;
185    ret = dladdr((void *)*stack, &info);
186    if (!ret)
187    continue;
188
189    target = find_target_by_name(info.dli_fname);
190 */
191 static struct target *find_target_info(void)
192 {
193         int ret;
194         register struct target *target;
195         register unsigned long *stack;
196         register int i;
197
198         if (s_info.target_cnt <= 0)
199                 return NULL;
200
201         stack = (unsigned long *)&ret;
202
203         if (s_info.stack_boundary) {
204                 pthread_t tid;
205                 unsigned long base;
206                 unsigned long boundary;
207
208                 tid = pthread_self();
209                 if (!pthread_equal(tid, s_info.main_tid)) {
210                         pthread_attr_t attr;
211                         size_t size = 0;
212                         base = 0;
213                         boundary = 0;
214                         if (!pthread_getattr_np(tid, &attr)) {
215                                 if (!pthread_attr_getstack(&attr, (void *)&base, &size))
216                                         boundary = base + size;
217                                 pthread_attr_destroy(&attr);
218                         }
219                 } else {
220                         base = s_info.stack_base;
221                         boundary = s_info.stack_boundary;
222                 }
223
224                 i = 0;
225                 while ((unsigned long)stack >= base && (unsigned long)stack < boundary) {
226                         i++;
227                         if (s_info.debugger) {
228                                 DbgPrint("FP: %p, boundary: %p, INDEX: %d, ret: 0x%X\n",
229                                                                 stack, boundary, i, *stack);
230                         }
231
232                         target = find_target_by_addr(*stack++);
233                         if (!target)
234                                 continue;
235
236                         if (s_info.debugger)
237                                 DbgPrint("Target[%s]: %d\n", target->name, target->usage);
238
239                         return target;
240                 }
241         } else {
242                 for (i = 0; i < s_info.dump_depth; i++) {
243                         target = find_target_by_addr(*(stack++));
244                         if (!target)
245                                 continue;
246
247                         if (s_info.debugger)
248                                 DbgPrint("[%d] Target[%s]: %d\n", i, target->name, target->usage);
249
250                         return target;
251                 }
252         }
253
254         return NULL;
255 }
256
257 static void mcheck_cb(enum mcheck_status status)
258 {
259         struct target *target;
260         switch (status) {
261         case MCHECK_DISABLED:
262                 ErrPrint("mcheck is disabled\n");
263                 break;
264         case MCHECK_OK:
265                 ErrPrint("Consitency is ok\n");
266                 break;
267         case MCHECK_HEAD:
268                 target = find_target_info();
269                 if (target)
270                         ErrPrint("[HEAD] Inconsistency: %s\n", target->name);
271                 break;
272         case MCHECK_TAIL:
273                 target = find_target_info();
274                 if (target)
275                         ErrPrint("[HEAD] Inconsistency: %s\n", target->name);
276                 break;
277         case MCHECK_FREE:
278                 target = find_target_info();
279                 if (target)
280                         ErrPrint("[FREE] Inconsistency: %s\n", target->name);
281                 break;
282         default:
283                 break;
284         }
285 }
286
287 static inline int MPROBE(void *ptr)
288 {
289         enum mcheck_status status;
290
291         status = mprobe(ptr);
292         if (status == MCHECK_DISABLED)
293                 return 1;
294
295         if (status != MCHECK_OK) {
296                 mcheck_cb(status);
297                 return 0;
298         }
299
300         return 1;
301 }
302
303 static void *heap_monitor_malloc(size_t size, const void *caller)
304 {
305         struct chunk *chunk;
306         void *ptr = NULL;
307
308         LOCK();
309         unhook();
310         ESTIMATE_START();
311         chunk = malloc(size + sizeof(*chunk));
312         if (chunk) {
313                 chunk->info = find_target_info();
314                 chunk->size = size;
315
316                 ptr = chunk->data;
317                 SET_PAD(ptr, 0);
318                 SET_STATE(ptr, VALID);
319
320                 if (chunk->info)
321                         chunk->info->usage += size;
322         }
323
324         ESTIMATE_END();
325         hook();
326         UNLOCK();
327         return ptr;
328 }
329
330 static void heap_monitor_free(void *ptr, const void *caller)
331 {
332         struct chunk *chunk;
333
334         if (!ptr)
335                 return;
336
337         LOCK();
338         unhook();
339         ESTIMATE_START();
340
341         if (STATE(ptr) != VALID) {
342                 DbgPrint("Unrecognizable chunk, do default operation\n");
343                 chunk = ptr;
344                 goto out;
345         }
346
347         chunk = container_of((void *)(((char *)ptr) - PAD(ptr)), struct chunk, data);
348         if (chunk->info)
349                 chunk->info->usage -= chunk->size;
350
351         if (MPROBE(chunk)) {
352                 SET_STATE(ptr, INVALID);
353         } else {
354                 chunk = ptr;
355                 if (MPROBE(chunk)) {
356                         DbgPrint("Successfully recovered\n");
357                 } else {
358                         ErrPrint("Failed to recover\n");
359                         chunk = NULL; /* Do nothing */
360                         goto out;
361                 }
362         }
363
364 out:
365         free(chunk);
366         ESTIMATE_END();
367         hook();
368         UNLOCK();
369         return;
370 }
371
372 static void *heap_monitor_realloc(void *__ptr, size_t size, const void *caller)
373 {
374         void *ptr = NULL;
375         struct chunk *chunk;
376
377         LOCK();
378         unhook();
379         ESTIMATE_START();
380         if (!__ptr) {
381                 if (!size)
382                         goto out;
383
384                 /* Allocation */
385                 chunk = realloc(__ptr, size + sizeof(*chunk));
386                 if (chunk) {
387                         chunk->info = find_target_info();
388                         chunk->size = size;
389                         ptr = chunk->data;
390
391                         SET_PAD(ptr, 0);
392                         SET_STATE(ptr, VALID);
393
394                         if (chunk->info)
395                                 chunk->info->usage += size;
396                 }
397         } else if (size == 0) {
398                 /* Free */
399                 if (STATE(__ptr) != VALID) {
400                         DbgPrint("Unrecognizable chunk, do default operation\n");
401                         ptr = realloc(__ptr, size);
402                 } else {
403                         chunk = container_of((void *)(((char *)__ptr) - PAD(__ptr)), struct chunk, data);
404                         if (chunk->info)
405                                 chunk->info->usage -= chunk->size;
406
407                         if (MPROBE(chunk)) {
408                                 SET_STATE(__ptr, INVALID);
409                         } else {
410                                 chunk = __ptr;
411                                 if (MPROBE(chunk)) {
412                                         DbgPrint("Successfully recovered\n");
413                                 } else {
414                                         ErrPrint("Failed to recover\n");
415                                         ptr = NULL;
416                                         goto out;
417                                 }
418                         }
419
420                         ptr = realloc(chunk, size);
421                 }
422         } else {
423                 if (STATE(__ptr) != VALID) {
424                         DbgPrint("Unrecognizable chunk, do default operation\n");
425                         ptr = realloc(__ptr, size);
426                 } else {
427                         int pad = PAD(__ptr);
428
429                         chunk = container_of((void *)(((char *)__ptr) - pad), struct chunk, data);
430                         if (MPROBE(chunk)) {
431                                 ptr = realloc(chunk, size + sizeof(*chunk) + pad);
432                                 if (ptr) {
433                                         struct chunk *new_chunk = ptr;
434
435                                         if (new_chunk->info) {
436                                                 new_chunk->info->usage -= new_chunk->size;
437                                                 new_chunk->info->usage += size;
438                                         }
439                                         new_chunk->size = size;
440                                         ptr = new_chunk->data + pad;
441
442                                         SET_STATE(ptr, VALID);
443                                         SET_PAD(ptr, pad);
444                                         /* Consider this, do we need to keep the alignment for realloc? */
445                                 }
446                         } else {
447                                 chunk = __ptr;
448                                 if (!MPROBE(chunk)) {
449                                         ErrPrint("Failed to recover\n");
450                                         ptr = NULL;
451                                         goto out;
452                                 }
453
454                                 ptr = realloc(chunk, size + sizeof(*chunk));
455                                 if (ptr) {
456                                         void *tmp;
457                                         struct chunk *new_chunk;
458
459                                         tmp = malloc(size);
460                                         if (!tmp) {
461                                                 ErrPrint("Heap: %s\n", strerror(errno));
462                                                 ptr = NULL;
463                                                 goto out;
464                                         }
465
466                                         memcpy(tmp, ptr, size);
467                                         memcpy(((char *)ptr) + sizeof(*chunk), tmp, size);
468                                         free(tmp);
469
470                                         new_chunk = ptr;
471                                         new_chunk->info = find_target_info();
472                                         if (new_chunk->info)
473                                                 new_chunk->info->usage += size;
474                                         new_chunk->size = size;
475                                         ptr = new_chunk->data;
476                                         SET_STATE(ptr, VALID);
477                                         SET_PAD(ptr, 0);
478
479                                         DbgPrint("Successfully recovered\n");
480                                 } else {
481                                         DbgPrint("Failed to recover\n");
482                                 }
483                         }
484                 }
485         }
486
487 out:
488         ESTIMATE_END();
489         hook();
490         UNLOCK();
491         return ptr;
492 }
493
494 static void *heap_monitor_memalign(size_t align, size_t __size, const void *caller)
495 {
496         void *ptr = NULL;
497         struct chunk *chunk;
498         int pad;
499         int size;
500
501         if (!__size)
502                 return NULL;
503
504         LOCK();
505         unhook();
506         ESTIMATE_START();
507
508         pad = align - (sizeof(*chunk) % align);
509         size = sizeof(*chunk) + pad;
510         chunk = memalign(align, size + __size);
511         if (!chunk)
512                 goto out;
513
514         chunk->info = find_target_info();
515         if (chunk->info)
516                 chunk->info->usage += __size;
517
518         chunk->size = __size;
519
520         ptr = chunk->data + pad;
521         SET_PAD(ptr, pad);
522         SET_STATE(ptr, VALID);
523
524 out:
525         ESTIMATE_END();
526         hook();
527         UNLOCK();
528         return ptr;
529 }
530
531 static void hook(void)
532 {
533         s_info.hook++;
534         if (s_info.hook == 1) {
535                 __malloc_hook = heap_monitor_malloc;
536                 __realloc_hook = heap_monitor_realloc;
537                 __memalign_hook = heap_monitor_memalign;
538                 __free_hook = heap_monitor_free;
539         }
540 }
541
542 static void unhook(void)
543 {
544         s_info.hook--;
545         if (s_info.hook == 0) {
546                 __malloc_hook = s_info._malloc;
547                 __realloc_hook = s_info._realloc;
548                 __memalign_hook = s_info._memalign;
549                 __free_hook = s_info._free;
550         }
551 }
552
553 void (*__malloc_initialize_hook)(void) = heap_monitor_init;
554
555 static int iterator_cb(struct dl_phdr_info *info, size_t size, void *data)
556 {
557         struct target *target;
558         register int i;
559         const ElfW(Phdr) *phdr;
560
561         target = data;
562
563         if (strcmp(info->dlpi_name, target->name))
564                 return 0;
565
566         for (i = 0; i < info->dlpi_phnum; i++) {
567                 phdr = info->dlpi_phdr + i;
568                 if (phdr->p_type == PT_LOAD && (phdr->p_flags & PF_X)) {
569                         target->begin = info->dlpi_addr + (unsigned long)phdr->p_vaddr;
570                         target->end = target->begin + phdr->p_memsz;
571                         if (s_info.debugger)
572                                 DbgPrint("Target: %s - [%s] 0x%lX - 0x%lX\n",
573                                                         target->name, info->dlpi_name, target->begin, target->end);
574                         break;
575                 }
576         }
577
578         return 1;
579 }
580
581 API int heap_monitor_del_target(const char *name)
582 {
583         struct dlist *l;
584         struct dlist *n;
585         struct target *target;
586         int ret;
587
588         LOCK();
589         unhook();
590         ret = -ENOENT;
591         dlist_foreach_safe(s_info.target_list, l, n, target) {
592                 if (!strcmp(target->name, name)) {
593                         s_info.target_list = dlist_remove(s_info.target_list, l);
594                         free(target->name);
595                         free(target);
596                         s_info.target_cnt--;
597                         ret = 0;
598                         break;
599                 }
600         }
601         hook();
602         UNLOCK();
603
604         return ret;
605 }
606
607 API int heap_monitor_add_target(const char *name)
608 {
609         struct target *target;
610         int ret;
611
612         LOCK();
613         unhook();
614
615         ret = 0;
616         target = find_target_by_name(name);
617         if (target)
618                 goto out;
619
620         target = malloc(sizeof(*target));
621         if (!target) {
622                 DbgPrint("Heap: %s\n", strerror(errno));
623                 ret = -ENOMEM;
624                 goto out;
625         }
626
627         target->name = strdup(name);
628         if (!target->name) {
629                 DbgPrint("Heap: %s\n", strerror(errno));
630                 free(target);
631                 ret = -ENOMEM;
632                 goto out;
633         }
634
635         target->usage = 0;
636         target->begin = 0;
637         target->end = 0;
638
639         dl_iterate_phdr(iterator_cb, target);
640
641         s_info.target_cnt++;
642         s_info.target_list = dlist_append(s_info.target_list, target);
643 out:
644         hook();
645         UNLOCK();
646         return ret;
647 }
648
649 API size_t heap_monitor_target_usage(const char *name)
650 {
651         struct target *target;
652         LOCK();
653         unhook();
654         target = find_target_by_name(name);
655         hook();
656         UNLOCK();
657         return target ? target->usage : 0;
658 }
659
660 API int heap_monitor_initialized(void)
661 {
662         return s_info.initialized;
663 }
664
665 API void heap_monitor_set_stack_boundary(unsigned long stack_boundary)
666 {
667         s_info.stack_boundary = stack_boundary;
668 }
669
670 API void heap_monitor_init(void)
671 {
672         const char *var;
673         pthread_attr_t attr;
674
675         if (s_info.initialized)
676                 return;
677         
678 #if defined(_ENABLE_MCHECK)
679         s_info.m_check = mcheck(mcheck_cb);
680         if (s_info.m_check < 0)
681                 ErrPrint("Failed to install mcheck[%d]\n", s_info.m_check);
682         else
683                 DbgPrint("mcheck installed: %d\n", s_info.m_check);
684 #endif
685
686         s_info._malloc = __malloc_hook;
687         s_info._realloc = __realloc_hook;
688         s_info._free = __free_hook;
689         s_info._memalign = __memalign_hook;
690
691         var = getenv("HEAP_MONITOR_DUMP_DEPTH");
692         if (var)
693                 s_info.dump_depth = atoi(var);
694
695         var = getenv("HEAP_MONITOR_DEBUGGER");
696         if (var)
697                 s_info.debugger = atoi(var);
698
699         s_info.main_tid = pthread_self();
700         s_info.initialized = 1;
701
702         if (pthread_getattr_np(s_info.main_tid, &attr))
703                 return;
704
705         if (pthread_attr_getstack(&attr, (void *)&s_info.stack_base, &s_info.stack_size)) {
706                 s_info.stack_base = 0;
707                 s_info.stack_size = 0;
708         } else {
709                 s_info.stack_boundary = s_info.stack_base + s_info.stack_size;
710         }
711
712         pthread_attr_destroy(&attr);
713         DbgPrint("Initialized\n");
714 }
715
716 API void heap_monitor_start(void)
717 {
718         if (!s_info.initialized)
719                 return;
720
721         hook();
722 }
723
724 API void heap_monitor_stop(void)
725 {
726         if (!s_info.initialized)
727                 return;
728
729         unhook();
730 }
731
732 /* End of a file */