[RFC,v2,1/2] malloc: support malloc and free tracking log

Message ID 1587110623-405-2-git-send-email-xuemingl@mellanox.com (mailing list archive)
State Deferred, archived
Delegated to: David Marchand
Headers
Series malloc: support malloc and free tracking log |

Checks

Context Check Description
ci/checkpatch success coding style OK
ci/Intel-compilation success Compilation OK

Commit Message

Xueming Li April 17, 2020, 8:03 a.m. UTC
  This patch introduces new feature to track rte_malloc leakage by logging
malloc and free.

Signed-off-by: Xueming Li <xuemingl@mellanox.com>
---
 lib/librte_eal/common/eal_memcfg.h  |  26 ++++
 lib/librte_eal/common/malloc_elem.h |   6 +-
 lib/librte_eal/common/rte_malloc.c  | 261 +++++++++++++++++++++++++++++++++++-
 lib/librte_eal/include/rte_malloc.h |  39 +++++-
 lib/librte_eal/rte_eal_version.map  |   3 +
 5 files changed, 327 insertions(+), 8 deletions(-)
  

Patch

diff --git a/lib/librte_eal/common/eal_memcfg.h b/lib/librte_eal/common/eal_memcfg.h
index 583fcb5..49d59d8 100644
--- a/lib/librte_eal/common/eal_memcfg.h
+++ b/lib/librte_eal/common/eal_memcfg.h
@@ -14,6 +14,28 @@ 
 
 #include "malloc_heap.h"
 
+
+enum rte_malloc_log_type {
+	rte_malloc_log_malloc,
+	rte_malloc_log_realloc,
+	rte_malloc_log_free,
+};
+
+/**
+ * Memory allocation and free tracking log
+ */
+struct rte_malloc_log {
+	int socket;
+	void *addr;
+	size_t req_size; /* requested size */
+	size_t size; /* outer element and pad */
+	size_t align;
+	uint32_t pad;
+	uint32_t type:2; /* log entry type. */
+	uint32_t paired:1; /* allocation and free both tracked. */
+	char name[64]; /* name may come from stack, copy. */
+};
+
 /**
  * Memory configuration shared across multiple processes.
  */
@@ -73,6 +95,10 @@  struct rte_mem_config {
 	/**< TSC rate */
 
 	uint8_t dma_maskbits; /**< Keeps the more restricted dma mask. */
+
+	struct rte_malloc_log *malloc_logs; /**< Log entries. */
+	int malloc_log_max; /**< Max number of logs to write. */
+	int malloc_log_count; /**< count of logs written. */
 };
 
 /* update internal config from shared mem config */
diff --git a/lib/librte_eal/common/malloc_elem.h b/lib/librte_eal/common/malloc_elem.h
index a1e5f7f..006480c 100644
--- a/lib/librte_eal/common/malloc_elem.h
+++ b/lib/librte_eal/common/malloc_elem.h
@@ -9,6 +9,8 @@ 
 
 #define MIN_DATA_SIZE (RTE_CACHE_LINE_SIZE)
 
+#define MALLOC_TRACKING_MAX (1 << 29)
+
 /* dummy definition of struct so we can use pointers to it in malloc_elem struct */
 struct malloc_heap;
 
