This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

RFC: api to walk memory allocations


We are exploring options for debugging memory corruption and leaks. One of the ideas involves walking allocations and extending glibc with a generic hook for that. Before we get too far down this road I wanted to get thoughts from the glibc maintainers on the idea and whether such an extension would be accepted into glibc.

The API we have discussed is:

+/* Walk user memory
+ *
+ * This walker function is provided for users to manually inspect
+ * their current allocated memory. It returns used memory which is
+ * previously passed to application from malloc() family calls,
+ * it also returns unused memory which is previously returned to system
+ * by application using free() family calls. Information is passed
+ * back to application through (*fn) callback.
+ *
+ * If walker finds error in current memory space, it calls (*errfn)
+ * with reason and suspected address.
+ *
+ * Application can provide *priv that is simply passed back through
+ * either (*fn) or (*errfn) to application as last argument.

... (snipped warning and caveat to keep focus on the idea)

+ */
+
+struct memwalk_opts {
+  int  (*fn)(void *adr, size_t len, int in_use, void *priv);
+  void (*errfn)(const char *err, void *adr, void *priv);
+  void  *priv;
+  char  *errbuf;
+  size_t errbuf_size;
+};
+
+extern int memwalk (struct memwalk_opts *opt);


The relevant code snippet from the POC:

diff --git a/malloc/malloc.c b/malloc/malloc.c
index b492c0a..283f3e0 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c

+static int
+memwalk_sanitize(Void_t *p, struct memwalk_opts *opt)
+{
+  /* XXX Add real logic here next, current code is just for test */
+  if (p == 0) {
+    if (opt && opt->errfn &&  opt->errbuf) {
+      #define DUMMYERR "sanitize dummy failure"
+      if (strlen(DUMMYERR) < opt->errbuf_size) {
+        strncpy(&opt->errbuf, DUMMYERR, strlen(DUMMYERR));
+        opt->errfn(&opt->errbuf, 0xfeedbeef, opt->priv);
+      }
+    }
+  }
+  return 0;
+}
+
+static int
+_int_memwalk_main(Void_t *start, Void_t *end, struct memwalk_opts *opt)
+{
+  mchunkptr p = (mchunkptr)start;
+  int result;
+  while ((uintptr_t)p < (uintptr_t)end) {
+    result = memwalk_sanitize(p, opt);
+    if (result) {
+      return result;
+    }
+    if (opt && opt->fn) {
+ result = opt->fn(chunk2mem(p), chunksize(p) - 2*SIZE_SZ, inuse(p), opt->priv);
+      if (result) {
+        return result;
+      }
+    }
+    p = next_chunk(p);
+  }
+  return 0;
+}
+
+static int
+_int_memwalk_nonmain_heap(heap_info *heap,
+                          struct memwalk_opts *opt)
+{
+  char *ptr;
+  mchunkptr p;
+  int result;
+
+  ptr = (heap->ar_ptr != (mstate)(heap+1)) ?
+    (char*)(heap + 1) : (char*)(heap + 1) + sizeof(struct malloc_state);
+  p = (mchunkptr)(((unsigned long)ptr + MALLOC_ALIGN_MASK) &
+                  ~MALLOC_ALIGN_MASK);
+  for(;;) {
+    if(p == top(heap->ar_ptr)) {
+      /* This is top */
+ memwalk_sanitize(0, opt); // XXX this is a test code, remove before release
+      break;
+    } else if(p->size == (0|PREV_INUSE)) {
+      /* This is fence */
+      break;
+    }
+    result = memwalk_sanitize(p, opt);
+    if (result) {
+      return result;
+    }
+    if (opt && opt->fn) {
+ result = opt->fn(chunk2mem(p), chunksize(p) - 2*SIZE_SZ, inuse(p), opt->priv);
+      if (result) {
+        return result;
+      }
+    }
+    p = next_chunk(p);
+  }
+  return 0;
+}
+
+static int
+_int_memwalk_nonmain(Void_t *start, Void_t *end, struct memwalk_opts *opt)
+{
+  heap_info *current = heap_for_ptr(end);
+  heap_info *first = heap_for_ptr(start);
+  int result;
+
+  for (;;) {
+    result = _int_memwalk_nonmain_heap(current, opt);
+    if (result) {
+      return result;
+    }
+    if (current == first) {
+      break;
+    }
+    current = current->prev;
+  }
+  return 0;
+}
+
+int
+public_mEMWALk(struct memwalk_opts *opt)
+{
+  /* */
+  mstate av = &main_arena;
+  int result;
+  if(__malloc_initialized < 0)
+    ptmalloc_init ();
+  /* Lock, consolidate, and walk */
+  do
+    {
+      (void)mutex_lock(&av->mutex);
+      malloc_consolidate(av);          // XXX this needs discussion
+      if (av == &main_arena) {
+        //printf("walking main arena: %p-%p\n", mp_.sbrk_base, av->top);
+        result = _int_memwalk_main(mp_.sbrk_base, av->top, opt);
+      } else {
+        //printf("walking non main arena: %p-%p\n", (av + 1), av->top);
+        result = _int_memwalk_nonmain((av + 1), av->top, opt);
+      }
+      (void)mutex_unlock(&av->mutex);
+      if (result) {
+        //printf("stopped walking due to result=%d", result);
+        return result;
+      }
+      av = av->next;
+    }
+  while (av != &main_arena);
+  return 0;
+}

The idea here is to have something that can be used within gdb as well as something that could be invoked runtime for a live dump. Any feedback on the idea would be appreciated.

Thanks,
David

PS. I am not subscribed to the list, so please CC me on responses.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]