Skip to content

Commit

Permalink
Provide mechanism to track allocations with heap profiling tools
Browse files Browse the repository at this point in the history
Tools such as Valgrind's massif[1] and KDE heaptrack[2] work by
intercepting dynamically linked allocator calls so that they can do
their own bookkeeping for tracking memory usage. While this enables them
to trivially hook into any of the BDWGC calls which allocate, they
aren't currently able to tell when the GC frees memory.

This commit introduces a new function to the API,
`GC_free_profiler_hook`, which is called for each object during the
reclaim phase of the GC. It provides a stub for heap profiling tools
to override so that they can see which objects were freed.

This functionality is feature gated behind the `-DVALGRIND_TRACKING`
flag.

[1]: https://valgrind.org/docs/manual/ms-manual.html
[2]: https://github.com/KDE/heaptrack
  • Loading branch information
jacob-hughes committed Mar 6, 2024
1 parent 0c88daa commit 59d89a3
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ option(enable_emscripten_asyncify "Use Emscripten asyncify feature" OFF)
option(install_headers "Install header and pkg-config metadata files" ON)
option(with_libatomic_ops "Use an external libatomic_ops" OFF)
option(without_libatomic_ops "Use atomic_ops.h in libatomic_ops/src" OFF)
option(enable_track_valgrind "Support tracking GC_malloc and friends for heap profiling tools" OFF)

# Override the default build type to RelWithDebInfo (this instructs cmake to
# pass -O2 -g -DNDEBUG options to the compiler by default).
Expand Down Expand Up @@ -714,6 +715,10 @@ if (build_cord)
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
endif(build_cord)

if (enable_valgrind_tracking)
add_definitions("-DVALGRIND_TRACKING")
endif()

if (BUILD_SHARED_LIBS AND HAVE_FLAG_WL_NO_UNDEFINED)
# Declare that the libraries do not refer to external symbols.
if (${CMAKE_VERSION} VERSION_LESS "3.13.0")
Expand Down
6 changes: 6 additions & 0 deletions allchblk.c
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,12 @@ STATIC struct hblk *GC_allochblk_nth(size_t lb_adjusted, int k,
return hbp;
}

# if defined(VALGRIND_TRACKING)
GC_API void GC_CALL GC_free_profiler_hook(void * p) {
UNUSED_ARG(p);
}
# endif

/*
* Free a heap block.
*
Expand Down
8 changes: 8 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,14 @@ if test "${enable_gc_assertions}" = yes; then
[Define to enable internal debug assertions.])
fi

AC_ARG_ENABLE(valgrind-tracking,
[AS_HELP_STRING([--enable-valgrind-tracking],
[heap profiler allocation tracking])])
if test "${enable_valgrind_tracking}" = yes; then
AC_DEFINE([VALGRIND_TRACKING], 1,
[Define to enable heap profiler allocation tracking.])
fi

AC_ARG_ENABLE(mmap,
[AS_HELP_STRING([--enable-mmap],
[use mmap instead of sbrk to expand the heap])],
Expand Down
14 changes: 14 additions & 0 deletions include/gc/gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,20 @@ GC_API int GC_CALL GC_posix_memalign(void ** /* memptr */, size_t /* align */,
/* GC_free(0) is a no-op, as required by ANSI C for free. */
GC_API void GC_CALL GC_free(void *);

# if defined(VALGRIND_TRACKING)
/* Notify heap profiling tools that an object was deallocated. */
/* Programs such as Valgrind massif and KDE heaptrack track allocated */
/* objects by overriding common allocator methods (e.g. malloc and */
/* free). However, because GC doesn't work by calling a standard alloc */
/* methods on objects which were reclaimed, we need a way to tell */
/* profilers that an object has been freed. */
/* */
/* This function is called from the sweeper whenever an object is */
/* freed, which can then be intercepted by heap profilers so that */
/* they can accurately track allocations. */
GC_API void GC_free_profiler_hook(void *);
# endif

