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 void parse_args(int argc, char **argv)
106 {
107         int 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                         exit(1);
146                 }
147         }
148 }
149
150 TAILQ_HEAD(debug_alloc_hdr_list, debug_alloc_hdr);
151 static struct debug_alloc_hdr_list debug_alloc_hdr_list =
152         TAILQ_HEAD_INITIALIZER(debug_alloc_hdr_list);
153
154 #define STACK_SZ 16
155 struct debug_alloc_hdr {
156         TAILQ_ENTRY(debug_alloc_hdr) next;
157         const char *file;
158         unsigned int line;
159         size_t size;
160         void *stack[STACK_SZ];
161         int stacklen;
162         unsigned int cookie;
163 };
164
165 struct debug_alloc_ftr {
166         unsigned int cookie;
167 } __attribute__((packed));
168
169 static void *debug_malloc(size_t size, const char *file, unsigned int line)
170 {
171         struct debug_alloc_hdr *hdr;
172         struct debug_alloc_ftr *ftr;
173         size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
174         void *ret;
175
176
177         if (alloc_fail_proba != 0 && (random() % 100) < alloc_fail_proba)
178                 hdr = NULL;
179         else
180                 hdr = malloc(new_size);
181
182         if (hdr == NULL) {
183                 ret = NULL;
184         } else {
185                 hdr->file = file;
186                 hdr->line = line;
187                 hdr->size = size;
188                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
189                 hdr->cookie = 0x12345678;
190                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
191                 ret = hdr + 1;
192                 ftr = (struct debug_alloc_ftr *)(
193                         (char *)hdr + size + sizeof(*hdr));
194                 ftr->cookie = 0x87654321;
195         }
196
197         ec_log(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p\n",
198                 file, line, size, ret);
199
200         return ret;
201 }
202
203 static void debug_free(void *ptr, const char *file, unsigned int line)
204 {
205         struct debug_alloc_hdr *hdr, *h;
206         struct debug_alloc_ftr *ftr;
207
208         (void)file;
209         (void)line;
210
211         ec_log(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
212
213         if (ptr == NULL)
214                 return;
215
216         hdr = (ptr - sizeof(*hdr));
217         if (hdr->cookie != 0x12345678) {
218                 ec_log(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
219                         file, line, ptr);
220                 abort();
221         }
222
223         ftr = (ptr + hdr->size);
224         if (ftr->cookie != 0x87654321) {
225                 ec_log(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
226                         file, line, ptr);
227                 abort();
228         }
229
230         TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
231                 if (h == hdr)
232                         break;
233         }
234
235         if (h == NULL) {
236                 ec_log(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
237                         file, line, ptr);
238                 abort();
239         }
240
241         TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
242         free(hdr);
243 }
244
245 static void *debug_realloc(void *ptr, size_t size, const char *file,
246         unsigned int line)
247 {
248         struct debug_alloc_hdr *hdr, *h;
249         struct debug_alloc_ftr *ftr;
250         size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
251         void *ret;
252
253         if (ptr != NULL) {
254                 hdr =  (ptr - sizeof(*hdr));
255                 if (hdr->cookie != 0x12345678) {
256                         ec_log(EC_LOG_ERR,
257                                 "%s:%d: error: realloc(%p): bad start cookie\n",
258                                 file, line, ptr);
259                         abort();
260                 }
261
262                 ftr = (ptr + hdr->size);
263                 if (ftr->cookie != 0x87654321) {
264                         ec_log(EC_LOG_ERR,
265                                 "%s:%d: error: realloc(%p): bad end cookie\n",
266                                 file, line, ptr);
267                         abort();
268                 }
269
270                 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
271                         if (h == hdr)
272                                 break;
273                 }
274
275                 if (h == NULL) {
276                         ec_log(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
277                                 file, line, ptr);
278                         abort();
279                 }
280
281                 TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
282                 hdr = realloc(hdr, new_size);
283                 if (hdr == NULL) {
284                         TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
285                         ret = NULL;
286                 } else {
287                         ret = hdr + 1;
288                 }
289         } else {
290                 hdr = realloc(NULL, new_size);
291                 if (hdr == NULL)
292                         ret = NULL;
293                 else
294                         ret = hdr + 1;
295         }
296
297         if (hdr != NULL) {
298                 hdr->file = file;
299                 hdr->line = line;
300                 hdr->size = size;
301                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
302                 hdr->cookie = 0x12345678;
303                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
304                 ftr = (struct debug_alloc_ftr *)(
305                         (char *)hdr + size + sizeof(*hdr));
306                 ftr->cookie = 0x87654321;
307         }
308
309         ec_log(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p\n",
310                 file, line, ptr, size, ret);
311
312         return ret;
313 }
314
315 static int debug_alloc_dump_leaks(void)
316 {
317         struct debug_alloc_hdr *hdr;
318         int i;
319         char **buffer;
320
321         if (TAILQ_EMPTY(&debug_alloc_hdr_list))
322                 return 0;
323
324         TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) {
325                 ec_log(EC_LOG_ERR,
326                         "%s:%d: error: memory leak size=%zd ptr=%p\n",
327                         hdr->file, hdr->line, hdr->size, hdr + 1);
328                 buffer = backtrace_symbols(hdr->stack, hdr->stacklen);
329                 if (buffer == NULL) {
330                         for (i = 0; i < hdr->stacklen; i++)
331                                 ec_log(EC_LOG_ERR, "  %p\n", hdr->stack[i]);
332                 } else {
333                         for (i = 0; i < hdr->stacklen; i++)
334                                 ec_log(EC_LOG_ERR, "  %s\n",
335                                         buffer ? buffer[i] : "unknown");
336                 }
337                 free(buffer);
338         }
339
340         ec_log(EC_LOG_ERR,
341                 "  missing static syms, use: addr2line -f -e <prog> <addr>\n");
342
343         return -1;
344 }
345
346 static int debug_log(unsigned int level, void *opaque, const char *str)
347 {
348         (void)opaque;
349
350         if (level > (unsigned int)log_level)
351                 return 0;
352
353         return printf("%s", str);
354 }
355
356 int main(int argc, char **argv)
357 {
358         int ret, leaks;
359
360         parse_args(argc, argv);
361         srandom(seed);
362
363         ec_log_register(debug_log, NULL);
364
365         /* register a new malloc to track memleaks */
366         TAILQ_INIT(&debug_alloc_hdr_list);
367         if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) {
368                 ec_log(EC_LOG_ERR, "cannot register new malloc\n");
369                 return -1;
370         }
371
372         ret = ec_test_all();
373
374         ec_malloc_unregister();
375         leaks = debug_alloc_dump_leaks();
376
377         if (alloc_fail_proba == 0 && ret != 0) {
378                 printf("tests failed\n");
379                 return 1;
380         } else if (alloc_fail_proba != 0 && leaks != 0) {
381                 printf("tests failed (memory leak)\n");
382                 return 1;
383         }
384
385         printf("\ntests ok\n");
386
387         return 0;
388 }