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