@@ -27,7 +29,9 @@  struct malloc_elem {
 	LIST_ENTRY(malloc_elem) free_list;
 	/**< list of free elements in heap */
 	struct rte_memseg_list *msl;
-	volatile enum elem_state state;
+	volatile uint32_t state:2;
+	volatile uint32_t log:1;		/* Element logged. */
+	volatile uint32_t log_index:29;		/* Element log index. */
 	uint32_t pad;
 	size_t size;
 	struct malloc_elem *orig_elem;
diff --git a/lib/librte_eal/common/rte_malloc.c b/lib/librte_eal/common/rte_malloc.c
index d6026a2..2e30693 100644
--- a/lib/librte_eal/common/rte_malloc.c
+++ b/lib/librte_eal/common/rte_malloc.c
@@ -6,6 +6,7 @@ 
 #include <stddef.h>
 #include <stdio.h>
 #include <string.h>
+#include <assert.h>
 #include <sys/queue.h>
 
 #include <rte_errno.h>
@@ -29,12 +30,245 @@ 
 #include "eal_private.h"
 
 
+const char *rte_malloc_log_type_name[] = { "malloc", "realloc", "free" };
+
+/*
+ * Track and log memory allocation information.
+ */
+static void
+rte_malloc_log(const char *type, size_t size, unsigned int align,
+	       void *addr)
+{
+	struct rte_malloc_log *log;
+	struct malloc_elem *elem = malloc_elem_from_data(addr);
+	struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
+
+	if (!addr || !elem || !mcfg->malloc_log_max ||
+	    mcfg->malloc_log_max == mcfg->malloc_log_count)
+		return;
+
+	log = &mcfg->malloc_logs[mcfg->malloc_log_count];
+	if (type)
+		strncpy(log->name, type, sizeof(log->name) - 1);
+	log->req_size = size;
+	log->pad = elem->pad;
+	log->size = elem->size + elem->pad;
+	align = align ? align : 1;
+	log->align = RTE_CACHE_LINE_ROUNDUP(align);
+	log->addr = addr;
+	log->socket = elem->heap->socket_id;
+	elem->log = 1;
+	elem->log_index = mcfg->malloc_log_count++;
+}
+
+/*
+ * track and log memory realloc
+ */
+static void
+rte_realloc_log(uint32_t pref_malloc, uint32_t prev_index,
+		void *new_addr, size_t size, unsigned int align)
+{
+	struct rte_malloc_log *prev_log;
+	struct rte_malloc_log *log;
+	struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
+	struct malloc_elem *elem = malloc_elem_from_data(new_addr);
+
+	if (!mcfg->malloc_log_max || !elem)
+		return;
+	if (pref_malloc) {
+		prev_log = &mcfg->malloc_logs[prev_index];
+		prev_log->paired = 1;
+	}
+	if (mcfg->malloc_log_max == mcfg->malloc_log_count)
+		return;
+	log = &mcfg->malloc_logs[mcfg->malloc_log_count++];
+	log->addr = new_addr;
+	log->type = rte_malloc_log_realloc;
+	log->req_size = size;
+	if (pref_malloc)
+		strncpy(log->name, prev_log->name, sizeof(log->name));
+	log->pad = elem->pad;
+	log->size = elem->size + elem->pad;
+	align = align ? align : 1;
+	log->align = RTE_CACHE_LINE_ROUNDUP(align);
+	log->socket = elem->heap->socket_id;
+	elem->log = 1;
+	elem->log_index = mcfg->malloc_log_count++;
+}
+
+/*
+ * track and log memory free
+ */
+static void
+rte_free_log(void *addr, uint32_t log_malloc, uint32_t log_index, size_t size)
+{
+	struct rte_malloc_log *alloc_log;
+	struct rte_malloc_log *log;
+	struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
+
+	if (!addr || !mcfg->malloc_log_max)
+		return;
+	if (log_malloc) {
+		alloc_log = &mcfg->malloc_logs[log_index];
+		alloc_log->paired = 1;
+	}
+	if (mcfg->malloc_log_max == mcfg->malloc_log_count)
+		return;
+	log = &mcfg->malloc_logs[mcfg->malloc_log_count++];
+	log->addr = addr;
+	log->type = rte_malloc_log_free;
+	log->size = size;
+	if (log_malloc) {
+		strncpy(log->name, alloc_log->name, sizeof(log->name));
+		log->req_size = alloc_log->req_size;
+		log->align = alloc_log->align;
+		log->socket = alloc_log->socket;
+		log->pad = alloc_log->pad;
+		log->paired = 1;
+	}
+}
+
+/*
+ * Initialize malloc tracking with max count of log entries
+ */
+int
+rte_malloc_log_init(uint32_t count)
+{
+	struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
+
+	if (mcfg->malloc_log_max) {
+		RTE_LOG(ERR, EAL, "malloc tracking started\n");
+		return -EINVAL;
+	}
+	if (!count || count > MALLOC_TRACKING_MAX) {
+		RTE_LOG(ERR, EAL, "invalid malloc tracking log number\n");
+		return -EINVAL;
+	}
+
+	/* allocate logs. */
+	mcfg->malloc_logs = rte_calloc("malloc_logs", count,
+				       sizeof(*mcfg->malloc_logs), 0);
+	if (!mcfg->malloc_logs)
+		return -ENOMEM;
+
+	mcfg->malloc_log_count = 0;
+	mcfg->malloc_log_max = count;
+	return 0;
+}
+
+/*
+ * Initialize malloc tracking with max count of log entries
+ */
+int
+rte_malloc_log_stop(void)
+{
+	struct malloc_elem *elem;
+	int i;
+	struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
+	struct rte_malloc_log *log;
+
+	if (!mcfg->malloc_log_max) {
+		RTE_LOG(ERR, EAL, "malloc tracking not started\n");
+		return -EINVAL;
+	}
+
+	/* release all logs. */
+	for (i = 0; i < mcfg->malloc_log_count; i++) {
+		log = &mcfg->malloc_logs[i];
+		/* clear log info from malloc elem. */
+		if (log->type)
+			continue;
+		elem = malloc_elem_from_data(log->addr);
+		if (elem) {
+			elem->log = 0;
+			elem->log_index = 0;
+		}
+	}
+
+	rte_free(mcfg->malloc_logs);
+	mcfg->malloc_logs = NULL;
+	mcfg->malloc_log_max = 0;
+	mcfg->malloc_log_count = 0;
+
+	return 0;
+}
+
+/*
+ * Dump malloc tracking
+ */
+void
+rte_malloc_log_dump(FILE *out, uint32_t detail)
+{
+	struct rte_malloc_log *log;
+	int i;
+	uint32_t n_alloc_free = 0;
+	uint32_t n_alloc = 0;
+	uint32_t n_free_alloc = 0;
+	uint32_t n_free = 0;
+	size_t alloc_leak = 0;
+	size_t free_leak = 0;
+	struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
+
+	if (!mcfg->malloc_log_max || !mcfg->malloc_logs) {
+		RTE_LOG(ERR, EAL, "malloc tracking not started\n");
+		return;
+	}
+	if (detail)
+		fprintf(out, "Socket  Action Paired            Address    Size Align Mgmt Pad   Total Type\n");
+	for (i = 0; i < mcfg->malloc_log_count; i++) {
+		log = &mcfg->malloc_logs[i];
+		if (detail > 1 ||
+		    (detail == 1 && !log->paired &&
+		     log->type != rte_malloc_log_free))
+			fprintf(out, "%6d %7s %6s %18p %7lu %5lu %4d %3u %7lu %s\n",
+				log->socket,
+				rte_malloc_log_type_name[log->type],
+				log->paired ? "y" : "n",
+				log->addr, log->req_size, log->align,
+				MALLOC_ELEM_OVERHEAD,
+				log->pad,
+				log->size,
+				log->name);
+		if (log->type == rte_malloc_log_free) {
+			n_free++;
+			if (log->paired)
+				n_free_alloc++;
+			else
+				free_leak += log->size;
+		} else {
+			n_alloc++;
+			if (log->paired)
+				n_alloc_free++;
+			else
+				alloc_leak += log->size;
+		}
+	}
+	fprintf(out, "Total rte_malloc and rte_realloc: %u/%u leak %zu(B), rte_free: %u/%u leak: %zu(B)\n",
+		n_alloc_free, n_alloc, alloc_leak,
+		n_free_alloc, n_free, free_leak);
+	if (mcfg->malloc_log_count == mcfg->malloc_log_max)
+		fprintf(out, "Warning: log full, please consider larger log buffer\n");
+}
+
 /* Free the memory space back to heap */
 void rte_free(void *addr)
 {
+	struct malloc_elem *elem = malloc_elem_from_data(addr);
+	uint32_t log_malloc, log_index;
+	size_t size;
+
 	if (addr == NULL) return;
-	if (malloc_heap_free(malloc_elem_from_data(addr)) < 0)
+	if (elem) {
+		log_malloc = elem->log;
+		log_index = elem->log_index;
+		size = elem->size + elem->pad;
+		if (malloc_heap_free(elem) < 0)
+			RTE_LOG(ERR, EAL, "Error: Invalid memory\n");
+		else
+			rte_free_log(addr, log_malloc, log_index, size);
+	} else {
 		RTE_LOG(ERR, EAL, "Error: Invalid memory\n");
+	}
 }
 
 /*
@@ -44,6 +278,8 @@  void rte_free(void *addr)
 rte_malloc_socket(const char *type, size_t size, unsigned int align,
 		int socket_arg)
 {
+	void *ret;
+
 	/* return NULL if size is 0 or alignment is not power-of-2 */
 	if (size == 0 || (align && !rte_is_power_of_2(align)))
 		return NULL;
@@ -57,8 +293,13 @@  void rte_free(void *addr)
 				!rte_eal_has_hugepages())
 		socket_arg = SOCKET_ID_ANY;
 
