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         int r = random();
185
186         if (alloc_fail_proba != 0 && (r % 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 [rand=%d]\n",
207                 file, line, size, ret, r);
208         if (r == 976499400)
209                 printf("here\n");
210
211         if (ret)
212                 alloc_success++;
213         return ret;
214 }
215
216 static void debug_free(void *ptr, const char *file, unsigned int line)
217 {
218         struct debug_alloc_hdr *hdr, *h;
219         struct debug_alloc_ftr *ftr;
220
221         (void)file;
222         (void)line;
223
224         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
225
226         if (ptr == NULL)
227                 return;
228
229         hdr = (ptr - sizeof(*hdr));
230         if (hdr->cookie != 0x12345678) {
231                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
232                         file, line, ptr);
233                 abort();
234         }
235
236         ftr = (ptr + hdr->size);
237         if (ftr->cookie != 0x87654321) {
238                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
239                         file, line, ptr);
240                 abort();
241         }
242
243         TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
244                 if (h == hdr)
245                         break;
246         }
247
248         if (h == NULL) {
249                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
250                         file, line, ptr);
251                 abort();
252         }
253
254         TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
255         free(hdr);
256 }
257
258 static void *debug_realloc(void *ptr, size_t size, const char *file,
259         unsigned int line)
260 {
261         struct debug_alloc_hdr *hdr, *h;
262         struct debug_alloc_ftr *ftr;
263         size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
264         void *ret;
265
266         if (ptr != NULL) {
267                 hdr =  (ptr - sizeof(*hdr));
268                 if (hdr->cookie != 0x12345678) {
269                         EC_LOG(EC_LOG_ERR,
270                                 "%s:%d: error: realloc(%p): bad start cookie\n",
271                                 file, line, ptr);
272                         abort();
273                 }
274
275                 ftr = (ptr + hdr->size);
276                 if (ftr->cookie != 0x87654321) {
277                         EC_LOG(EC_LOG_ERR,
278                                 "%s:%d: error: realloc(%p): bad end cookie\n",
279                                 file, line, ptr);
280                         abort();
281                 }
282
283                 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
284                         if (h == hdr)
285                                 break;
286                 }
287
288                 if (h == NULL) {
289                         EC_LOG(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
290                                 file, line, ptr);
291                         abort();
292                 }
293
294                 TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
295                 hdr = realloc(hdr, new_size);
296                 if (hdr == NULL) {
297                         TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
298                         ret = NULL;
299                 } else {
300                         ret = hdr + 1;
301                 }
302         } else {
303                 hdr = realloc(NULL, new_size);
304                 if (hdr == NULL)
305                         ret = NULL;
306                 else
307                         ret = hdr + 1;
308         }
309
310         if (hdr != NULL) {
311                 hdr->file = file;
312                 hdr->line = line;
313                 hdr->size = size;
314                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
315                 hdr->cookie = 0x12345678;
316                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
317                 ftr = (struct debug_alloc_ftr *)(
318                         (char *)hdr + size + sizeof(*hdr));
319                 ftr->cookie = 0x87654321;
320         }
321
322         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p\n",
323                 file, line, ptr, size, ret);
324
325         if (ret)
326                 alloc_success++;
327         return ret;
328 }
329
330 static int debug_alloc_dump_leaks(void)
331 {
332         struct debug_alloc_hdr *hdr;
333         int i;
334         char **buffer;
335
336         EC_LOG(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
337
338         if (TAILQ_EMPTY(&debug_alloc_hdr_list))
339                 return 0;
340
341         TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) {
342                 EC_LOG(EC_LOG_ERR,
343                         "%s:%d: error: memory leak size=%zd ptr=%p\n",
344                         hdr->file, hdr->line, hdr->size, hdr + 1);
345                 buffer = backtrace_symbols(hdr->stack, hdr->stacklen);
346                 if (buffer == NULL) {
347                         for (i = 0; i < hdr->stacklen; i++)
348                                 EC_LOG(EC_LOG_ERR, "  %p\n", hdr->stack[i]);
349                 } else {
350                         for (i = 0; i < hdr->stacklen; i++)
351                                 EC_LOG(EC_LOG_ERR, "  %s\n",
352                                         buffer ? buffer[i] : "unknown");
353                 }
354                 free(buffer);
355         }
356
357         EC_LOG(EC_LOG_ERR,
358                 "  missing static syms, use: addr2line -f -e <prog> <addr>\n");
359
360         return -1;
361 }
362
363 static int debug_log(int type, unsigned int level, void *opaque,
364                 const char *str)
365 {
366         (void)type;
367         (void)opaque;
368
369         if (level > (unsigned int)log_level)
370                 return 0;
371
372         if (printf("%s", str) < 0)
373                 return -1;
374
375         return 0;
376 }
377
378 int main(int argc, char **argv)
379 {
380         int i, ret = 0, leaks;
381
382         ret = parse_args(argc, argv);
383         if (ret < 0)
384                 return 1;
385
386         argc -= ret;
387         argv += ret;
388
389         srandom(seed);
390
391         /* register a new malloc to track memleaks */
392         TAILQ_INIT(&debug_alloc_hdr_list);
393         if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) {
394                 EC_LOG(EC_LOG_ERR, "cannot register new malloc\n");
395                 return 1;
396         }
397
398         if (ec_init() < 0) {
399                 fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
400                 return 1;
401         }
402         ec_log_fct_register(debug_log, NULL);
403
404         ret = 0;
405         if (argc <= 1) {
406                 ret = ec_test_all();
407         } else {
408                 for (i = 1; i < argc; i++)
409                         ret |= ec_test_one(argv[i]);
410         }
411
412         leaks = debug_alloc_dump_leaks();
413
414         if (alloc_fail_proba == 0 && ret != 0) {
415                 printf("tests failed\n");
416                 return 1;
417         } else if (alloc_fail_proba != 0 && leaks != 0) {
418                 printf("tests failed (memory leak)\n");
419                 return 1;
420         }
421
422         printf("\ntests ok\n");
423
424         return 0;
425 }