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