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