-	return malloc_heap_alloc(type, size, socket_arg, 0,
+	ret = malloc_heap_alloc(type, size, socket_arg, 0,
 			align == 0 ? 1 : align, 0, false);
+
+	if (ret)
+		rte_malloc_log(type, size, align == 0 ? 1 : align, ret);
+
+	return ret;
 }
 
 /*
@@ -123,10 +364,12 @@  void rte_free(void *addr)
 void *
 rte_realloc_socket(void *ptr, size_t size, unsigned int align, int socket)
 {
+	struct malloc_elem *elem = malloc_elem_from_data(ptr);
+	uint32_t log_malloc, log_index;
+
 	if (ptr == NULL)
 		return rte_malloc_socket(NULL, size, align, socket);
 
-	struct malloc_elem *elem = malloc_elem_from_data(ptr);
 	if (elem == NULL) {
 		RTE_LOG(ERR, EAL, "Error: memory corruption detected\n");
 		return NULL;
@@ -139,9 +382,15 @@  void rte_free(void *addr)
 	 */
 	if ((socket == SOCKET_ID_ANY ||
 	     (unsigned int)socket == elem->heap->socket_id) &&
-			RTE_PTR_ALIGN(ptr, align) == ptr &&
-			malloc_heap_resize(elem, size) == 0)
-		return ptr;
+	    RTE_PTR_ALIGN(ptr, align) == ptr) {
+		log_malloc = elem->log;
+		log_index = elem->log_index;
+		if (malloc_heap_resize(elem, size) == 0) {
+			rte_realloc_log(log_malloc, log_index, ptr, size,
+					align);
+			return ptr;
+		}
+	}
 
 	/* either requested socket id doesn't match, alignment is off
 	 * or we have no room to expand,
diff --git a/lib/librte_eal/include/rte_malloc.h b/lib/librte_eal/include/rte_malloc.h
index 42ca051..4947ba9 100644
--- a/lib/librte_eal/include/rte_malloc.h
+++ b/lib/librte_eal/include/rte_malloc.h
@@ -508,7 +508,44 @@  struct rte_malloc_socket_stats {
  *   to dump all objects.
  */
 void
