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