aboutsummaryrefslogtreecommitdiffstats
path: root/memcheck.c
diff options
context:
space:
mode:
Diffstat (limited to 'memcheck.c')
-rw-r--r--memcheck.c638
1 files changed, 638 insertions, 0 deletions
diff --git a/memcheck.c b/memcheck.c
new file mode 100644
index 0000000..b119fad
--- /dev/null
+++ b/memcheck.c
@@ -0,0 +1,638 @@
+/* See LICENSE file for copyright and license details. */
+#include "memcheck.h"
+
+#include <sys/syscall.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#include <elfutils/libdwfl.h>
+
+
+enum alloctype {
+ ALLOCTYPE_REALLOC,
+ ALLOCTYPE_MMAP
+};
+
+#ifdef PRINT_BACKTRACES
+struct backtrace {
+ size_t alloc_size;
+ size_t n;
+ uintptr_t rips[];
+};
+#endif
+
+struct allocinfo {
+ void *real;
+ void *ptr;
+ size_t real_size;
+ enum alloctype type;
+ int flags;
+ int dont_track;
+#ifdef PRINT_BACKTRACES
+ struct backtrace *backtrace;
+#endif
+};
+
+
+static struct allocinfo *allocs = NULL;
+static size_t nallocs = 0;
+static size_t allocs_size = 0;
+static size_t alloc_fail_exclusion = 0;
+static size_t memleak_exclusion = 1;
+static size_t alloc_fail_in = 0;
+static int alloc_fail_all = 0;
+static int have_custom_malloc = 0;
+
+
+static void *
+memcheck_mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off)
+{
+#ifdef SYS_mmap2
+ unsigned long pgoff = (unsigned long)(off / 4096);
+ long int r = syscall(SYS_mmap2, addr, len, (unsigned long)prot, (unsigned long)flags, fd, pgoff);
+#else
+ long int r = syscall(SYS_mmap, addr, len, (unsigned long)prot, (unsigned long)flags, fd, off);
+#endif
+ return (void *)r;
+}
+
+
+static void *
+memcheck_mremap(void *old, size_t old_size, size_t new_size, int flags, void *new_address)
+{
+ long int r = syscall(SYS_mremap, old, old_size, new_size, flags, new_address);
+ return (void *)r;
+}
+
+
+static int
+memcheck_munmap(void *ptr, size_t n)
+{
+ return (int)syscall(SYS_munmap, ptr, n);
+}
+
+
+static void *
+memcheck_mmap_ram(size_t n)
+{
+ return memcheck_mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+
+#ifdef PRINT_BACKTRACES
+static struct backtrace *
+memcheck_create_backtrace(void)
+{
+ struct backtrace *ret = NULL, *new;
+ unw_word_t rip;
+ unw_cursor_t cursor;
+ unw_context_t context;
+ size_t n = 0, size = 0;
+ int saved_errno = errno;
+
+ memleak_exclusion += 1;
+
+ if (unw_getcontext(&context))
+ goto fail;
+ if (unw_init_local(&cursor, &context))
+ goto fail;
+
+ n = 8;
+ size = offsetof(struct backtrace, rips) + n * sizeof(*ret->rips);
+ ret = memcheck_mmap_ram(size);
+ if (!ret)
+ goto fail;
+ ret->alloc_size = size;
+
+ ret->n = 0;
+ while (unw_step(&cursor) > 0) {
+ if (unw_get_reg(&cursor, UNW_REG_IP, &rip))
+ goto fail;
+ if (ret->n == n) {
+ n += 8;
+ size = offsetof(struct backtrace, rips) + n * sizeof(*ret->rips);
+ new = memcheck_mmap_ram(size);
+ if (!new)
+ goto fail;
+ new->alloc_size = size;
+ new->n = ret->n;
+ memcpy(new->rips, ret->rips, n * sizeof(*ret->rips));
+ memcheck_munmap(ret, ret->alloc_size);
+ ret = new;
+ }
+ ret->rips[ret->n++] = (uintptr_t)rip;
+ }
+
+ errno = saved_errno;
+ memleak_exclusion -= 1;
+ return ret;
+
+fail:
+ if (ret)
+ memcheck_munmap(ret, ret->alloc_size);
+ errno = saved_errno;
+ memleak_exclusion -= 1;
+ return NULL;
+}
+#endif
+
+
+#ifdef PRINT_BACKTRACES
+static void
+memcheck_print_backtrace(struct backtrace *backtrace, const char *indent)
+{
+ int saved_errno = errno, lineno;
+ char *debuginfo_path = NULL;
+ Dwarf_Addr ip;
+ Dwfl_Callbacks callbacks;
+ Dwfl *dwfl = NULL;
+ Dwfl_Line *line = NULL;
+ Dwfl_Module *module = NULL;
+ const char *filename = NULL;
+ const char *funcname = NULL;
+ size_t i;
+
+ if (!backtrace)
+ return;
+
+ memleak_exclusion += 1;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ callbacks.find_elf = dwfl_linux_proc_find_elf;
+ callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
+ callbacks.debuginfo_path = &debuginfo_path;
+
+ if (dwfl) {
+ if (dwfl_linux_proc_report(dwfl, getpid()) ||
+ dwfl_report_end(dwfl, NULL, NULL)) {
+ perror("");
+ dwfl_end(dwfl);
+ dwfl = NULL;
+ }
+ }
+
+ for (i = 0; i < backtrace->n; i++) {
+ ip = (Dwarf_Addr)backtrace->rips[i];
+
+ if (dwfl) {
+ module = dwfl_addrmodule(dwfl, ip);
+ funcname = module ? dwfl_module_addrname(module, ip) : NULL;
+ line = dwfl_getsrc(dwfl, ip);
+ if (line)
+ filename = dwfl_lineinfo(line, &(Dwarf_Addr){0}, &lineno, NULL, NULL, NULL);
+ }
+
+ dprintf(STDERR_FILENO, "%s%s 0x%016"PRIxPTR": %s",
+ indent, !i ? "at" : "by",
+ (uintptr_t)ip,
+ funcname ? funcname : "???");
+ if (line)
+ dprintf(STDERR_FILENO, " (%s:%i)\n", filename, lineno);
+ else
+ dprintf(STDERR_FILENO, "\n");
+ }
+
+ if (dwfl)
+ dwfl_end(dwfl);
+
+ errno = saved_errno;
+ memleak_exclusion -= 1;
+}
+#endif
+
+
+#if defined(__GNUC__)
+__attribute__((__format__(__gnu_printf__, 1, 2)))
+#endif
+static void
+memcheck_errorf(const char *fmt, ...)
+{
+ va_list ap;
+ memleak_exclusion = 1;
+ va_start(ap, fmt);
+ vdprintf(STDERR_FILENO, fmt, ap);
+ dprintf(STDERR_FILENO, ", aborting...\n");
+ va_end(ap);
+#ifdef PRINT_BACKTRACES
+ memcheck_print_backtrace(memcheck_create_backtrace(), "\t");
+#endif
+ abort();
+}
+
+
+#if defined(__GNUC__)
+__attribute__((__pure__))
+#endif
+static struct allocinfo *
+memcheck_getalloc(void *ptr)
+{
+ size_t i;
+ for (i = 0; i < nallocs; i++)
+ if (allocs[i].ptr == ptr)
+ return &allocs[i];
+ return NULL;
+}
+
+
+static int
+memcheck_fake_fail(void)
+{
+ have_custom_malloc = 1;
+
+ if (!alloc_fail_exclusion) {
+ if (alloc_fail_all || (alloc_fail_in && !--alloc_fail_in)) {
+ alloc_fail_all = 1;
+ errno = ENOMEM;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static void
+memcheck_putalloc(struct allocinfo *info)
+{
+ if (nallocs == allocs_size) {
+ size_t old_allocs_size = allocs_size;
+ void *new;
+ if (allocs_size > SIZE_MAX / sizeof(*allocs) - 64)
+ memcheck_errorf("size of memory bookkeeping will exceed SIZE_MAX");
+ allocs_size += 64;
+ new = memcheck_mmap_ram(allocs_size * sizeof(*allocs));
+ if (!new)
+ memcheck_errorf("failed to allocate enought memory for bookkeeping");
+ if (allocs) {
+ memcpy(new, allocs, nallocs * sizeof(*allocs));
+ memcheck_munmap(allocs, old_allocs_size * sizeof(*allocs));
+ }
+ allocs = new;
+ }
+ allocs[nallocs++] = *info;
+}
+
+
+static int
+memcheck_free(void *ptr, size_t size, enum alloctype type)
+{
+ int ret;
+ struct allocinfo *alloc;
+ alloc = memcheck_getalloc(ptr);
+ if (!alloc)
+ memcheck_errorf("attempting to deallocate unknown pointer %p", ptr);
+ if (alloc->type != type)
+ memcheck_errorf("attempting to deallocating with incompatible function");
+ if (type == ALLOCTYPE_MMAP && size != alloc->real_size)
+ memcheck_errorf("attempting to munmap(2) pointer %p with wrong size", ptr);
+ ret = memcheck_munmap(alloc->real, alloc->real_size);
+#ifdef PRINT_BACKTRACES
+ if (alloc->backtrace)
+ memcheck_munmap(alloc->backtrace, alloc->backtrace->alloc_size);
+#endif
+ *alloc = allocs[--nallocs];
+ if (!nallocs) {
+ memcheck_munmap(allocs, allocs_size * sizeof(*allocs));
+ allocs = NULL;
+ nallocs = 0;
+ allocs_size = 0;
+ }
+ return ret;
+}
+
+
+static void *
+memcheck_realloc(void *old, size_t n, size_t m, size_t alignment, int initbyte)
+{
+ struct allocinfo info;
+ uintptr_t addr;
+ void *ret;
+
+ if (memcheck_fake_fail())
+ return NULL;
+
+ if (!n || !m)
+ memcheck_errorf("attempting to allocate with zero size which is implementation-defined behaviour");
+ if (n > SIZE_MAX / m)
+ memcheck_errorf("attempting allocate more than SIZE_MAX");
+
+ if (!alignment)
+ alignment = _Alignof(max_align_t);
+
+ n *= m;
+ if (n > SIZE_MAX - (alignment - 1U))
+ memcheck_errorf("attempting allocate more than SIZE_MAX after alignment padding");
+
+#ifdef PRINT_BACKTRACES
+ info.backtrace = memleak_exclusion ? NULL : memcheck_create_backtrace();
+#endif
+ info.dont_track = memleak_exclusion > 0;
+ info.flags = 0;
+ info.type = ALLOCTYPE_REALLOC;
+ info.real_size = n + (alignment - 1U);
+ info.real = memcheck_mmap_ram(info.real_size);
+ if (!info.real)
+ memcheck_errorf("failed to allocate enough memory");
+ memset(info.real, initbyte, info.real_size);
+
+ addr = (uintptr_t)info.real;
+ if (addr % alignment)
+ addr += alignment - addr % alignment;
+ ret = info.ptr = (void *)addr;
+
+ memcheck_putalloc(&info);
+
+ if (old) {
+ struct allocinfo *oldinfo;
+ oldinfo = memcheck_getalloc(old);
+ if (!oldinfo)
+ memcheck_errorf("attempting to reallocate unknown pointer %p", old);
+ memcpy(info.real, oldinfo->real, info.real_size < oldinfo->real_size ? info.real_size : oldinfo->real_size);
+ memcheck_free(old, 0, ALLOCTYPE_REALLOC);
+ }
+
+ return ret;
+}
+
+
+
+/* Implementation of checked functions */
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wmissing-prototypes"
+#endif
+
+
+void *
+mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off)
+{
+ struct allocinfo info;
+ void *ret;
+
+ if (off % 4096)
+ memcheck_errorf("attempting to mmap(2) with offset %ju which is not a multiple of 4096", (uintmax_t)off);
+
+#ifdef PRINT_BACKTRACES
+ info.backtrace = memleak_exclusion ? NULL : memcheck_create_backtrace();
+#endif
+ info.dont_track = memleak_exclusion > 0;
+ info.flags = flags;
+ info.type = ALLOCTYPE_MMAP;
+ info.real_size = len;
+ info.real = memcheck_mmap(addr, len, prot, flags, fd, off);
+ if (!info.real)
+ memcheck_errorf("failed to mmap(2)");
+ ret = info.ptr = info.real;
+
+ memcheck_putalloc(&info);
+ return ret;
+}
+
+
+int
+munmap(void *ptr, size_t n)
+{
+ return memcheck_free(ptr, n, ALLOCTYPE_MMAP);
+}
+
+
+void *
+mremap(void *old, size_t old_size, size_t new_size, int flags, ...)
+{
+ struct allocinfo *oldinfo;
+ void *new_address = NULL;
+ void *ret;
+
+ if (memcheck_fake_fail())
+ return NULL;
+
+ oldinfo = memcheck_getalloc(old);
+ if (!oldinfo)
+ memcheck_errorf("attempting to mremap(2) unknown pointer %p", old);
+ if (oldinfo->type != ALLOCTYPE_MMAP)
+ memcheck_errorf("attempting to mremap(2) pointer %p that was not allocated with mmap(2) or mremap(2)", old);
+
+ if (flags & MREMAP_FIXED) {
+ va_list ap;
+ va_start(ap, flags);
+ new_address = va_arg(ap, void *);
+ va_end(ap);
+ }
+
+ ret = memcheck_mremap(old, old_size, new_size, flags, new_address);
+ if (!ret)
+ return NULL;
+
+ if (ret == old || !(flags & MREMAP_DONTUNMAP)) {
+ oldinfo->ptr = oldinfo->real = ret;
+ oldinfo->real_size = new_size;
+ } else {
+ struct allocinfo info;
+ info = *oldinfo;
+ info.ptr = info.real = ret;
+ info.real_size = new_size;
+ memcheck_putalloc(&info);
+ }
+
+ return ret;
+}
+
+
+void
+free(void *ptr)
+{
+ if (!ptr)
+ return;
+ memcheck_free(ptr, 0, ALLOCTYPE_REALLOC);
+}
+
+
+void *
+malloc(size_t n)
+{
+ return memcheck_realloc(NULL, 1, n, 0, 'x');
+}
+
+
+void *
+calloc(size_t n, size_t m)
+{
+ return memcheck_realloc(NULL, n, m, 0, 0);
+}
+
+
+void *
+realloc(void *old, size_t n)
+{
+ return memcheck_realloc(old, 1, n, 0, 'x');
+}
+
+
+void *
+reallocarray(void *old, size_t n, size_t m)
+{
+ return memcheck_realloc(old, n, m, 0, 'x');
+}
+
+
+void *
+memdup(const void *s, size_t n)
+{
+ char *r = memcheck_realloc(NULL, 1, n, 0, 'x');
+ if (r)
+ memcpy(r, s, n);
+ return r;
+}
+
+
+char *
+strdup(const char *s)
+{
+ size_t n = strlen(s) + 1U;
+ char *r = memcheck_realloc(NULL, 1, n, 0, 'x');
+ if (r)
+ memcpy(r, s, n);
+ return r;
+}
+
+
+char *
+strndup(const char *s, size_t max)
+{
+ size_t n = strnlen(s, max) + 1U;
+ char *r = memcheck_realloc(NULL, 1, n, 0, 'x');
+ if (n > max)
+ n = max;
+ if (r)
+ memcpy(r, s, n);
+ return r;
+}
+
+
+int
+posix_memalign(void **ret_out, size_t align, size_t n)
+{
+ int saved_errno, r;
+ saved_errno = errno, errno = 0;
+ if (!align || (align & (align - 1U)) || align % sizeof(void *))
+ memcheck_errorf("invalid alignment for posix_memalign: %zu", align);
+ *ret_out = memcheck_realloc(NULL, 1, n, align, 'x');
+ return r = errno, errno = saved_errno, r;
+}
+
+
+void *
+memalign(size_t align, size_t n)
+{
+ if (!align)
+ memcheck_errorf("invalid alignment for memalign: %zu", align);
+ return memcheck_realloc(NULL, 1, n, align, 'x');
+}
+
+
+void *
+aligned_alloc(size_t align, size_t n)
+{
+ if (!align || (align & (align - 1U)))
+ memcheck_errorf("invalid alignment for aligned_alloc: %zu", align);
+ return memcheck_realloc(NULL, 1, n, align, 'x');
+}
+
+
+void *
+valloc(size_t n)
+{
+ return memcheck_realloc(NULL, 1, n, 4096U, 'x');
+}
+
+
+void *
+pvalloc(size_t n)
+{
+ if (n % 4096U) {
+ n |= 4096U - 1U;
+ if (n == SIZE_MAX)
+ memcheck_errorf("attempting to allocate beyond SIZE_MAX");
+ n += 1U;
+ }
+ return memcheck_realloc(NULL, 1, n, 4096U, 'x');
+}
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+
+
+/* Test functions */
+
+
+int
+memcheck_have_custom_malloc(void)
+{
+ static void *volatile test_have_custom_malloc_ptr = NULL;
+ int saved_errno = errno;
+ alloc_fail_exclusion++;
+ memleak_exclusion++;
+ test_have_custom_malloc_ptr = malloc(1);
+ free(test_have_custom_malloc_ptr);
+ memleak_exclusion--;
+ alloc_fail_exclusion--;
+ errno = saved_errno;
+ return have_custom_malloc;
+}
+
+size_t
+memcheck_alloc_fail_in(size_t n)
+{
+ size_t ret = alloc_fail_in;
+ alloc_fail_in = n;
+ alloc_fail_all = 0;
+ return ret;
+}
+
+
+int
+memcheck_check_memleaks(void)
+{
+ struct allocinfo *leak;
+ size_t i, count = 0;
+ memleak_exclusion = 1;
+ for (i = 0; i < nallocs; i++) {
+ leak = &allocs[i];
+ if (leak->dont_track)
+ continue;
+ count += 1;
+ }
+ if (!count)
+ return 1;
+ dprintf(STDERR_FILENO, "%zu %s!\n", count, count == 1 ? "memory leak" : "memory leaks");
+#ifdef PRINT_BACKTRACES
+ for (i = 0; i < nallocs; i++) {
+ leak = &allocs[i];
+ if (leak->dont_track)
+ continue;
+ memcheck_print_backtrace(leak->backtrace, "\t");
+ }
+#endif
+ return 0;
+}
+
+void memcheck_alloc_fail_exclusion_begin(void) { alloc_fail_exclusion++; }
+void memcheck_alloc_fail_exclusion_end(void) { alloc_fail_exclusion--; }
+void memcheck_exclusion_begin(void) { memleak_exclusion++; }
+void memcheck_exclusion_end(void) { memleak_exclusion--; }
+void memcheck_begin(void) { memleak_exclusion = 0; }