alloc stats
[protos/libecoli.git] / lib / main.c
1 /*
2  * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <assert.h>
31 #include <getopt.h>
32 #include <limits.h>
33 #include <execinfo.h>
34
35 #include <ecoli_log.h>
36 #include <ecoli_test.h>
37 #include <ecoli_malloc.h>
38
39 #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
40                 ((size_t)(!(sizeof(x) % sizeof(0[x])))))
41
42 static int log_level = EC_LOG_INFO;
43 static int alloc_fail_proba = 0;
44 static int seed = 0;
45 static size_t alloc_success = 0;
46
47 static const char ec_short_options[] =
48         "h"  /* help */
49         "l:" /* log-level */
50         "r:" /* random-alloc-fail */
51         "s:" /* seed */
52         ;
53
54 #define EC_OPT_HELP "help"
55 #define EC_OPT_LOG_LEVEL "log-level"
56 #define EC_OPT_RANDOM_ALLOC_FAIL "random-alloc-fail"
57 #define EC_OPT_SEED "seed"
58
59 static const struct option ec_long_options[] = {
60         {EC_OPT_HELP, 1, NULL, 'h'},
61         {EC_OPT_LOG_LEVEL, 1, NULL, 'l'},
62         {EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'},
63         {EC_OPT_SEED, 1, NULL, 's'},
64         {NULL, 0, NULL, 0}
65 };
66
67 static void usage(const char *prgname)
68 {
69         printf("%s [options] [test1 test2 test3...]\n"
70                 "  -h\n"
71                 "  --"EC_OPT_HELP"\n"
72                 "      Show this help.\n"
73                 "  -l <level>\n"
74                 "  --"EC_OPT_LOG_LEVEL"=<level>\n"
75                 "      Set log level (0 = no log, 7 = verbose).\n"
76                 "  -r <probability>\n"
77                 "  --"EC_OPT_RANDOM_ALLOC_FAIL"=<probability>\n"
78                 "      Cause malloc to fail randomly. This helps to debug\n"
79                 "      leaks or crashes in error cases. The probability is\n"
80                 "      between 0 and 100.\n"
81                 "  -s <seed>\n"
82                 "  --seed=<seed>\n"
83                 "      Seeds the random number generator. Default is 0.\n"
84                 , prgname);
85 }
86
87 static int
88 parse_int(const char *s, int min, int max, int *ret, unsigned int base)
89 {
90         char *end = NULL;
91         long long n;
92
93         n = strtoll(s, &end, base);
94         if ((s[0] == '\0') || (end == NULL) || (*end != '\0'))
95                 return -1;
96         if (n < min)
97                 return -1;
98         if (n > max)
99                 return -1;
100
101         *ret = n;
102         return 0;
103 }
104
105 static int parse_args(int argc, char **argv)
106 {
107         int ret, opt;
108
109         while ((opt = getopt_long(argc, argv, ec_short_options,
110                                 ec_long_options, NULL)) != EOF) {
111
112                 switch (opt) {
113                 case 'h': /* help */
114                         usage(argv[0]);
115                         exit(0);
116
117                 case 'l': /* log-level */
118                         if (parse_int(optarg, EC_LOG_EMERG,
119                                         EC_LOG_DEBUG, &log_level, 10) < 0) {
120                                 printf("Invalid log value\n");
121                                 usage(argv[0]);
122                                 exit(1);
123                         }
124                         break;
125
126                 case 'r': /* random-alloc-fail */
127                         if (parse_int(optarg, 0, 100, &alloc_fail_proba,
128                                         10) < 0) {
129                                 printf("Invalid probability value\n");
130                                 usage(argv[0]);
131                                 exit(1);
132                         }
133                         break;
134
135                 case 's': /* seed */
136                         if (parse_int(optarg, 0, INT_MAX, &seed, 10) < 0) {
137                                 printf("Invalid seed value\n");
138                                 usage(argv[0]);
139                                 exit(1);
140                         }
141                         break;
142
143                 default:
144                         usage(argv[0]);
145                         return -1;
146                 }
147         }
148
149         ret = optind - 1;
150         optind = 1;
151
152         return ret;
153 }
154
155 TAILQ_HEAD(debug_alloc_hdr_list, debug_alloc_hdr);
156 static struct debug_alloc_hdr_list debug_alloc_hdr_list =
157         TAILQ_HEAD_INITIALIZER(debug_alloc_hdr_list);
158
159 #define STACK_SZ 16
160 struct debug_alloc_hdr {
161         TAILQ_ENTRY(debug_alloc_hdr) next;
162         const char *file;
163         unsigned int line;
164         size_t size;
165         void *stack[STACK_SZ];
166         int stacklen;
167         unsigned int cookie;
168 };
169
170 struct debug_alloc_ftr {
171         unsigned int cookie;
172 } __attribute__((packed));
173
174 static void *debug_malloc(size_t size, const char *file, unsigned int line)
175 {
176         struct debug_alloc_hdr *hdr;
177         struct debug_alloc_ftr *ftr;
178         size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
179         void *ret;
180
181
182         if (alloc_fail_proba != 0 && (random() % 100) < alloc_fail_proba)
183                 hdr = NULL;
184         else
185                 hdr = malloc(new_size);
186
187         if (hdr == NULL) {
188                 ret = NULL;
189         } else {
190                 hdr->file = file;
191                 hdr->line = line;
192                 hdr->size = size;
193                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
194                 hdr->cookie = 0x12345678;
195                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
196                 ret = hdr + 1;
197                 ftr = (struct debug_alloc_ftr *)(
198                         (char *)hdr + size + sizeof(*hdr));
199                 ftr->cookie = 0x87654321;
200         }
201
202         ec_log(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p\n",
203                 file, line, size, ret);
204
205         if (ret)
206                 alloc_success++;
207         return ret;
208 }
209
210 static void debug_free(void *ptr, const char *file, unsigned int line)
211 {
212         struct debug_alloc_hdr *hdr, *h;
213         struct debug_alloc_ftr *ftr;
214
215         (void)file;
216         (void)line;
217
218         ec_log(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
219
220         if (ptr == NULL)
221                 return;
222
223         hdr = (ptr - sizeof(*hdr));
224         if (hdr->cookie != 0x12345678) {
225                 ec_log(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
226                         file, line, ptr);
227                 abort();
228         }
229
230         ftr = (ptr + hdr->size);
231         if (ftr->cookie != 0x87654321) {
232                 ec_log(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
233                         file, line, ptr);
234                 abort();
235         }
236
237         TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
238                 if (h == hdr)
239                         break;
240         }
241
242         if (h == NULL) {
243                 ec_log(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
244                         file, line, ptr);
245                 abort();
246         }
247
248         TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
249         free(hdr);
250 }
251
252 static void *debug_realloc(void *ptr, size_t size, const char *file,
253         unsigned int line)
254 {
255         struct debug_alloc_hdr *hdr, *h;
256         struct debug_alloc_ftr *ftr;
257         size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
258         void *ret;
259
260         if (ptr != NULL) {
261                 hdr =  (ptr - sizeof(*hdr));
262                 if (hdr->cookie != 0x12345678) {
263                         ec_log(EC_LOG_ERR,
264                                 "%s:%d: error: realloc(%p): bad start cookie\n",
265                                 file, line, ptr);
266                         abort();
267                 }
268
269                 ftr = (ptr + hdr->size);
270                 if (ftr->cookie != 0x87654321) {
271                         ec_log(EC_LOG_ERR,
272                                 "%s:%d: error: realloc(%p): bad end cookie\n",
273                                 file, line, ptr);
274                         abort();
275                 }
276
277                 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
278                         if (h == hdr)
279                                 break;
280                 }
281
282                 if (h == NULL) {
283                         ec_log(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
284                                 file, line, ptr);
285                         abort();
286                 }
287
288                 TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
289                 hdr = realloc(hdr, new_size);
290                 if (hdr == NULL) {
291                         TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
292                         ret = NULL;
293                 } else {
294                         ret = hdr + 1;
295                 }
296         } else {
297                 hdr = realloc(NULL, new_size);
298                 if (hdr == NULL)
299                         ret = NULL;
300                 else
301                         ret = hdr + 1;
302         }
303
304         if (hdr != NULL) {
305                 hdr->file = file;
306                 hdr->line = line;
307                 hdr->size = size;
308                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
309                 hdr->cookie = 0x12345678;
310                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
311                 ftr = (struct debug_alloc_ftr *)(
312                         (char *)hdr + size + sizeof(*hdr));
313                 ftr->cookie = 0x87654321;
314         }
315
316         ec_log(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p\n",
317                 file, line, ptr, size, ret);
318
319         if (ret)
320                 alloc_success++;
321         return ret;
322 }
323
324 static int debug_alloc_dump_leaks(void)
325 {
326         struct debug_alloc_hdr *hdr;
327         int i;
328         char **buffer;
329
330         ec_log(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
331
332         if (TAILQ_EMPTY(&debug_alloc_hdr_list))
333                 return 0;
334
335         TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) {
336                 ec_log(EC_LOG_ERR,
337                         "%s:%d: error: memory leak size=%zd ptr=%p\n",
338                         hdr->file, hdr->line, hdr->size, hdr + 1);
339                 buffer = backtrace_symbols(hdr->stack, hdr->stacklen);
340                 if (buffer == NULL) {
341                         for (i = 0; i < hdr->stacklen; i++)
342                                 ec_log(EC_LOG_ERR, "  %p\n", hdr->stack[i]);
343                 } else {
344                         for (i = 0; i < hdr->stacklen; i++)
345                                 ec_log(EC_LOG_ERR, "  %s\n",
346                                         buffer ? buffer[i] : "unknown");
347                 }
348                 free(buffer);
349         }
350
351         ec_log(EC_LOG_ERR,
352                 "  missing static syms, use: addr2line -f -e <prog> <addr>\n");
353
354         return -1;
355 }
356
357 static int debug_log(unsigned int level, void *opaque, const char *str)
358 {
359         (void)opaque;
360
361         if (level > (unsigned int)log_level)
362                 return 0;
363
364         return printf("%s", str);
365 }
366
367 int main(int argc, char **argv)
368 {
369         int i, ret = 0, leaks;
370
371         ret = parse_args(argc, argv);
372         if (ret < 0)
373                 return 1;
374
375         argc -= ret;
376         argv += ret;
377
378         srandom(seed);
379
380         ec_log_register(debug_log, NULL);
381
382         /* register a new malloc to track memleaks */
383         TAILQ_INIT(&debug_alloc_hdr_list);
384         if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) {
385                 ec_log(EC_LOG_ERR, "cannot register new malloc\n");
386                 return -1;
387         }
388
389         ret = 0;
390         if (argc <= 1) {
391                 ret = ec_test_all();
392         } else {
393                 for (i = 1; i < argc; i++)
394                         ret |= ec_test_one(argv[i]);
395         }
396
397         ec_malloc_unregister();
398         leaks = debug_alloc_dump_leaks();
399
400         if (alloc_fail_proba == 0 && ret != 0) {
401                 printf("tests failed\n");
402                 return 1;
403         } else if (alloc_fail_proba != 0 && leaks != 0) {
404                 printf("tests failed (memory leak)\n");
405                 return 1;
406         }
407
408         printf("\ntests ok\n");
409
410         return 0;
411 }