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