1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
13 #include <ecoli_init.h>
14 #include <ecoli_log.h>
15 #include <ecoli_test.h>
16 #include <ecoli_malloc.h>
19 EC_LOG_TYPE_REGISTER(main);
21 #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
22 ((size_t)(!(sizeof(x) % sizeof(0[x])))))
24 static int log_level = EC_LOG_INFO;
25 static int alloc_fail_proba = 0;
27 static size_t alloc_success = 0;
29 static const char ec_short_options[] =
32 "r:" /* random-alloc-fail */
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"
41 static const struct option ec_long_options[] = {
42 {EC_OPT_HELP, 0, 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'},
49 static void usage(const char *prgname)
51 fprintf(stderr, "%s [options] [test1 test2 test3...]\n"
56 " --"EC_OPT_LOG_LEVEL"=<level>\n"
57 " Set log level (0 = no log, 7 = verbose).\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"
65 " Seeds the random number generator. Default is 0.\n"
70 parse_int(const char *s, int min, int max, int *ret, unsigned int base)
75 n = strtoll(s, &end, base);
76 if ((s[0] == '\0') || (end == NULL) || (*end != '\0'))
87 static int parse_args(int argc, char **argv)
91 while ((opt = getopt_long(argc, argv, ec_short_options,
92 ec_long_options, NULL)) != EOF) {
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");
108 case 'r': /* random-alloc-fail */
109 if (parse_int(optarg, 0, 100, &alloc_fail_proba,
111 printf("Invalid probability value\n");
118 if (parse_int(optarg, 0, INT_MAX, &seed, 10) < 0) {
119 printf("Invalid seed value\n");
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);
142 struct debug_alloc_hdr {
143 TAILQ_ENTRY(debug_alloc_hdr) next;
148 void *stack[STACK_SZ];
153 struct debug_alloc_ftr {
155 } __attribute__((packed));
157 static int malloc_seq;
159 static void *debug_malloc(size_t size, const char *file, unsigned int line)
161 struct debug_alloc_hdr *hdr;
162 struct debug_alloc_ftr *ftr;
163 size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
167 if (alloc_fail_proba != 0 && (r % 100) < alloc_fail_proba)
170 hdr = malloc(new_size);
175 hdr->seq = malloc_seq;
179 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
180 hdr->cookie = 0x12345678;
181 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
183 ftr = (struct debug_alloc_ftr *)(
184 (char *)hdr + size + sizeof(*hdr));
185 ftr->cookie = 0x87654321;
188 EC_LOG(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p seq=%d\n",
189 file, line, size, ret, malloc_seq++);
196 static void debug_free(void *ptr, const char *file, unsigned int line)
198 struct debug_alloc_hdr *hdr, *h;
199 struct debug_alloc_ftr *ftr;
204 EC_LOG(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
209 hdr = (ptr - sizeof(*hdr));
210 if (hdr->cookie != 0x12345678) {
211 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
216 ftr = (ptr + hdr->size);
217 if (ftr->cookie != 0x87654321) {
218 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
223 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
229 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
234 TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
238 static void *debug_realloc(void *ptr, size_t size, const char *file,
241 struct debug_alloc_hdr *hdr, *h;
242 struct debug_alloc_ftr *ftr;
243 size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
247 hdr = (ptr - sizeof(*hdr));
248 if (hdr->cookie != 0x12345678) {
250 "%s:%d: error: realloc(%p): bad start cookie\n",
255 ftr = (ptr + hdr->size);
256 if (ftr->cookie != 0x87654321) {
258 "%s:%d: error: realloc(%p): bad end cookie\n",
263 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
269 EC_LOG(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
274 TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
275 hdr = realloc(hdr, new_size);
277 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
283 hdr = realloc(NULL, new_size);
291 hdr->seq = malloc_seq;
295 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
296 hdr->cookie = 0x12345678;
297 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
298 ftr = (struct debug_alloc_ftr *)(
299 (char *)hdr + size + sizeof(*hdr));
300 ftr->cookie = 0x87654321;
303 EC_LOG(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p seq=%d\n",
304 file, line, ptr, size, ret, malloc_seq++);
311 static int debug_alloc_dump_leaks(void)
313 struct debug_alloc_hdr *hdr;
317 EC_LOG(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
319 if (TAILQ_EMPTY(&debug_alloc_hdr_list))
322 TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) {
324 "%s:%d: error: memory leak seq=%u size=%zd ptr=%p\n",
325 hdr->file, hdr->line, hdr->seq, hdr->size, hdr + 1);
326 buffer = backtrace_symbols(hdr->stack, hdr->stacklen);
327 if (buffer == NULL) {
328 for (i = 0; i < hdr->stacklen; i++)
329 EC_LOG(EC_LOG_ERR, " %p\n", hdr->stack[i]);
331 for (i = 0; i < hdr->stacklen; i++)
332 EC_LOG(EC_LOG_ERR, " %s\n",
333 buffer ? buffer[i] : "unknown");
339 " missing static syms, use: addr2line -f -e <prog> <addr>\n");
344 static int debug_log(int type, unsigned int level, void *opaque,
350 if (level > (unsigned int)log_level)
353 if (printf("%s", str) < 0)
359 int main(int argc, char **argv)
361 int i, ret = 0, leaks;
363 ret = parse_args(argc, argv);
372 /* register a new malloc to track memleaks */
373 TAILQ_INIT(&debug_alloc_hdr_list);
374 if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) {
375 EC_LOG(EC_LOG_ERR, "cannot register new malloc\n");
380 fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
383 ec_log_fct_register(debug_log, NULL);
389 for (i = 1; i < argc; i++)
390 ret |= ec_test_one(argv[i]);
395 leaks = debug_alloc_dump_leaks();
397 if (alloc_fail_proba == 0 && ret != 0) {
398 printf("tests failed\n");
400 } else if (alloc_fail_proba != 0 && leaks != 0) {
401 printf("tests failed (memory leak)\n");
405 printf("\ntests ok\n");