Skip to content
Snippets Groups Projects
astmm.c 36.2 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * Asterisk -- An open source telephony toolkit.
    
     * Copyright (C) 1999 - 2012, Digium, Inc.
    
     * Mark Spencer <markster@digium.com>
    
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
    
     * This program is free software, distributed under the terms of
    
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    
     * \brief Memory Management
    
     *
     * \author Mark Spencer <markster@digium.com>
    
     * \author Richard Mudgett <rmudgett@digium.com>
    
    /*** MODULEINFO
    	<support_level>core</support_level>
     ***/
    
    
    #define ASTMM_LIBC ASTMM_IGNORE
    
    #include "asterisk.h"
    
    
    #if defined(__AST_DEBUG_MALLOC)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #include "asterisk/paths.h"	/* use ast_config_AST_LOG_DIR */
    
    #include <stddef.h>
    
    #include "asterisk/cli.h"
    #include "asterisk/lock.h"
    
    #include "asterisk/backtrace.h"
    
    /*!
     * The larger the number the faster memory can be freed.
     * However, more memory then is used for the regions[] hash
     * table.
     */
    #define SOME_PRIME 1567
    
    enum func_type {
    	FUNC_CALLOC = 1,
    	FUNC_MALLOC,
    	FUNC_REALLOC,
    	FUNC_STRDUP,
    	FUNC_STRNDUP,
    	FUNC_VASPRINTF,
    	FUNC_ASPRINTF
    };
    
    #define FENCE_MAGIC		0xfeedbabe	/*!< Allocated memory high/low fence overwrite check. */
    
    #define FREED_MAGIC		0xdeaddead	/*!< Freed memory wipe filler. */
    #define MALLOC_FILLER	0x55		/*!< Malloced memory filler.  Must not be zero. */
    
    static FILE *mmlog;
    
    
    struct ast_region {
    
    	AST_LIST_ENTRY(ast_region) node;
    
    	unsigned int cache;		/* region was allocated as part of a cache pool */
    
    	unsigned int lineno;
    
    	enum func_type which;
    
    	char file[64];
    	char func[40];
    	/*!
    	 * \brief Lower guard fence.
    	 *
    	 * \note Must be right before data[].
    	 *
    	 * \note Padding between fence and data[] is irrelevent because
    	 * data[] is used to fill in the lower fence check value and not
    	 * the fence member.  The fence member is to ensure that there
    	 * is space reserved for the fence check value.
    	 */
    
    	/*!
    	 * \brief Location of the requested malloc block to return.
    	 *
    	 * \note Must have the same alignment that malloc returns.
    	 * i.e., It is suitably aligned for any kind of varible.
    	 */
    	unsigned char data[0] __attribute__((aligned));
    };
    
    /*! Hash table of lists of active allocated memory regions. */
    static struct ast_region *regions[SOME_PRIME];
    
    /*! Number of freed regions to keep around to delay actually freeing them. */
    #define FREED_MAX_COUNT		1500
    
    /*! Maximum size of a minnow block */
    #define MINNOWS_MAX_SIZE	50
    
    struct ast_freed_regions {
    	/*! Memory regions that have been freed. */
    	struct ast_region *regions[FREED_MAX_COUNT];
    	/*! Next index into freed regions[] to use. */
    	int index;
    };
    
    /*! Large memory blocks that have been freed. */
    static struct ast_freed_regions whales;
    /*! Small memory blocks that have been freed. */
    static struct ast_freed_regions minnows;
    
    
    enum summary_opts {
    	/*! No summary at exit. */
    	SUMMARY_OFF,
    	/*! Bit set if summary by line at exit. */
    	SUMMARY_BY_LINE = (1 << 0),
    	/*! Bit set if summary by function at exit. */
    	SUMMARY_BY_FUNC = (1 << 1),
    	/*! Bit set if summary by file at exit. */
    	SUMMARY_BY_FILE = (1 << 2),
    };
    
    /*! Summary options of unfreed regions at exit. */
    static enum summary_opts atexit_summary;
    /*! Nonzero if the unfreed regions are listed at exit. */
    static int atexit_list;
    
    /*! Nonzero if the memory allocation backtrace is enabled. */
    static int backtrace_enabled;
    
    #define HASH(a)		(((unsigned long)(a)) % ARRAY_LEN(regions))
    
    
    /*! Tracking this mutex will cause infinite recursion, as the mutex tracking
     *  code allocates memory */
    AST_MUTEX_DEFINE_STATIC_NOTRACKING(reglock);
    
    #define astmm_log(...)                               \
    	do {                                         \
    		fprintf(stderr, __VA_ARGS__);        \
    		if (mmlog) {                         \
    			fprintf(mmlog, __VA_ARGS__); \
    			fflush(mmlog);               \
    		}                                    \
    	} while (0)
    
    
    void *ast_std_malloc(size_t size)
    {
    	return malloc(size);
    }
    
    void *ast_std_calloc(size_t nmemb, size_t size)
    {
    	return calloc(nmemb, size);
    }
    
    void *ast_std_realloc(void *ptr, size_t size)
    {
    	return realloc(ptr, size);
    }
    
    void ast_std_free(void *ptr)
    {
    	free(ptr);
    }
    
    void ast_free_ptr(void *ptr)
    {
    	ast_free(ptr);
    }
    
    
    static void print_backtrace(struct ast_bt *bt)
    {
    	int i = 0;
    	char **strings;
    
    	if (!bt) {
    		return;
    	}
    
    	if ((strings = ast_bt_get_symbols(bt->addresses, bt->num_frames))) {
    		astmm_log("Memory allocation backtrace:\n");
    		for (i = 3; i < bt->num_frames - 2; i++) {
    			astmm_log("#%d: [%p] %s\n", i - 3, bt->addresses[i], strings[i]);
    		}
    
    /*!
     * \internal
     *
     * \note If DO_CRASH is not defined then the function returns.
     *
     * \return Nothing
     */
    static void my_do_crash(void)
    {
    	/*
    	 * Give the logger a chance to get the message out, just in case
    	 * we abort(), or Asterisk crashes due to whatever problem just
    	 * happened.
    	 */
    	usleep(1);
    	ast_do_crash();
    }
    
    static void *__ast_alloc_region(size_t size, const enum func_type which, const char *file, int lineno, const char *func, unsigned int cache)
    
    
    	if (!(reg = malloc(size + sizeof(*reg) + sizeof(*fence)))) {
    
    		astmm_log("Memory Allocation Failure - '%d' bytes at %s %s() line %d\n",
    			(int) size, file, func, lineno);
    
    
    	reg->len = size;
    
    	reg->cache = cache;
    
    	reg->lineno = lineno;
    	reg->which = which;
    
    	reg->bt = backtrace_enabled ? ast_bt_create() : NULL;
    
    	ast_copy_string(reg->file, file, sizeof(reg->file));
    	ast_copy_string(reg->func, func, sizeof(reg->func));
    
    	/*
    	 * Init lower fence.
    	 *
    	 * We use the bytes just preceeding reg->data and not reg->fence
    	 * because there is likely to be padding between reg->fence and
    	 * reg->data for reg->data alignment.
    	 */
    	fence = (unsigned int *) (reg->data - sizeof(*fence));
    	*fence = FENCE_MAGIC;
    
    	/* Init higher fence. */
    	fence = (unsigned int *) (reg->data + reg->len);
    
    	put_unaligned_uint32(fence, FENCE_MAGIC);
    
    
    	hash = HASH(reg->data);
    
    	ast_mutex_lock(&reglock);
    
    	AST_LIST_NEXT(reg, node) = regions[hash];
    
    	regions[hash] = reg;
    
    	return reg->data;
    
    /*!
     * \internal
     * \brief Wipe the region payload data with a known value.
     *
     * \param reg Region block to be wiped.
     *
     * \return Nothing
     */
    static void region_data_wipe(struct ast_region *reg)
    
    	void *end;
    	unsigned int *pos;
    
    	/*
    	 * Wipe the lower fence, the payload, and whatever amount of the
    	 * higher fence that falls into alignment with the payload.
    	 */
    	end = reg->data + reg->len;
    	for (pos = &reg->fence; (void *) pos <= end; ++pos) {
    		*pos = FREED_MAGIC;
    	}
    }
    
    /*!
     * \internal
     * \brief Check the region payload data for memory corruption.
     *
     * \param reg Region block to be checked.
     *
     * \return Nothing
     */
    static void region_data_check(struct ast_region *reg)
    {
    	void *end;
    	unsigned int *pos;
    
    	/*
    	 * Check the lower fence, the payload, and whatever amount of
    	 * the higher fence that falls into alignment with the payload.
    	 */
    	end = reg->data + reg->len;
    	for (pos = &reg->fence; (void *) pos <= end; ++pos) {
    		if (*pos != FREED_MAGIC) {
    			astmm_log("WARNING: Memory corrupted after free of %p allocated at %s %s() line %d\n",
    				reg->data, reg->file, reg->func, reg->lineno);
    
    			my_do_crash();
    
    }
    
    /*!
     * \internal
     * \brief Flush the circular array of freed regions.
     *
     * \param freed Already freed region blocks storage.
     *
     * \return Nothing
     */
    static void freed_regions_flush(struct ast_freed_regions *freed)
    {
    	int idx;
    	struct ast_region *old;
    
    	ast_mutex_lock(&reglock);
    	for (idx = 0; idx < ARRAY_LEN(freed->regions); ++idx) {
    		old = freed->regions[idx];
    		freed->regions[idx] = NULL;
    		if (old) {
    			region_data_check(old);
    			free(old);
    		}
    	}
    	freed->index = 0;
    
    }
    
    /*!
     * \internal
     * \brief Delay freeing a region block.
     *
     * \param freed Already freed region blocks storage.
     * \param reg Region block to be freed.
     *
     * \return Nothing
     */
    static void region_free(struct ast_freed_regions *freed, struct ast_region *reg)
    {
    	struct ast_region *old;
    
    	region_data_wipe(reg);
    
    	ast_mutex_lock(&reglock);
    	old = freed->regions[freed->index];
    	freed->regions[freed->index] = reg;
    
    	++freed->index;
    	if (ARRAY_LEN(freed->regions) <= freed->index) {
    		freed->index = 0;
    	}
    	ast_mutex_unlock(&reglock);
    
    	if (old) {
    		region_data_check(old);
    
    		old->bt = ast_bt_destroy(old->bt);
    
    		free(old);
    	}
    
    /*!
     * \internal
     * \brief Remove a region from the active regions.
     *
     * \param ptr Region payload data pointer.
     *
     * \retval region on success.
     * \retval NULL if not found.
     */
    static struct ast_region *region_remove(void *ptr)
    
    	struct ast_region *reg;
    	struct ast_region *prev = NULL;
    
    	for (reg = regions[hash]; reg; reg = AST_LIST_NEXT(reg, node)) {
    
    			if (prev) {
    
    				AST_LIST_NEXT(prev, node) = AST_LIST_NEXT(reg, node);
    
    			} else {
    
    				regions[hash] = AST_LIST_NEXT(reg, node);
    
    	return reg;
    }
    
    /*!
     * \internal
     * \brief Check the fences of a region.
     *
     * \param reg Region block to check.
     *
     * \return Nothing
     */
    static void region_check_fences(struct ast_region *reg)
    {
    	unsigned int *fence;
    
    	/*
    	 * We use the bytes just preceeding reg->data and not reg->fence
    	 * because there is likely to be padding between reg->fence and
    	 * reg->data for reg->data alignment.
    	 */
    	fence = (unsigned int *) (reg->data - sizeof(*fence));
    	if (*fence != FENCE_MAGIC) {
    		astmm_log("WARNING: Low fence violation of %p allocated at %s %s() line %d\n",
    			reg->data, reg->file, reg->func, reg->lineno);
    
    		my_do_crash();
    	}
    	fence = (unsigned int *) (reg->data + reg->len);
    	if (get_unaligned_uint32(fence) != FENCE_MAGIC) {
    		astmm_log("WARNING: High fence violation of %p allocated at %s %s() line %d\n",
    			reg->data, reg->file, reg->func, reg->lineno);
    
    /*!
     * \internal
     * \brief Check the fences of all regions currently allocated.
     *
     * \return Nothing
     */
    static void regions_check_all_fences(void)
    {
    	int idx;
    	struct ast_region *reg;
    
    	ast_mutex_lock(&reglock);
    	for (idx = 0; idx < ARRAY_LEN(regions); ++idx) {
    		for (reg = regions[idx]; reg; reg = AST_LIST_NEXT(reg, node)) {
    			region_check_fences(reg);
    		}
    	}
    	ast_mutex_unlock(&reglock);
    }
    
    
    static void __ast_free_region(void *ptr, const char *file, int lineno, const char *func)
    {
    	struct ast_region *reg;
    
    	if (!ptr) {
    		return;
    	}
    
    	reg = region_remove(ptr);
    
    		region_check_fences(reg);
    
    		if (reg->len <= MINNOWS_MAX_SIZE) {
    			region_free(&minnows, reg);
    		} else {
    			region_free(&whales, reg);
    
    	} else {
    
    		/*
    		 * This memory region is not registered.  It could be because of
    		 * a double free or the memory block was not allocated by the
    		 * malloc debug code.
    		 */
    		astmm_log("WARNING: Freeing unregistered memory %p by %s %s() line %d\n",
    			ptr, file, func, lineno);
    		my_do_crash();
    
    void *__ast_calloc(size_t nmemb, size_t size, const char *file, int lineno, const char *func)
    
    	ptr = __ast_alloc_region(size * nmemb, FUNC_CALLOC, file, lineno, func, 0);
    	if (ptr) {
    
    		memset(ptr, 0, size * nmemb);
    
    void *__ast_calloc_cache(size_t nmemb, size_t size, const char *file, int lineno, const char *func)
    
    	ptr = __ast_alloc_region(size * nmemb, FUNC_CALLOC, file, lineno, func, 1);
    	if (ptr) {
    
    void *__ast_malloc(size_t size, const char *file, int lineno, const char *func)
    
    	void *ptr;
    
    	ptr = __ast_alloc_region(size, FUNC_MALLOC, file, lineno, func, 0);
    	if (ptr) {
    		/* Make sure that the malloced memory is not zero. */
    		memset(ptr, MALLOC_FILLER, size);
    	}
    
    	return ptr;
    
    void __ast_free(void *ptr, const char *file, int lineno, const char *func)
    
    /*!
     * \note reglock must be locked before calling.
     */
    static struct ast_region *region_find(void *ptr)
    
    	int hash;
    	struct ast_region *reg;
    
    	hash = HASH(ptr);
    
    	for (reg = regions[hash]; reg; reg = AST_LIST_NEXT(reg, node)) {
    
    		if (reg->data == ptr) {
    			break;
    		}
    
    	return reg;
    }
    
    void *__ast_realloc(void *ptr, size_t size, const char *file, int lineno, const char *func)
    {
    	size_t len;
    	struct ast_region *found;
    	void *new_mem;
    
    		ast_mutex_lock(&reglock);
    		found = region_find(ptr);
    		if (!found) {
    			ast_mutex_unlock(&reglock);
    			astmm_log("WARNING: Realloc of unregistered memory %p by %s %s() line %d\n",
    				ptr, file, func, lineno);
    			my_do_crash();
    			return NULL;
    		}
    		len = found->len;
    		ast_mutex_unlock(&reglock);
    	} else {
    		found = NULL;
    		len = 0;
    	}
    
    	if (!size) {
    
    		__ast_free_region(ptr, file, lineno, func);
    
    		return NULL;
    	}
    
    	new_mem = __ast_alloc_region(size, FUNC_REALLOC, file, lineno, func, 0);
    	if (new_mem) {
    		if (found) {
    			/* Copy the old data to the new malloced memory. */
    			if (size <= len) {
    				memcpy(new_mem, ptr, size);
    			} else {
    				memcpy(new_mem, ptr, len);
    				/* Make sure that the added memory is not zero. */
    				memset(new_mem + len, MALLOC_FILLER, size - len);
    			}
    			__ast_free_region(ptr, file, lineno, func);
    		} else {
    			/* Make sure that the malloced memory is not zero. */
    			memset(new_mem, MALLOC_FILLER, size);
    		}
    
    	return new_mem;
    
    char *__ast_strdup(const char *s, const char *file, int lineno, const char *func)
    
    	if ((ptr = __ast_alloc_region(len, FUNC_STRDUP, file, lineno, func, 0)))
    
    char *__ast_strndup(const char *s, size_t n, const char *file, int lineno, const char *func)
    
    	len = strnlen(s, n);
    	if ((ptr = __ast_alloc_region(len + 1, FUNC_STRNDUP, file, lineno, func, 0))) {
    		memcpy(ptr, s, len);
    		ptr[len] = '\0';
    	}
    
    int __ast_asprintf(const char *file, int lineno, const char *func, char **strp, const char *fmt, ...)
    {
    	int size;
    	va_list ap, ap2;
    	char s;
    
    	*strp = NULL;
    	va_start(ap, fmt);
    	va_copy(ap2, ap);
    	size = vsnprintf(&s, 1, fmt, ap2);
    	va_end(ap2);
    
    	if (!(*strp = __ast_alloc_region(size + 1, FUNC_ASPRINTF, file, lineno, func, 0))) {
    
    		va_end(ap);
    		return -1;
    	}
    	vsnprintf(*strp, size + 1, fmt, ap);
    	va_end(ap);
    
    	return size;
    }
    
    
    int __ast_vasprintf(char **strp, const char *fmt, va_list ap, const char *file, int lineno, const char *func)
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	int size;
    	va_list ap2;
    	char s;
    
    	*strp = NULL;
    	va_copy(ap2, ap);
    	size = vsnprintf(&s, 1, fmt, ap2);
    	va_end(ap2);
    
    	if (!(*strp = __ast_alloc_region(size + 1, FUNC_VASPRINTF, file, lineno, func, 0))) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		return -1;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	vsnprintf(*strp, size + 1, fmt, ap);
    
    	return size;
    
    /*!
     * \internal
     * \brief Count the number of bytes in the specified freed region.
     *
     * \param freed Already freed region blocks storage.
     *
     * \note reglock must be locked before calling.
     *
     * \return Number of bytes in freed region.
     */
    static size_t freed_regions_size(struct ast_freed_regions *freed)
    {
    	size_t total_len = 0;
    	int idx;
    	struct ast_region *old;
    
    	for (idx = 0; idx < ARRAY_LEN(freed->regions); ++idx) {
    		old = freed->regions[idx];
    		if (old) {
    			total_len += old->len;
    		}
    	}
    
    	return total_len;
    }
    
    
    static char *handle_memory_atexit_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    
    		e->command = "memory atexit list {on|off}";
    
    		e->usage =
    			"Usage: memory atexit list {on|off}\n"
    			"       Enable dumping a list of still allocated memory segments at exit.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 4) {
    		return CLI_SHOWUSAGE;
    	}
    
    	if (ast_true(a->argv[3])) {
    		atexit_list = 1;
    	} else if (ast_false(a->argv[3])) {
    		atexit_list = 0;
    	} else {
    		return CLI_SHOWUSAGE;
    	}
    
    	ast_cli(a->fd, "The atexit list is: %s\n", atexit_list ? "On" : "Off");
    
    	return CLI_SUCCESS;
    }
    
    static char *handle_memory_atexit_summary(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	char buf[80];
    
    	switch (cmd) {
    	case CLI_INIT:
    
    		e->command = "memory atexit summary {off|byline|byfunc|byfile}";
    
    		e->usage =
    			"Usage: memory atexit summary {off|byline|byfunc|byfile}\n"
    			"       Summary of still allocated memory segments at exit options.\n"
    			"       off - Disable at exit summary.\n"
    			"       byline - Enable at exit summary by file line number.\n"
    			"       byfunc - Enable at exit summary by function name.\n"
    			"       byfile - Enable at exit summary by file.\n"
    			"\n"
    			"       Note: byline, byfunc, and byfile are cumulative enables.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 4) {
    		return CLI_SHOWUSAGE;
    	}
    
    	if (ast_false(a->argv[3])) {
    		atexit_summary = SUMMARY_OFF;
    	} else if (!strcasecmp(a->argv[3], "byline")) {
    		atexit_summary |= SUMMARY_BY_LINE;
    	} else if (!strcasecmp(a->argv[3], "byfunc")) {
    		atexit_summary |= SUMMARY_BY_FUNC;
    	} else if (!strcasecmp(a->argv[3], "byfile")) {
    		atexit_summary |= SUMMARY_BY_FILE;
    	} else {
    		return CLI_SHOWUSAGE;
    	}
    
    	if (atexit_summary) {
    		buf[0] = '\0';
    		if (atexit_summary & SUMMARY_BY_LINE) {
    			strcat(buf, "byline");
    		}
    		if (atexit_summary & SUMMARY_BY_FUNC) {
    			if (buf[0]) {
    				strcat(buf, " | ");
    			}
    			strcat(buf, "byfunc");
    		}
    		if (atexit_summary & SUMMARY_BY_FILE) {
    			if (buf[0]) {
    				strcat(buf, " | ");
    			}
    			strcat(buf, "byfile");
    		}
    	} else {
    		strcpy(buf, "Off");
    	}
    	ast_cli(a->fd, "The atexit summary is: %s\n", buf);
    
    	return CLI_SUCCESS;
    }
    
    
    /*!
     * \internal
     * \brief Common summary output at the end of the memory show commands.
     *
     * \param fd CLI output file descriptor.
     * \param whales_len Accumulated size of free large allocations.
     * \param minnows_len Accumulated size of free small allocations.
     * \param total_len Accumulated size of all current allocations.
     * \param selected_len Accumulated size of the selected allocations.
     * \param cache_len Accumulated size of the allocations that are part of a cache.
     * \param count Number of selected allocations.
     *
     * \return Nothing
     */
    static void print_memory_show_common_stats(int fd,
    	unsigned int whales_len,
    	unsigned int minnows_len,
    	unsigned int total_len,
    	unsigned int selected_len,
    	unsigned int cache_len,
    	unsigned int count)
    {
    	if (cache_len) {
    		ast_cli(fd, "%10u bytes allocated (%u in caches) in %u selected allocations\n\n",
    			selected_len, cache_len, count);
    	} else {
    		ast_cli(fd, "%10u bytes allocated in %u selected allocations\n\n",
    			selected_len, count);
    	}
    
    	ast_cli(fd, "%10u bytes in all allocations\n", total_len);
    	ast_cli(fd, "%10u bytes in deferred free large allocations\n", whales_len);
    	ast_cli(fd, "%10u bytes in deferred free small allocations\n", minnows_len);
    	ast_cli(fd, "%10u bytes in deferred free allocations\n",
    		whales_len + minnows_len);
    	ast_cli(fd, "%10u bytes in all allocations and deferred free allocations\n",
    		total_len + whales_len + minnows_len);
    }
    
    
    static char *handle_memory_show_allocations(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    
    	const char *fn = NULL;
    
    	unsigned int whales_len;
    	unsigned int minnows_len;
    	unsigned int total_len = 0;
    	unsigned int selected_len = 0;
    
    	unsigned int cache_len = 0;
    
    	unsigned int count = 0;
    
    
    Jason Parker's avatar
    Jason Parker committed
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "memory show allocations";
    		e->usage =
    
    			"Usage: memory show allocations [<file>|anomalies]\n"
    
    			"       Dumps a list of segments of allocated memory.\n"
    			"       Defaults to listing all memory allocations.\n"
    			"       <file> - Restricts output to memory allocated by the file.\n"
    
    			"       anomalies - Only check for fence violations.\n";
    
    Jason Parker's avatar
    Jason Parker committed
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    
    	if (a->argc == 4) {
    
    Jason Parker's avatar
    Jason Parker committed
    		fn = a->argv[3];
    
    	} else if (a->argc != 3) {
    		return CLI_SHOWUSAGE;
    	}
    
    	/* Look for historical misspelled option as well. */
    
    	if (fn && (!strcasecmp(fn, "anomalies") || !strcasecmp(fn, "anomolies"))) {
    		regions_check_all_fences();
    		ast_cli(a->fd, "Anomaly check complete.\n");
    		return CLI_SUCCESS;
    	}
    
    	ast_mutex_lock(&reglock);
    
    	for (idx = 0; idx < ARRAY_LEN(regions); ++idx) {
    		for (reg = regions[idx]; reg; reg = AST_LIST_NEXT(reg, node)) {
    
    			if (fn && strcasecmp(fn, reg->file)) {
    				continue;
    			}
    
    			region_check_fences(reg);
    
    			ast_cli(a->fd, "%10u bytes allocated%s by %20s() line %5u of %s\n",
    				(unsigned int) reg->len, reg->cache ? " (cache)" : "",
    				reg->func, reg->lineno, reg->file);
    
    
    			selected_len += reg->len;
    
    			if (reg->cache) {
    				cache_len += reg->len;
    
    
    	whales_len = freed_regions_size(&whales);
    	minnows_len = freed_regions_size(&minnows);
    
    	ast_mutex_unlock(&reglock);
    
    	print_memory_show_common_stats(a->fd,
    		whales_len, minnows_len, total_len,
    		selected_len, cache_len, count);
    
    Jason Parker's avatar
    Jason Parker committed
    	return CLI_SUCCESS;
    
    Jason Parker's avatar
    Jason Parker committed
    static char *handle_memory_show_summary(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    
    #define my_max(a, b) ((a) >= (b) ? (a) : (b))
    
    
    	const char *fn = NULL;
    
    	int idx;
    	int cmp;
    
    	unsigned int whales_len;
    	unsigned int minnows_len;
    	unsigned int total_len = 0;
    	unsigned int selected_len = 0;
    
    	unsigned int cache_len = 0;
    
    	unsigned int count = 0;
    
    	struct file_summary {
    		struct file_summary *next;
    
    		unsigned int len;
    		unsigned int cache_len;
    		unsigned int count;
    		unsigned int lineno;
    		char name[my_max(sizeof(reg->file), sizeof(reg->func))];
    	} *list = NULL, *cur, **prev;
    
    Jason Parker's avatar
    Jason Parker committed
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "memory show summary";
    		e->usage =
    			"Usage: memory show summary [<file>]\n"
    			"       Summarizes heap memory allocations by file, or optionally\n"
    
    			"       by line if a file is specified.\n";
    
    Jason Parker's avatar
    Jason Parker committed
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    
    	if (a->argc == 4) {
    
    Jason Parker's avatar
    Jason Parker committed
    		fn = a->argv[3];
    
    	} else if (a->argc != 3) {
    		return CLI_SHOWUSAGE;
    	}
    
    	for (idx = 0; idx < ARRAY_LEN(regions); ++idx) {
    
    		for (reg = regions[idx]; reg; reg = AST_LIST_NEXT(reg, node)) {
    
    			if (fn) {
    				if (strcasecmp(fn, reg->file)) {
    					continue;
    				}
    
    				/* Sort list by func/lineno.  Find existing or place to insert. */
    				for (prev = &list; (cur = *prev); prev = &cur->next) {
    					cmp = strcmp(cur->name, reg->func);
    					if (cmp < 0) {
    						continue;
    					}
    					if (cmp > 0) {
    						/* Insert before current */
    						cur = NULL;
    						break;
    					}
    					cmp = cur->lineno - reg->lineno;
    					if (cmp < 0) {
    						continue;
    					}
    					if (cmp > 0) {
    						/* Insert before current */
    						cur = NULL;
    					}
    					break;
    				}
    			} else {
    				/* Sort list by filename.  Find existing or place to insert. */
    				for (prev = &list; (cur = *prev); prev = &cur->next) {
    					cmp = strcmp(cur->name, reg->file);
    					if (cmp < 0) {
    						continue;
    					}
    					if (cmp > 0) {
    						/* Insert before current */
    						cur = NULL;
    					}
    
    			if (!cur) {
    
    				cur = ast_alloca(sizeof(*cur));
    
    				memset(cur, 0, sizeof(*cur));
    
    				cur->lineno = reg->lineno;
    				ast_copy_string(cur->name, fn ? reg->func : reg->file, sizeof(cur->name));
    
    				cur->next = *prev;
    				*prev = cur;
    
    
    			cur->len += reg->len;
    
    			if (reg->cache) {
    
    				cur->cache_len += reg->len;
    
    			}
    			++cur->count;
    
    
    	whales_len = freed_regions_size(&whales);
    	minnows_len = freed_regions_size(&minnows);