├── .gitignore ├── Makefile ├── README.md └── malloc-stats.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: malloc-stats.so 2 | 3 | malloc-stats.o: malloc-stats.c 4 | $(CC) -c -fPIC -o $@ $< 5 | 6 | malloc-stats.so: malloc-stats.o 7 | $(LD) -shared -o $@ $< -ldl 8 | 9 | clean: 10 | $(RM) malloc-stats.o malloc-stats.so 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # malloc-stats 2 | 3 | Small LD_PRELOAD library to show allocation stats 4 | 5 | ## Build 6 | 7 | ``` 8 | $ make 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | $ LD_PRELOAD=/path/to/malloc-stats.so your-binary args... 15 | ``` 16 | 17 | ## Interpretting the results 18 | 19 | Sample usage: 20 | ``` 21 | $ LD_PRELOAD=~/malloc-stats/malloc-stats.so build/util/hb-shape Gulzar-Regular.ttf -u 660 --no-glyph-names 22 | [1154=0+310] 23 | TOTAL MALLOC REALLOC 24 | num size num size num size 25 | 1 5 1 5 0 0 /lib64/libc.so.6(+0x39b71) [0x7efd91744b71] 26 | 1 8 1 8 0 0 /home/behdad/harfbuzz/build/util/../src/libharfbuzz.so.0(hb_ot_layout_has_glyph_classes+0xe0) [0x7efd91ba4380] 27 | 1 12 1 12 0 0 /home/behdad/harfbuzz/build/util/../src/libharfbuzz.so.0(+0x29b6c) [0x7efd91b4eb6c] 28 | ... 29 | 18 3,328 0 0 18 3,328 /lib64/libglib-2.0.so.0(g_realloc+0x30) [0x7efd91a27720] 30 | 23 4,692 23 4,692 0 0 /lib64/libglib-2.0.so.0(g_malloc0+0x21) [0x7efd91a275f1] 31 | 31 13,872 31 13,872 0 0 /home/behdad/harfbuzz/build/util/../src/libharfbuzz.so.0(+0xabd1d) [0x7efd91bd0d1d] 32 | 37 229,216 37 229,216 0 0 /home/behdad/harfbuzz/build/util/../src/libharfbuzz.so.0(+0xab352) [0x7efd91bd0352] 33 | 40 21,655 40 21,655 0 0 /lib64/libglib-2.0.so.0(g_malloc+0x19) [0x7efd91a27169] 34 | 243 294,351 203 282,076 40 12,275 (total) 35 | ``` 36 | 37 | Now, if we want to know where the allocation callsite `home/behdad/harfbuzz/build/util/../src/libharfbuzz.so.0(+0xab352)` is, we can use the `addr2line` tool: 38 | ``` 39 | $ addr2line -i -e /home/behdad/harfbuzz/build/util/../src/libharfbuzz.so +0xab352 40 | /home/behdad/harfbuzz/build/../src/OT/Layout/GPOS/../../../hb-ot-layout-gsubgpos.hh:4047 41 | /home/behdad/harfbuzz/build/../src/OT/Layout/GPOS/../../../hb-ot-layout-gsubgpos.hh:4502 42 | /home/behdad/harfbuzz/build/../src/hb-ot-layout.cc:1965 43 | ``` 44 | -------------------------------------------------------------------------------- /malloc-stats.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ 2 | /* 3 | * Copyright © 2007 Red Hat, Inc. 4 | * 5 | * Permission to use, copy, modify, distribute, and sell this software 6 | * and its documentation for any purpose is hereby granted without 7 | * fee, provided that the above copyright notice appear in all copies 8 | * and that both that copyright notice and this permission notice 9 | * appear in supporting documentation, and that the name of 10 | * Red Hat, Inc. not be used in advertising or publicity pertaining to 11 | * distribution of the software without specific, written prior 12 | * permission. Red Hat, Inc. makes no representations about the 13 | * suitability of this software for any purpose. It is provided "as 14 | * is" without express or implied warranty. 15 | * 16 | * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 17 | * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 18 | * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL, 19 | * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 20 | * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 21 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 22 | * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 23 | * 24 | * Author: Behdad Esfahbod 25 | */ 26 | 27 | /* A simple malloc wrapper that prints out statistics on termination */ 28 | 29 | #ifndef _GNU_SOURCE 30 | #define _GNU_SOURCE 31 | #endif 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | /* caller-logging */ 38 | 39 | #include 40 | 41 | struct alloc_stat_t { 42 | unsigned int num; 43 | unsigned long long size; 44 | }; 45 | 46 | struct alloc_stats_t { 47 | struct alloc_stat_t malloc, realloc, total; 48 | }; 49 | 50 | struct func_stat_t { 51 | struct func_stat_t *next; 52 | 53 | const void *addr; 54 | const char *name; 55 | 56 | struct alloc_stats_t stat; 57 | }; 58 | 59 | static struct alloc_stats_t total_allocations; 60 | static struct func_stat_t *func_stats[31627]; 61 | static int func_stats_num; 62 | 63 | #ifndef ARRAY_LENGTH 64 | #define ARRAY_LENGTH(__array) ((int) (sizeof (__array) / sizeof (__array[0]))) 65 | #endif 66 | static void 67 | alloc_stats_add (struct alloc_stats_t *stats, int is_realloc, size_t size) 68 | { 69 | struct alloc_stat_t *stat = is_realloc ? &stats->realloc : &stats->malloc; 70 | 71 | stats->total.num++; 72 | stats->total.size += size; 73 | 74 | stat->num++; 75 | stat->size += size; 76 | } 77 | 78 | #include 79 | 80 | static void * 81 | _perm_alloc (size_t size) 82 | { 83 | static uint8_t *ptr; 84 | static size_t rem; 85 | 86 | void *ret; 87 | 88 | #define SUPERBLOCK_SIZE (1<<23) 89 | #define align(x, y) (((x) + ((y)-1)) & ~((y)-1)) 90 | 91 | size = align (size, 2 * sizeof (void *)); 92 | if (size > rem || rem == 0) { 93 | ptr = malloc (SUPERBLOCK_SIZE); 94 | if (ptr == NULL) 95 | exit (1); 96 | rem = SUPERBLOCK_SIZE; 97 | } 98 | 99 | #undef SUPERBLOCK_SIZE 100 | #undef align 101 | 102 | ret = ptr; 103 | rem -= size; 104 | ptr += size; 105 | 106 | return ret; 107 | } 108 | 109 | static void 110 | resolve_addrs (struct func_stat_t *func_stats, int num) 111 | { 112 | int i; 113 | void **addrs; 114 | char **strings; 115 | 116 | addrs = malloc (num * sizeof (void *)); 117 | for (i = 0; i < num; i++) 118 | addrs[i] = (void *) func_stats[i].addr; 119 | 120 | strings = backtrace_symbols (addrs, num); 121 | 122 | for (i = 0; i < num; i++) { 123 | char *p; 124 | char *name; 125 | int len; 126 | 127 | p = strchr (strings[i], '\t'); 128 | if (p) 129 | p++; 130 | else 131 | p = strings[i]; 132 | 133 | len = strlen (p) + 1; 134 | name = _perm_alloc (len); 135 | memcpy (name, p, len); 136 | func_stats[i].name = name; 137 | } 138 | 139 | free (strings); 140 | free (addrs); 141 | } 142 | 143 | static void 144 | func_stats_add (const void *caller, int is_realloc, size_t size) 145 | { 146 | int i; 147 | struct func_stat_t *elt; 148 | 149 | alloc_stats_add (&total_allocations, is_realloc, size); 150 | 151 | i = ((uintptr_t) caller ^ 1215497) % ARRAY_LENGTH (func_stats); 152 | for (elt = func_stats[i]; elt != NULL; elt = elt->next) { 153 | if (elt->addr == caller) 154 | break; 155 | } 156 | 157 | if (elt == NULL) { 158 | func_stats_num++; 159 | 160 | elt = _perm_alloc (sizeof (struct func_stat_t)); 161 | elt->next = func_stats[i]; 162 | func_stats[i] = elt; 163 | elt->addr = caller; 164 | elt->name = NULL; 165 | memset (&elt->stat, 0, sizeof (struct alloc_stats_t)); 166 | } 167 | 168 | alloc_stats_add (&elt->stat, is_realloc, size); 169 | } 170 | 171 | /* wrapper stuff */ 172 | 173 | #include 174 | 175 | static void *(*old_malloc)(size_t); 176 | static int (*old_posix_memalign)(void **, size_t, size_t); 177 | static void *(*old_calloc)(size_t, size_t); 178 | static void *(*old_realloc)(void *, size_t); 179 | static int enable_hook = 0; 180 | 181 | static void init(void); 182 | 183 | void * 184 | malloc(size_t size) 185 | { 186 | if (!old_malloc) 187 | init (); 188 | 189 | if (enable_hook) { 190 | enable_hook = 0; 191 | void *caller = __builtin_return_address(0); 192 | func_stats_add (caller, 0, size); 193 | enable_hook = 1; 194 | } 195 | 196 | return old_malloc (size); 197 | } 198 | 199 | int 200 | posix_memalign(void **memptr, size_t alignment, size_t size) 201 | { 202 | if (!old_posix_memalign) 203 | init (); 204 | 205 | if (enable_hook) { 206 | enable_hook = 0; 207 | void *caller = __builtin_return_address(0); 208 | func_stats_add (caller, 0, size); 209 | enable_hook = 1; 210 | } 211 | 212 | return old_posix_memalign (memptr, alignment, size); 213 | } 214 | 215 | void * 216 | calloc(size_t nmemb, size_t size) 217 | { 218 | if (!old_calloc) 219 | init (); 220 | 221 | if (enable_hook) { 222 | enable_hook = 0; 223 | void *caller = __builtin_return_address(0); 224 | func_stats_add (caller, 0, nmemb * size); 225 | enable_hook = 1; 226 | } 227 | 228 | return old_calloc (nmemb, size); 229 | } 230 | 231 | void * 232 | realloc(void *ptr, size_t size) 233 | { 234 | if (!old_malloc) 235 | init (); 236 | 237 | if (enable_hook) { 238 | enable_hook = 0; 239 | void *caller = __builtin_return_address(0); 240 | func_stats_add (caller, 1, size); 241 | enable_hook = 1; 242 | } 243 | 244 | return old_realloc (ptr, size); 245 | } 246 | 247 | static void 248 | init(void) 249 | { 250 | old_malloc = dlsym(RTLD_NEXT, "malloc"); 251 | if (!old_malloc) { 252 | fprintf(stderr, "%s\n", dlerror()); 253 | exit(1); 254 | } 255 | old_posix_memalign = dlsym(RTLD_NEXT, "posix_memalign"); 256 | if (!old_posix_memalign) { 257 | fprintf(stderr, "%s\n", dlerror()); 258 | exit(1); 259 | } 260 | old_calloc = dlsym(RTLD_NEXT, "calloc"); 261 | if (!old_calloc) { 262 | fprintf(stderr, "%s\n", dlerror()); 263 | exit(1); 264 | } 265 | old_realloc = dlsym(RTLD_NEXT, "realloc"); 266 | if (!old_realloc) { 267 | fprintf(stderr, "%s\n", dlerror()); 268 | exit(1); 269 | } 270 | enable_hook = 1; 271 | } 272 | 273 | /* reporting */ 274 | 275 | #include 276 | 277 | static void 278 | add_alloc_stats (struct alloc_stats_t *a, struct alloc_stats_t *b) 279 | { 280 | a->total.num += b->total.num; 281 | a->total.size += b->total.size; 282 | a->malloc.num += b->malloc.num; 283 | a->malloc.size += b->malloc.size; 284 | a->realloc.num += b->realloc.num; 285 | a->realloc.size += b->realloc.size; 286 | } 287 | 288 | static void 289 | dump_alloc_stats (struct alloc_stats_t *stats, const char *name) 290 | { 291 | printf ("%8u %'11llu %8u %'11llu %8u %'11llu %s\n", 292 | stats->total.num, stats->total.size, 293 | stats->malloc.num, stats->malloc.size, 294 | stats->realloc.num, stats->realloc.size, 295 | name); 296 | } 297 | 298 | static int 299 | compare_func_stats_name (const void *pa, const void *pb) 300 | { 301 | const struct func_stat_t *a = pa, *b = pb; 302 | int i; 303 | 304 | i = strcmp (a->name, b->name); 305 | if (i) 306 | return i; 307 | 308 | return ((char *) a->addr - (char *) b->addr); 309 | } 310 | 311 | static int 312 | compare_func_stats (const void *pa, const void *pb) 313 | { 314 | const struct func_stat_t *a = pa, *b = pb; 315 | 316 | if (a->stat.total.num != b->stat.total.num) 317 | return (a->stat.total.num - b->stat.total.num); 318 | 319 | if (a->stat.total.size != b->stat.total.size) 320 | return (a->stat.total.size - b->stat.total.size); 321 | 322 | return compare_func_stats_name (pa, pb); 323 | } 324 | 325 | static int 326 | merge_similar_entries (struct func_stat_t *func_stats, int num) 327 | { 328 | int i, j; 329 | 330 | j = 0; 331 | for (i = 1; i < num; i++) { 332 | if (i != j && 0 == strcmp (func_stats[i].name, func_stats[j].name)) { 333 | add_alloc_stats (&func_stats[j].stat, &func_stats[i].stat); 334 | } else { 335 | j++; 336 | if (i != j) 337 | func_stats[j] = func_stats[i]; 338 | } 339 | } 340 | j++; 341 | 342 | return j; 343 | } 344 | 345 | __attribute__ ((destructor)) 346 | static void 347 | malloc_stats (void) 348 | { 349 | unsigned int i, j; 350 | struct func_stat_t *sorted_func_stats; 351 | 352 | enable_hook = 0; 353 | 354 | if (! func_stats_num) 355 | return; 356 | 357 | sorted_func_stats = malloc (sizeof (struct func_stat_t) * (func_stats_num + 1)); 358 | if (sorted_func_stats == NULL) 359 | return; 360 | 361 | j = 0; 362 | for (i = 0; i < ARRAY_LENGTH (func_stats); i++) { 363 | struct func_stat_t *elt; 364 | for (elt = func_stats[i]; elt != NULL; elt = elt->next) 365 | sorted_func_stats[j++] = *elt; 366 | } 367 | 368 | resolve_addrs (sorted_func_stats, j); 369 | 370 | /* merge entries with same name */ 371 | qsort (sorted_func_stats, j, 372 | sizeof (struct func_stat_t), compare_func_stats_name); 373 | j = merge_similar_entries (sorted_func_stats, j); 374 | 375 | qsort (sorted_func_stats, j, 376 | sizeof (struct func_stat_t), compare_func_stats); 377 | 378 | /* add total */ 379 | sorted_func_stats[j].next = NULL; 380 | sorted_func_stats[j].addr = (void *) -1; 381 | sorted_func_stats[j].name = "(total)"; 382 | sorted_func_stats[j].stat = total_allocations; 383 | j++; 384 | 385 | setlocale (LC_ALL, ""); 386 | 387 | printf (" TOTAL MALLOC REALLOC\n"); 388 | printf (" num size num size num size\n"); 389 | 390 | for (i = 0; i < j; i++) { 391 | dump_alloc_stats (&sorted_func_stats[i].stat, 392 | sorted_func_stats[i].name); 393 | } 394 | 395 | /* XXX free other stuff? */ 396 | 397 | free (sorted_func_stats); 398 | } 399 | --------------------------------------------------------------------------------