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