1 /* Profile heap and stack memory usage of running program.
2    Copyright (C) 1998-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <assert.h>
20 #include <atomic.h>
21 #include <dlfcn.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <inttypes.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdbool.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <stdint.h>
33 #include <sys/mman.h>
34 #include <sys/time.h>
35 
36 #include <hp-timing.h>
37 #include <machine-sp.h>
38 #include <stackinfo.h>  /* For _STACK_GROWS_UP  */
39 
40 /* Pointer to the real functions.  These are determined used `dlsym'
41    when really needed.  */
42 static void *(*mallocp)(size_t);
43 static void *(*reallocp) (void *, size_t);
44 static void *(*callocp) (size_t, size_t);
45 static void (*freep) (void *);
46 
47 static void *(*mmapp) (void *, size_t, int, int, int, off_t);
48 static void *(*mmap64p) (void *, size_t, int, int, int, off64_t);
49 static int (*munmapp) (void *, size_t);
50 static void *(*mremapp) (void *, size_t, size_t, int, void *);
51 
52 enum
53 {
54   idx_malloc = 0,
55   idx_realloc,
56   idx_calloc,
57   idx_free,
58   idx_mmap_r,
59   idx_mmap_w,
60   idx_mmap_a,
61   idx_mremap,
62   idx_munmap,
63   idx_last
64 };
65 
66 
67 struct header
68 {
69   size_t length;
70   size_t magic;
71 };
72 
73 #define MAGIC 0xfeedbeaf
74 
75 
76 static unsigned long int calls[idx_last];
77 static unsigned long int failed[idx_last];
78 static size_t total[idx_last];
79 static size_t grand_total;
80 static unsigned long int histogram[65536 / 16];
81 static unsigned long int large;
82 static unsigned long int calls_total;
83 static unsigned long int inplace;
84 static unsigned long int decreasing;
85 static unsigned long int realloc_free;
86 static unsigned long int inplace_mremap;
87 static unsigned long int decreasing_mremap;
88 static size_t current_heap;
89 static size_t peak_use[3];
90 static __thread uintptr_t start_sp;
91 
92 /* A few macros to make the source more readable.  */
93 #define peak_heap       peak_use[0]
94 #define peak_stack      peak_use[1]
95 #define peak_total      peak_use[2]
96 
97 #define DEFAULT_BUFFER_SIZE     32768
98 static size_t buffer_size;
99 
100 static int fd = -1;
101 
102 static bool not_me;
103 static int initialized;
104 static bool trace_mmap;
105 extern const char *__progname;
106 
107 struct entry
108 {
109   uint64_t heap;
110   uint64_t stack;
111   uint32_t time_low;
112   uint32_t time_high;
113 };
114 
115 static struct entry buffer[2 * DEFAULT_BUFFER_SIZE];
116 static uint32_t buffer_cnt;
117 static struct entry first;
118 
119 static void
gettime(struct entry * e)120 gettime (struct entry *e)
121 {
122 #if HP_TIMING_INLINE
123   hp_timing_t now;
124   HP_TIMING_NOW (now);
125   e->time_low = now & 0xffffffff;
126   e->time_high = now >> 32;
127 #else
128   struct __timespec64 now;
129   uint64_t usecs;
130   __clock_gettime64 (CLOCK_REALTIME, &now);
131   usecs = (uint64_t)now.tv_nsec / 1000 + (uint64_t)now.tv_sec * 1000000;
132   e->time_low = usecs & 0xffffffff;
133   e->time_high = usecs >> 32;
134 #endif
135 }
136 
137 /* Update the global data after a successful function call.  */
138 static void
update_data(struct header * result,size_t len,size_t old_len)139 update_data (struct header *result, size_t len, size_t old_len)
140 {
141   if (result != NULL)
142     {
143       /* Record the information we need and mark the block using a
144          magic number.  */
145       result->length = len;
146       result->magic = MAGIC;
147     }
148 
149   /* Compute current heap usage and compare it with the maximum value.  */
150   size_t heap
151     = catomic_exchange_and_add (&current_heap, len - old_len) + len - old_len;
152   catomic_max (&peak_heap, heap);
153 
154   /* Compute current stack usage and compare it with the maximum
155      value.  The base stack pointer might not be set if this is not
156      the main thread and it is the first call to any of these
157      functions.  */
158   if (__glibc_unlikely (!start_sp))
159     start_sp = __thread_stack_pointer ();
160 
161   uintptr_t sp = __thread_stack_pointer ();
162 #ifdef _STACK_GROWS_UP
163   /* This can happen in threads where we didn't catch the thread's
164      stack early enough.  */
165   if (__glibc_unlikely (sp < start_sp))
166     start_sp = sp;
167   size_t current_stack = sp - start_sp;
168 #else
169   /* This can happen in threads where we didn't catch the thread's
170      stack early enough.  */
171   if (__glibc_unlikely (sp > start_sp))
172     start_sp = sp;
173   size_t current_stack = start_sp - sp;
174 #endif
175   catomic_max (&peak_stack, current_stack);
176 
177   /* Add up heap and stack usage and compare it with the maximum value.  */
178   catomic_max (&peak_total, heap + current_stack);
179 
180   /* Store the value only if we are writing to a file.  */
181   if (fd != -1)
182     {
183       uint32_t idx = catomic_exchange_and_add (&buffer_cnt, 1);
184       if (idx + 1 >= 2 * buffer_size)
185         {
186           /* We try to reset the counter to the correct range.  If
187              this fails because of another thread increasing the
188              counter it does not matter since that thread will take
189              care of the correction.  */
190           uint32_t reset = (idx + 1) % (2 * buffer_size);
191           catomic_compare_and_exchange_val_acq (&buffer_cnt, reset, idx + 1);
192           if (idx >= 2 * buffer_size)
193             idx = reset - 1;
194         }
195       assert (idx < 2 * DEFAULT_BUFFER_SIZE);
196 
197       buffer[idx].heap = current_heap;
198       buffer[idx].stack = current_stack;
199       gettime (&buffer[idx]);
200 
201       /* Write out buffer if it is full.  */
202       if (idx + 1 == buffer_size)
203         write (fd, buffer, buffer_size * sizeof (struct entry));
204       else if (idx + 1 == 2 * buffer_size)
205         write (fd, &buffer[buffer_size], buffer_size * sizeof (struct entry));
206     }
207 }
208 
209 
210 /* Interrupt handler.  */
211 static void
int_handler(int signo)212 int_handler (int signo)
213 {
214   /* Nothing gets allocated.  Just record the stack pointer position.  */
215   update_data (NULL, 0, 0);
216 }
217 
218 
219 /* Find out whether this is the program we are supposed to profile.
220    For this the name in the variable `__progname' must match the one
221    given in the environment variable MEMUSAGE_PROG_NAME.  If the variable
222    is not present every program assumes it should be profiling.
223 
224    If this is the program open a file descriptor to the output file.
225    We will write to it whenever the buffer overflows.  The name of the
226    output file is determined by the environment variable MEMUSAGE_OUTPUT.
227 
228    If the environment variable MEMUSAGE_BUFFER_SIZE is set its numerical
229    value determines the size of the internal buffer.  The number gives
230    the number of elements in the buffer.  By setting the number to one
231    one effectively selects unbuffered operation.
232 
233    If MEMUSAGE_NO_TIMER is not present an alarm handler is installed
234    which at the highest possible frequency records the stack pointer.  */
235 static void
me(void)236 me (void)
237 {
238   const char *env = getenv ("MEMUSAGE_PROG_NAME");
239   size_t prog_len = strlen (__progname);
240 
241   initialized = -1;
242   mallocp = (void *(*)(size_t))dlsym (RTLD_NEXT, "malloc");
243   reallocp = (void *(*)(void *, size_t))dlsym (RTLD_NEXT, "realloc");
244   callocp = (void *(*)(size_t, size_t))dlsym (RTLD_NEXT, "calloc");
245   freep = (void (*)(void *))dlsym (RTLD_NEXT, "free");
246 
247   mmapp = (void *(*)(void *, size_t, int, int, int, off_t))dlsym (RTLD_NEXT,
248                                                                   "mmap");
249   mmap64p =
250     (void *(*)(void *, size_t, int, int, int, off64_t))dlsym (RTLD_NEXT,
251                                                               "mmap64");
252   mremapp = (void *(*)(void *, size_t, size_t, int, void *))dlsym (RTLD_NEXT,
253                                                                    "mremap");
254   munmapp = (int (*)(void *, size_t))dlsym (RTLD_NEXT, "munmap");
255   initialized = 1;
256 
257   if (env != NULL)
258     {
259       /* Check for program name.  */
260       size_t len = strlen (env);
261       if (len > prog_len || strcmp (env, &__progname[prog_len - len]) != 0
262           || (prog_len != len && __progname[prog_len - len - 1] != '/'))
263         not_me = true;
264     }
265 
266   /* Only open the file if it's really us.  */
267   if (!not_me && fd == -1)
268     {
269       const char *outname;
270 
271       if (!start_sp)
272         start_sp = __thread_stack_pointer ();
273 
274       outname = getenv ("MEMUSAGE_OUTPUT");
275       if (outname != NULL && outname[0] != '\0'
276           && (access (outname, R_OK | W_OK) == 0 || errno == ENOENT))
277         {
278           fd = creat64 (outname, 0666);
279 
280           if (fd == -1)
281             /* Don't do anything in future calls if we cannot write to
282                the output file.  */
283             not_me = true;
284           else
285             {
286               /* Write the first entry.  */
287               first.heap = 0;
288               first.stack = 0;
289               gettime (&first);
290               /* Write it two times since we need the starting and end time. */
291               write (fd, &first, sizeof (first));
292               write (fd, &first, sizeof (first));
293 
294               /* Determine the buffer size.  We use the default if the
295                  environment variable is not present.  */
296               buffer_size = DEFAULT_BUFFER_SIZE;
297               const char *str_buffer_size = getenv ("MEMUSAGE_BUFFER_SIZE");
298               if (str_buffer_size != NULL)
299                 {
300                   buffer_size = atoi (str_buffer_size);
301                   if (buffer_size == 0 || buffer_size > DEFAULT_BUFFER_SIZE)
302                     buffer_size = DEFAULT_BUFFER_SIZE;
303                 }
304 
305               /* Possibly enable timer-based stack pointer retrieval.  */
306               if (getenv ("MEMUSAGE_NO_TIMER") == NULL)
307                 {
308                   struct sigaction act;
309 
310                   act.sa_handler = (sighandler_t) &int_handler;
311                   act.sa_flags = SA_RESTART;
312                   sigfillset (&act.sa_mask);
313 
314                   if (sigaction (SIGPROF, &act, NULL) >= 0)
315                     {
316                       struct itimerval timer;
317 
318                       timer.it_value.tv_sec = 0;
319                       timer.it_value.tv_usec = 1;
320                       timer.it_interval = timer.it_value;
321                       setitimer (ITIMER_PROF, &timer, NULL);
322                     }
323                 }
324             }
325         }
326 
327       if (!not_me && getenv ("MEMUSAGE_TRACE_MMAP") != NULL)
328         trace_mmap = true;
329     }
330 }
331 
332 
333 /* Record the initial stack position.  */
334 static void
335 __attribute__ ((constructor))
init(void)336 init (void)
337 {
338   start_sp = __thread_stack_pointer ();
339   if (!initialized)
340     me ();
341 }
342 
343 
344 /* `malloc' replacement.  We keep track of the memory usage if this is the
345    correct program.  */
346 void *
malloc(size_t len)347 malloc (size_t len)
348 {
349   struct header *result = NULL;
350 
351   /* Determine real implementation if not already happened.  */
352   if (__glibc_unlikely (initialized <= 0))
353     {
354       if (initialized == -1)
355         return NULL;
356 
357       me ();
358     }
359 
360   /* If this is not the correct program just use the normal function.  */
361   if (not_me)
362     return (*mallocp)(len);
363 
364   /* Keep track of number of calls.  */
365   catomic_increment (&calls[idx_malloc]);
366   /* Keep track of total memory consumption for `malloc'.  */
367   catomic_add (&total[idx_malloc], len);
368   /* Keep track of total memory requirement.  */
369   catomic_add (&grand_total, len);
370   /* Remember the size of the request.  */
371   if (len < 65536)
372     catomic_increment (&histogram[len / 16]);
373   else
374     catomic_increment (&large);
375   /* Total number of calls of any of the functions.  */
376   catomic_increment (&calls_total);
377 
378   /* Do the real work.  */
379   result = (struct header *) (*mallocp)(len + sizeof (struct header));
380   if (result == NULL)
381     {
382       catomic_increment (&failed[idx_malloc]);
383       return NULL;
384     }
385 
386   /* Update the allocation data and write out the records if necessary.  */
387   update_data (result, len, 0);
388 
389   /* Return the pointer to the user buffer.  */
390   return (void *) (result + 1);
391 }
392 
393 
394 /* `realloc' replacement.  We keep track of the memory usage if this is the
395    correct program.  */
396 void *
realloc(void * old,size_t len)397 realloc (void *old, size_t len)
398 {
399   struct header *result = NULL;
400   struct header *real;
401   size_t old_len;
402 
403   /* Determine real implementation if not already happened.  */
404   if (__glibc_unlikely (initialized <= 0))
405     {
406       if (initialized == -1)
407         return NULL;
408 
409       me ();
410     }
411 
412   /* If this is not the correct program just use the normal function.  */
413   if (not_me)
414     return (*reallocp)(old, len);
415 
416   if (old == NULL)
417     {
418       /* This is really a `malloc' call.  */
419       real = NULL;
420       old_len = 0;
421     }
422   else
423     {
424       real = ((struct header *) old) - 1;
425       if (real->magic != MAGIC)
426         /* This is no memory allocated here.  */
427         return (*reallocp)(old, len);
428 
429       old_len = real->length;
430     }
431 
432   /* Keep track of number of calls.  */
433   catomic_increment (&calls[idx_realloc]);
434   if (len > old_len)
435     {
436       /* Keep track of total memory consumption for `realloc'.  */
437       catomic_add (&total[idx_realloc], len - old_len);
438       /* Keep track of total memory requirement.  */
439       catomic_add (&grand_total, len - old_len);
440     }
441 
442   if (len == 0 && old != NULL)
443     {
444       /* Special case.  */
445       catomic_increment (&realloc_free);
446       /* Keep track of total memory freed using `free'.  */
447       catomic_add (&total[idx_free], real->length);
448 
449       /* Update the allocation data and write out the records if necessary.  */
450       update_data (NULL, 0, old_len);
451 
452       /* Do the real work.  */
453       (*freep) (real);
454 
455       return NULL;
456     }
457 
458   /* Remember the size of the request.  */
459   if (len < 65536)
460     catomic_increment (&histogram[len / 16]);
461   else
462     catomic_increment (&large);
463   /* Total number of calls of any of the functions.  */
464   catomic_increment (&calls_total);
465 
466   /* Do the real work.  */
467   result = (struct header *) (*reallocp)(real, len + sizeof (struct header));
468   if (result == NULL)
469     {
470       catomic_increment (&failed[idx_realloc]);
471       return NULL;
472     }
473 
474   /* Record whether the reduction/increase happened in place.  */
475   if (real == result)
476     catomic_increment (&inplace);
477   /* Was the buffer increased?  */
478   if (old_len > len)
479     catomic_increment (&decreasing);
480 
481   /* Update the allocation data and write out the records if necessary.  */
482   update_data (result, len, old_len);
483 
484   /* Return the pointer to the user buffer.  */
485   return (void *) (result + 1);
486 }
487 
488 
489 /* `calloc' replacement.  We keep track of the memory usage if this is the
490    correct program.  */
491 void *
calloc(size_t n,size_t len)492 calloc (size_t n, size_t len)
493 {
494   struct header *result;
495   size_t size = n * len;
496 
497   /* Determine real implementation if not already happened.  */
498   if (__glibc_unlikely (initialized <= 0))
499     {
500       if (initialized == -1)
501         return NULL;
502 
503       me ();
504     }
505 
506   /* If this is not the correct program just use the normal function.  */
507   if (not_me)
508     return (*callocp)(n, len);
509 
510   /* Keep track of number of calls.  */
511   catomic_increment (&calls[idx_calloc]);
512   /* Keep track of total memory consumption for `calloc'.  */
513   catomic_add (&total[idx_calloc], size);
514   /* Keep track of total memory requirement.  */
515   catomic_add (&grand_total, size);
516   /* Remember the size of the request.  */
517   if (size < 65536)
518     catomic_increment (&histogram[size / 16]);
519   else
520     catomic_increment (&large);
521   /* Total number of calls of any of the functions.  */
522   ++calls_total;
523 
524   /* Do the real work.  */
525   result = (struct header *) (*mallocp)(size + sizeof (struct header));
526   if (result == NULL)
527     {
528       catomic_increment (&failed[idx_calloc]);
529       return NULL;
530     }
531 
532   /* Update the allocation data and write out the records if necessary.  */
533   update_data (result, size, 0);
534 
535   /* Do what `calloc' would have done and return the buffer to the caller.  */
536   return memset (result + 1, '\0', size);
537 }
538 
539 
540 /* `free' replacement.  We keep track of the memory usage if this is the
541    correct program.  */
542 void
free(void * ptr)543 free (void *ptr)
544 {
545   struct header *real;
546 
547   /* Determine real implementation if not already happened.  */
548   if (__glibc_unlikely (initialized <= 0))
549     {
550       if (initialized == -1)
551         return;
552 
553       me ();
554     }
555 
556   /* If this is not the correct program just use the normal function.  */
557   if (not_me)
558     {
559       (*freep) (ptr);
560       return;
561     }
562 
563   /* `free (NULL)' has no effect.  */
564   if (ptr == NULL)
565     {
566       catomic_increment (&calls[idx_free]);
567       return;
568     }
569 
570   /* Determine the pointer to the header.  */
571   real = ((struct header *) ptr) - 1;
572   if (real->magic != MAGIC)
573     {
574       /* This block wasn't allocated here.  */
575       (*freep) (ptr);
576       return;
577     }
578 
579   /* Keep track of number of calls.  */
580   catomic_increment (&calls[idx_free]);
581   /* Keep track of total memory freed using `free'.  */
582   catomic_add (&total[idx_free], real->length);
583 
584   /* Update the allocation data and write out the records if necessary.  */
585   update_data (NULL, 0, real->length);
586 
587   /* Do the real work.  */
588   (*freep) (real);
589 }
590 
591 
592 /* `mmap' replacement.  We do not have to keep track of the size since
593    `munmap' will get it as a parameter.  */
594 void *
mmap(void * start,size_t len,int prot,int flags,int fd,off_t offset)595 mmap (void *start, size_t len, int prot, int flags, int fd, off_t offset)
596 {
597   void *result = NULL;
598 
599   /* Determine real implementation if not already happened.  */
600   if (__glibc_unlikely (initialized <= 0))
601     {
602       if (initialized == -1)
603         return NULL;
604 
605       me ();
606     }
607 
608   /* Always get a block.  We don't need extra memory.  */
609   result = (*mmapp)(start, len, prot, flags, fd, offset);
610 
611   if (!not_me && trace_mmap)
612     {
613       int idx = (flags & MAP_ANON
614                  ? idx_mmap_a : prot & PROT_WRITE ? idx_mmap_w : idx_mmap_r);
615 
616       /* Keep track of number of calls.  */
617       catomic_increment (&calls[idx]);
618       /* Keep track of total memory consumption for `malloc'.  */
619       catomic_add (&total[idx], len);
620       /* Keep track of total memory requirement.  */
621       catomic_add (&grand_total, len);
622       /* Remember the size of the request.  */
623       if (len < 65536)
624         catomic_increment (&histogram[len / 16]);
625       else
626         catomic_increment (&large);
627       /* Total number of calls of any of the functions.  */
628       catomic_increment (&calls_total);
629 
630       /* Check for failures.  */
631       if (result == NULL)
632         catomic_increment (&failed[idx]);
633       else if (idx == idx_mmap_w)
634         /* Update the allocation data and write out the records if
635            necessary.  Note the first parameter is NULL which means
636            the size is not tracked.  */
637         update_data (NULL, len, 0);
638     }
639 
640   /* Return the pointer to the user buffer.  */
641   return result;
642 }
643 
644 
645 /* `mmap64' replacement.  We do not have to keep track of the size since
646    `munmap' will get it as a parameter.  */
647 void *
mmap64(void * start,size_t len,int prot,int flags,int fd,off64_t offset)648 mmap64 (void *start, size_t len, int prot, int flags, int fd, off64_t offset)
649 {
650   void *result = NULL;
651 
652   /* Determine real implementation if not already happened.  */
653   if (__glibc_unlikely (initialized <= 0))
654     {
655       if (initialized == -1)
656         return NULL;
657 
658       me ();
659     }
660 
661   /* Always get a block.  We don't need extra memory.  */
662   result = (*mmap64p)(start, len, prot, flags, fd, offset);
663 
664   if (!not_me && trace_mmap)
665     {
666       int idx = (flags & MAP_ANON
667                  ? idx_mmap_a : prot & PROT_WRITE ? idx_mmap_w : idx_mmap_r);
668 
669       /* Keep track of number of calls.  */
670       catomic_increment (&calls[idx]);
671       /* Keep track of total memory consumption for `malloc'.  */
672       catomic_add (&total[idx], len);
673       /* Keep track of total memory requirement.  */
674       catomic_add (&grand_total, len);
675       /* Remember the size of the request.  */
676       if (len < 65536)
677         catomic_increment (&histogram[len / 16]);
678       else
679         catomic_increment (&large);
680       /* Total number of calls of any of the functions.  */
681       catomic_increment (&calls_total);
682 
683       /* Check for failures.  */
684       if (result == NULL)
685         catomic_increment (&failed[idx]);
686       else if (idx == idx_mmap_w)
687         /* Update the allocation data and write out the records if
688            necessary.  Note the first parameter is NULL which means
689            the size is not tracked.  */
690         update_data (NULL, len, 0);
691     }
692 
693   /* Return the pointer to the user buffer.  */
694   return result;
695 }
696 
697 
698 /* `mremap' replacement.  We do not have to keep track of the size since
699    `munmap' will get it as a parameter.  */
700 void *
mremap(void * start,size_t old_len,size_t len,int flags,...)701 mremap (void *start, size_t old_len, size_t len, int flags, ...)
702 {
703   void *result = NULL;
704   va_list ap;
705 
706   va_start (ap, flags);
707   void *newaddr = (flags & MREMAP_FIXED) ? va_arg (ap, void *) : NULL;
708   va_end (ap);
709 
710   /* Determine real implementation if not already happened.  */
711   if (__glibc_unlikely (initialized <= 0))
712     {
713       if (initialized == -1)
714         return NULL;
715 
716       me ();
717     }
718 
719   /* Always get a block.  We don't need extra memory.  */
720   result = (*mremapp)(start, old_len, len, flags, newaddr);
721 
722   if (!not_me && trace_mmap)
723     {
724       /* Keep track of number of calls.  */
725       catomic_increment (&calls[idx_mremap]);
726       if (len > old_len)
727         {
728           /* Keep track of total memory consumption for `malloc'.  */
729           catomic_add (&total[idx_mremap], len - old_len);
730           /* Keep track of total memory requirement.  */
731           catomic_add (&grand_total, len - old_len);
732         }
733       /* Remember the size of the request.  */
734       if (len < 65536)
735         catomic_increment (&histogram[len / 16]);
736       else
737         catomic_increment (&large);
738       /* Total number of calls of any of the functions.  */
739       catomic_increment (&calls_total);
740 
741       /* Check for failures.  */
742       if (result == NULL)
743         catomic_increment (&failed[idx_mremap]);
744       else
745         {
746           /* Record whether the reduction/increase happened in place.  */
747           if (start == result)
748             catomic_increment (&inplace_mremap);
749           /* Was the buffer increased?  */
750           if (old_len > len)
751             catomic_increment (&decreasing_mremap);
752 
753           /* Update the allocation data and write out the records if
754              necessary.  Note the first parameter is NULL which means
755              the size is not tracked.  */
756           update_data (NULL, len, old_len);
757         }
758     }
759 
760   /* Return the pointer to the user buffer.  */
761   return result;
762 }
763 
764 
765 /* `munmap' replacement.  */
766 int
munmap(void * start,size_t len)767 munmap (void *start, size_t len)
768 {
769   int result;
770 
771   /* Determine real implementation if not already happened.  */
772   if (__glibc_unlikely (initialized <= 0))
773     {
774       if (initialized == -1)
775         return -1;
776 
777       me ();
778     }
779 
780   /* Do the real work.  */
781   result = (*munmapp)(start, len);
782 
783   if (!not_me && trace_mmap)
784     {
785       /* Keep track of number of calls.  */
786       catomic_increment (&calls[idx_munmap]);
787 
788       if (__glibc_likely (result == 0))
789         {
790           /* Keep track of total memory freed using `free'.  */
791           catomic_add (&total[idx_munmap], len);
792 
793           /* Update the allocation data and write out the records if
794              necessary.  */
795           update_data (NULL, 0, len);
796         }
797       else
798         catomic_increment (&failed[idx_munmap]);
799     }
800 
801   return result;
802 }
803 
804 
805 /* Write some statistics to standard error.  */
806 static void
807 __attribute__ ((destructor))
dest(void)808 dest (void)
809 {
810   int percent, cnt;
811   unsigned long int maxcalls;
812 
813   /* If we haven't done anything here just return.  */
814   if (not_me)
815     return;
816 
817   /* If we should call any of the memory functions don't do any profiling.  */
818   not_me = true;
819 
820   /* Finish the output file.  */
821   if (fd != -1)
822     {
823       /* Write the partially filled buffer.  */
824       if (buffer_cnt > buffer_size)
825         write (fd, buffer + buffer_size,
826                (buffer_cnt - buffer_size) * sizeof (struct entry));
827       else
828         write (fd, buffer, buffer_cnt * sizeof (struct entry));
829 
830       /* Go back to the beginning of the file.  We allocated two records
831          here when we opened the file.  */
832       lseek (fd, 0, SEEK_SET);
833       /* Write out a record containing the total size.  */
834       first.stack = peak_total;
835       write (fd, &first, sizeof (struct entry));
836       /* Write out another record containing the maximum for heap and
837          stack.  */
838       first.heap = peak_heap;
839       first.stack = peak_stack;
840       gettime (&first);
841       write (fd, &first, sizeof (struct entry));
842 
843       /* Close the file.  */
844       close (fd);
845       fd = -1;
846     }
847 
848   /* Write a colorful statistic.  */
849   fprintf (stderr, "\n\
850 \e[01;32mMemory usage summary:\e[0;0m heap total: %llu, heap peak: %lu, stack peak: %lu\n\
851 \e[04;34m         total calls   total memory   failed calls\e[0m\n\
852 \e[00;34m malloc|\e[0m %10lu   %12llu   %s%12lu\e[00;00m\n\
853 \e[00;34mrealloc|\e[0m %10lu   %12llu   %s%12lu\e[00;00m  (nomove:%ld, dec:%ld, free:%ld)\n\
854 \e[00;34m calloc|\e[0m %10lu   %12llu   %s%12lu\e[00;00m\n\
855 \e[00;34m   free|\e[0m %10lu   %12llu\n",
856            (unsigned long long int) grand_total, (unsigned long int) peak_heap,
857            (unsigned long int) peak_stack,
858            (unsigned long int) calls[idx_malloc],
859            (unsigned long long int) total[idx_malloc],
860            failed[idx_malloc] ? "\e[01;41m" : "",
861            (unsigned long int) failed[idx_malloc],
862            (unsigned long int) calls[idx_realloc],
863            (unsigned long long int) total[idx_realloc],
864            failed[idx_realloc] ? "\e[01;41m" : "",
865            (unsigned long int) failed[idx_realloc],
866            (unsigned long int) inplace,
867            (unsigned long int) decreasing,
868            (unsigned long int) realloc_free,
869            (unsigned long int) calls[idx_calloc],
870            (unsigned long long int) total[idx_calloc],
871            failed[idx_calloc] ? "\e[01;41m" : "",
872            (unsigned long int) failed[idx_calloc],
873            (unsigned long int) calls[idx_free],
874            (unsigned long long int) total[idx_free]);
875 
876   if (trace_mmap)
877     fprintf (stderr, "\
878 \e[00;34mmmap(r)|\e[0m %10lu   %12llu   %s%12lu\e[00;00m\n\
879 \e[00;34mmmap(w)|\e[0m %10lu   %12llu   %s%12lu\e[00;00m\n\
880 \e[00;34mmmap(a)|\e[0m %10lu   %12llu   %s%12lu\e[00;00m\n\
881 \e[00;34m mremap|\e[0m %10lu   %12llu   %s%12lu\e[00;00m  (nomove: %ld, dec:%ld)\n\
882 \e[00;34m munmap|\e[0m %10lu   %12llu   %s%12lu\e[00;00m\n",
883              (unsigned long int) calls[idx_mmap_r],
884              (unsigned long long int) total[idx_mmap_r],
885              failed[idx_mmap_r] ? "\e[01;41m" : "",
886              (unsigned long int) failed[idx_mmap_r],
887              (unsigned long int) calls[idx_mmap_w],
888              (unsigned long long int) total[idx_mmap_w],
889              failed[idx_mmap_w] ? "\e[01;41m" : "",
890              (unsigned long int) failed[idx_mmap_w],
891              (unsigned long int) calls[idx_mmap_a],
892              (unsigned long long int) total[idx_mmap_a],
893              failed[idx_mmap_a] ? "\e[01;41m" : "",
894              (unsigned long int) failed[idx_mmap_a],
895              (unsigned long int) calls[idx_mremap],
896              (unsigned long long int) total[idx_mremap],
897              failed[idx_mremap] ? "\e[01;41m" : "",
898              (unsigned long int) failed[idx_mremap],
899              (unsigned long int) inplace_mremap,
900              (unsigned long int) decreasing_mremap,
901              (unsigned long int) calls[idx_munmap],
902              (unsigned long long int) total[idx_munmap],
903              failed[idx_munmap] ? "\e[01;41m" : "",
904              (unsigned long int) failed[idx_munmap]);
905 
906   /* Write out a histoogram of the sizes of the allocations.  */
907   fprintf (stderr, "\e[01;32mHistogram for block sizes:\e[0;0m\n");
908 
909   /* Determine the maximum of all calls for each size range.  */
910   maxcalls = large;
911   for (cnt = 0; cnt < 65536; cnt += 16)
912     if (histogram[cnt / 16] > maxcalls)
913       maxcalls = histogram[cnt / 16];
914 
915   for (cnt = 0; cnt < 65536; cnt += 16)
916     /* Only write out the nonzero entries.  */
917     if (histogram[cnt / 16] != 0)
918       {
919         percent = (histogram[cnt / 16] * 100) / calls_total;
920         fprintf (stderr, "%5d-%-5d%12lu ", cnt, cnt + 15,
921                  (unsigned long int) histogram[cnt / 16]);
922         if (percent == 0)
923           fputs (" <1% \e[41;37m", stderr);
924         else
925           fprintf (stderr, "%3d%% \e[41;37m", percent);
926 
927         /* Draw a bar with a length corresponding to the current
928            percentage.  */
929         percent = (histogram[cnt / 16] * 50) / maxcalls;
930         while (percent-- > 0)
931           fputc ('=', stderr);
932         fputs ("\e[0;0m\n", stderr);
933       }
934 
935   if (large != 0)
936     {
937       percent = (large * 100) / calls_total;
938       fprintf (stderr, "   large   %12lu ", (unsigned long int) large);
939       if (percent == 0)
940         fputs (" <1% \e[41;37m", stderr);
941       else
942         fprintf (stderr, "%3d%% \e[41;37m", percent);
943       percent = (large * 50) / maxcalls;
944       while (percent-- > 0)
945         fputc ('=', stderr);
946       fputs ("\e[0;0m\n", stderr);
947     }
948 
949   /* Any following malloc/free etc. calls should generate statistics again,
950      because otherwise freeing something that has been malloced before
951      this destructor (including struct header in front of it) wouldn't
952      be properly freed.  */
953   not_me = false;
954 }
955