-rte_malloc_dump_stats(FILE *f, const char *type);
+rte_malloc_dump_stats(FILE *out, const char *type);
+
+/**
+ * Initialize malloc tracking log buffer.
+ *
+ * @param count
+ *   Max count of tracking log entries, range (1 : 1 << 29)
+ * @return
+ *   0 on success, negative errno otherwise
+ */
+__rte_experimental
+int
+rte_malloc_log_init(uint32_t count);
+
+/**
+ * Stop malloc tracking log.
+ *
+ * @return
+ *   0 on success, negative errno otherwise
+ */
+__rte_experimental
+int
+rte_malloc_log_stop(void);
+
+/**
+ * Dump malloc tracking log to output.
+ *
+ * @param out
+ *   output file handle
+ * @param detail
+ *   detail level of output
+ *   0: summary
+ *   1: potential leaks - allocated without free
+ *   2: all entries
+ */
+__rte_experimental
+void
+rte_malloc_log_dump(FILE *out, uint32_t detail);
 
 /**
  * Dump contents of all malloc heaps to a file.
diff --git a/lib/librte_eal/rte_eal_version.map b/lib/librte_eal/rte_eal_version.map
index f9ede5b..5151057 100644
--- a/lib/librte_eal/rte_eal_version.map
+++ b/lib/librte_eal/rte_eal_version.map
@@ -338,4 +338,7 @@  EXPERIMENTAL {
 
 	# added in 20.05
 	rte_log_can_log;
+	rte_malloc_log_init;
+	rte_malloc_log_dump;
+	rte_malloc_log_stop;
 };