/* The "stubborn" objects allocation is not supported anymore. Exists */
/* only for the backward compatibility. */
#define GC_MALLOC_STUBBORN(sz) GC_MALLOC(sz)
Expand Down
4 changes: 4 additions & 0 deletions malloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,10 @@ GC_API void GC_CALL GC_free(void * p)
return;
}

# if defined(VALGRIND_TRACKING)
GC_free_profiler_hook(p);
# endif

# ifdef LOG_ALLOCS
GC_log_printf("GC_free(%p) after GC #%lu\n",
p, (unsigned long)GC_gc_no);
Expand Down
37 changes: 37 additions & 0 deletions reclaim.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ STATIC ptr_t GC_reclaim_clear(struct hblk *hbp, hdr *hhdr, word sz,
obj_link(p) = list;
list = p;

# if defined(VALGRIND_TRACKING)
GC_free_profiler_hook(p);
# endif

p = (ptr_t)GC_clear_block((word *)p, sz, pcount);
}
bit_no += MARK_BIT_OFFSET(sz);
Expand Down Expand Up @@ -227,6 +231,9 @@ STATIC ptr_t GC_reclaim_uninit(struct hblk *hbp, hdr *hhdr, word sz,
n_bytes_found += sz;
/* object is available - put on list */
obj_link(p) = list;
# if defined(VALGRIND_TRACKING)
GC_free_profiler_hook(p);
# endif
list = ((ptr_t)p);
}
p = (word *)((ptr_t)p + sz);
Expand Down Expand Up @@ -291,6 +298,27 @@ STATIC void GC_reclaim_check(struct hblk *hbp, hdr *hhdr, word sz)
}
}

# if defined(VALGRIND_TRACKING)
/* Call GC_free_profiler_hook on freed objects so that profiling */
/* tools can track allocations. */
STATIC void GC_reclaim_check_for_profiler(struct hblk *hbp, word sz)
{
hdr *hhdr = HDR(hbp);
word bit_no;
ptr_t p, plim;

/* go through all words in block */
p = hbp->hb_body;
plim = p + HBLKSIZE - sz;
for (bit_no = 0; (word)p <= (word)plim;
p += sz, bit_no += MARK_BIT_OFFSET(sz)) {
if (!mark_bit_from_hdr(hhdr, bit_no)) {
GC_free_profiler_hook(p);
}
}
}
# endif

/* Is a pointer-free block? Same as IS_PTRFREE() macro but uses */
/* unordered atomic access to avoid racing with GC_realloc. */
#ifdef AO_HAVE_load
Expand Down Expand Up @@ -431,6 +459,9 @@ STATIC void GC_CALLBACK GC_reclaim_block(struct hblk *hbp,
}
GC_bytes_found += (signed_word)sz;
GC_freehblk(hbp);
# if defined(VALGRIND_TRACKING)
GC_free_profiler_hook(hbp);
# endif
}
} else {
# ifdef ENABLE_DISCLAIM
Expand All @@ -455,6 +486,9 @@ STATIC void GC_CALLBACK GC_reclaim_block(struct hblk *hbp,
* (HBLKSIZE/sz + 1) + 16 >= hhdr->hb_n_marks);
# else
GC_ASSERT(sz * hhdr -> hb_n_marks <= HBLKSIZE);
# endif
# if defined(VALGRIND_TRACKING)
GC_reclaim_check_for_profiler(hbp, sz);
# endif
if (report_if_found) {
GC_reclaim_small_nonempty_block(hbp, sz,
Expand All @@ -468,6 +502,9 @@ STATIC void GC_CALLBACK GC_reclaim_block(struct hblk *hbp,
/* else */ {
GC_bytes_found += (signed_word)HBLKSIZE;
GC_freehblk(hbp);
# if defined(VALGRIND_TRACKING)
GC_free_profiler_hook(hbp);
# endif
}
} else if (GC_find_leak || !GC_block_nearly_full(hhdr, sz)) {
/* group of smaller objects, enqueue the real work */
Expand Down

0 comments on commit 59d89a3

Please sign in to comment.