dynamic log types
[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 EC_LOG_TYPE_REGISTER(main);
40
41 #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
42                 ((size_t)(!(sizeof(x) % sizeof(0[x])))))
43
44 static int log_level = EC_LOG_INFO;
45 static int alloc_fail_proba = 0;
46 static int seed = 0;
47 static size_t alloc_success = 0;
48
49 static const char ec_short_options[] =
50         "h"  /* help */
51         "l:" /* log-level */
52         "r:" /* random-alloc-fail */
53         "s:" /* seed */
54         ;
55
56 #define EC_OPT_HELP "help"
57 #define EC_OPT_LOG_LEVEL "log-level"
58 #define EC_OPT_RANDOM_ALLOC_FAIL "random-alloc-fail"
59 #define EC_OPT_SEED "seed"
60
61 static const struct option ec_long_options[] = {
62         {EC_OPT_HELP, 1, NULL, 'h'},
63         {EC_OPT_LOG_LEVEL, 1, NULL, 'l'},
64         {EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'},
65         {EC_OPT_SEED, 1, NULL, 's'},
66         {NULL, 0, NULL, 0}
67 };
68
69 static void usage(const char *prgname)
70 {
71         printf("%s [options] [test1 test2 test3...]\n"
72                 "  -h\n"
73                 "  --"EC_OPT_HELP"\n"
74                 "      Show this help.\n"
75                 "  -l <level>\n"
76                 "  --"EC_OPT_LOG_LEVEL"=<level>\n"
77                 "      Set log level (0 = no log, 7 = verbose).\n"
78                 "  -r <probability>\n"
79                 "  --"EC_OPT_RANDOM_ALLOC_FAIL"=<probability>\n"
80                 "      Cause malloc to fail randomly. This helps to debug\n"
81                 "      leaks or crashes in error cases. The probability is\n"
82                 "      between 0 and 100.\n"
83                 "  -s <seed>\n"
84                 "  --seed=<seed>\n"
85                 "      Seeds the random number generator. Default is 0.\n"
86                 , prgname);
87 }
88
89 static int
90 parse_int(const char *s, int min, int max, int *ret, unsigned int base)
91 {
92         char *end = NULL;
93         long long n;
94
95         n = strtoll(s, &end, base);
96         if ((s[0] == '\0') || (end == NULL) || (*end != '\0'))
97                 return -1;
98         if (n < min)
99                 return -1;
100         if (n > max)
101                 return -1;
102
103         *ret = n;
104         return 0;
105 }
106
107 static int parse_args(int argc, char **argv)
108 {
109         int ret, opt;
110
111         while ((opt = getopt_long(argc, argv, ec_short_options,
112                                 ec_long_options, NULL)) != EOF) {
113
114                 switch (opt) {
115                 case 'h': /* help */
116                         usage(argv[0]);
117                         exit(0);
118
119                 case 'l': /* log-level */
120                         if (parse_int(optarg, EC_LOG_EMERG,
121                                         EC_LOG_DEBUG, &log_level, 10) < 0) {
122                                 printf("Invalid log value\n");
123                                 usage(argv[0]);
124                                 exit(1);
125                         }
126                         break;
127
128                 case 'r': /* random-alloc-fail */
129                         if (parse_int(optarg, 0, 100, &alloc_fail_proba,
130                                         10) < 0) {
131                                 printf("Invalid probability value\n");
132                                 usage(argv[0]);
133                                 exit(1);
134                         }
135                         break;
136
137                 case 's': /* seed */
138                         if (parse_int(optarg, 0, INT_MAX, &seed, 10) < 0) {
139                                 printf("Invalid seed value\n");
140                                 usage(argv[0]);
141                                 exit(1);
142                         }
143                         break;
144
145                 default:
146                         usage(argv[0]);
147                         return -1;
148                 }
149         }
150
151         ret = optind - 1;
152         optind = 1;
153
154         return ret;
155 }
156
157 TAILQ_HEAD(debug_alloc_hdr_list, debug_alloc_hdr);
158 static struct debug_alloc_hdr_list debug_alloc_hdr_list =
159         TAILQ_HEAD_INITIALIZER(debug_alloc_hdr_list);
160
161 #define STACK_SZ 16
162 struct debug_alloc_hdr {
163         TAILQ_ENTRY(debug_alloc_hdr) next;
164         const char *file;
165         unsigned int line;
166         size_t size;
167         void *stack[STACK_SZ];
168         int stacklen;
169         unsigned int cookie;
170 };
171
172 struct debug_alloc_ftr {
173         unsigned int cookie;
174 } __attribute__((packed));
175
176 static void *debug_malloc(size_t size, const char *file, unsigned int line)
177 {
178         struct debug_alloc_hdr *hdr;
179         struct debug_alloc_ftr *ftr;
180         size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
181         void *ret;
182
183
184         if (alloc_fail_proba != 0 && (random() % 100) < alloc_fail_proba)
185                 hdr = NULL;
186         else
187                 hdr = malloc(new_size);
188
189         if (hdr == NULL) {
190                 ret = NULL;
191         } else {
192                 hdr->file = file;
193                 hdr->line = line;
194                 hdr->size = size;
195                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
196                 hdr->cookie = 0x12345678;
197                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
198                 ret = hdr + 1;
199                 ftr = (struct debug_alloc_ftr *)(
200                         (char *)hdr + size + sizeof(*hdr));
201                 ftr->cookie = 0x87654321;
202         }
203
204         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p\n",
205                 file, line, size, ret);
206
207         if (ret)
208                 alloc_success++;
209         return ret;
210 }
211
212 static void debug_free(void *ptr, const char *file, unsigned int line)
213 {
214         struct debug_alloc_hdr *hdr, *h;
215         struct debug_alloc_ftr *ftr;
216
217         (void)file;
218         (void)line;
219
220         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
221
222         if (ptr == NULL)
223                 return;
224
225         hdr = (ptr - sizeof(*hdr));
226         if (hdr->cookie != 0x12345678) {
227                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
228                         file, line, ptr);
229                 abort();
230         }
231
232         ftr = (ptr + hdr->size);
233         if (ftr->cookie != 0x87654321) {
234                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
235                         file, line, ptr);
236                 abort();
237         }
238
239         TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
240                 if (h == hdr)
241                         break;
242         }
243
244         if (h == NULL) {
245                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
246                         file, line, ptr);
247                 abort();
248         }
249
250         TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
251         free(hdr);
252 }
253
254 static void *debug_realloc(void *ptr, size_t size, const char *file,
255         unsigned int line)
256 {
257         struct debug_alloc_hdr *hdr, *h;
258         struct debug_alloc_ftr *ftr;
259         size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
260         void *ret;
261
262         if (ptr != NULL) {
263                 hdr =  (ptr - sizeof(*hdr));
264                 if (hdr->cookie != 0x12345678) {
265                         EC_LOG(EC_LOG_ERR,
266                                 "%s:%d: error: realloc(%p): bad start cookie\n",
267                                 file, line, ptr);
268                         abort();
269                 }
270
271                 ftr = (ptr + hdr->size);
272                 if (ftr->cookie != 0x87654321) {
273                         EC_LOG(EC_LOG_ERR,
274                                 "%s:%d: error: realloc(%p): bad end cookie\n",
275                                 file, line, ptr);
276                         abort();
277                 }
278
279                 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
280                         if (h == hdr)
281                                 break;
282                 }
283
284                 if (h == NULL) {
285                         EC_LOG(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
286                                 file, line, ptr);
287                         abort();
288                 }
289
290                 TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
291                 hdr = realloc(hdr, new_size);
292                 if (hdr == NULL) {
293                         TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
294                         ret = NULL;
295                 } else {
296                         ret = hdr + 1;
297                 }
298         } else {
299                 hdr = realloc(NULL, new_size);
300                 if (hdr == NULL)
301                         ret = NULL;
302                 else
303                         ret = hdr + 1;
304         }
305
306         if (hdr != NULL) {
307                 hdr->file = file;
308                 hdr->line = line;
309                 hdr->size = size;
310                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
311                 hdr->cookie = 0x12345678;
312                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
313                 ftr = (struct debug_alloc_ftr *)(
314                         (char *)hdr + size + sizeof(*hdr));
315                 ftr->cookie = 0x87654321;
316         }
317
318         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p\n",
319                 file, line, ptr, size, ret);
320
321         if (ret)
322                 alloc_success++;
323         return ret;
324 }
325
326 static int debug_alloc_dump_leaks(void)
327 {
328         struct debug_alloc_hdr *hdr;
329         int i;
330         char **buffer;
331
332         EC_LOG(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
333
334         if (TAILQ_EMPTY(&debug_alloc_hdr_list))
335                 return 0;
336
337         TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) {
338                 EC_LOG(EC_LOG_ERR,
339                         "%s:%d: error: memory leak size=%zd ptr=%p\n",
340                         hdr->file, hdr->line, hdr->size, hdr + 1);
341                 buffer = backtrace_symbols(hdr->stack, hdr->stacklen);
342                 if (buffer == NULL) {
343                         for (i = 0; i < hdr->stacklen; i++)
344                                 EC_LOG(EC_LOG_ERR, "  %p\n", hdr->stack[i]);
345                 } else {
346                         for (i = 0; i < hdr->stacklen; i++)
347                                 EC_LOG(EC_LOG_ERR, "  %s\n",
348                                         buffer ? buffer[i] : "unknown");
349                 }
350                 free(buffer);
351         }
352
353         EC_LOG(EC_LOG_ERR,
354                 "  missing static syms, use: addr2line -f -e <prog> <addr>\n");
355
356         return -1;
357 }
358
359 static int debug_log(int type, unsigned int level, void *opaque,
360                 const char *str)
361 {
362         (void)type;
363         (void)opaque;
364
365         if (level > (unsigned int)log_level)
366                 return 0;
367
368         return printf("%s", str);
369 }
370
371 int main(int argc, char **argv)
372 {
373         int i, ret = 0, leaks;
374
375         ret = parse_args(argc, argv);
376         if (ret < 0)
377                 return 1;
378
379         argc -= ret;
380         argv += ret;
381
382         srandom(seed);
383
384         if (0) ec_log_fct_register(debug_log, NULL);
385
386         /* register a new malloc to track memleaks */
387         TAILQ_INIT(&debug_alloc_hdr_list);
388         if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) {
389                 EC_LOG(EC_LOG_ERR, "cannot register new malloc\n");
390                 return -1;
391         }
392
393         ret = 0;
394         if (argc <= 1) {
395                 ret = ec_test_all();
396         } else {
397                 for (i = 1; i < argc; i++)
398                         ret |= ec_test_one(argv[i]);
399         }
400
401         ec_malloc_unregister();
402         leaks = debug_alloc_dump_leaks();
403
404         if (alloc_fail_proba == 0 && ret != 0) {
405                 printf("tests failed\n");
406                 return 1;
407         } else if (alloc_fail_proba != 0 && leaks != 0) {
408                 printf("tests failed (memory leak)\n");
409                 return 1;
410         }
411
412         printf("\ntests ok\n");
413
414         return 0;
415 }