This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
RFC: api to walk memory allocations
- From: David Ahern <dsahern at gmail dot com>
- To: libc-alpha at sourceware dot org
- Date: Wed, 30 Oct 2013 16:30:59 -0600
- Subject: RFC: api to walk memory allocations
- Authentication-results: sourceware.org; auth=none
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.