diff options
| author | Mattias Andrée <m@maandree.se> | 2026-05-08 22:29:35 +0200 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2026-05-08 22:29:35 +0200 |
| commit | 2d3a573977417d917c16742d8d9d8ead047d0ebc (patch) | |
| tree | caeac52856a9df0478e2bee53e5dda1f84422461 /libtest | |
| parent | Add DEFAULT_SUPPORT option and improve DEPENDENCIES (diff) | |
| download | librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.gz librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.bz2 librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.xz | |
Misc
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
| -rw-r--r-- | libtest/Makefile | 78 | ||||
| -rw-r--r-- | libtest/alloc.c | 505 | ||||
| -rw-r--r-- | libtest/alloc_have_custom.c | 236 | ||||
| -rw-r--r-- | libtest/common.h | 242 | ||||
| -rw-r--r-- | libtest/config.mk | 4 | ||||
| -rw-r--r-- | libtest/config_backtraces=false.mk | 10 | ||||
| -rw-r--r-- | libtest/config_backtraces=true.mk | 12 | ||||
| -rw-r--r-- | libtest/globals.c | 44 | ||||
| -rw-r--r-- | libtest/libtest.h | 23 | ||||
| -rw-r--r-- | libtest/libtest_alloc.c | 208 | ||||
| -rw-r--r-- | libtest/libtest_base_pointer.c | 20 | ||||
| -rw-r--r-- | libtest/libtest_check_no_leaks.c | 77 | ||||
| -rw-r--r-- | libtest/libtest_dump_stack.c | 34 | ||||
| -rw-r--r-- | libtest/libtest_expect_zeroed_on_free.c | 31 | ||||
| -rw-r--r-- | libtest/libtest_force_zero_on_alloc.c | 31 | ||||
| -rw-r--r-- | libtest/libtest_free.c | 94 | ||||
| -rw-r--r-- | libtest/libtest_get_pagesize.c | 37 | ||||
| -rw-r--r-- | libtest/libtest_print_backtrace.c | 122 | ||||
| -rw-r--r-- | libtest/libtest_start_tracking.c | 29 | ||||
| -rw-r--r-- | libtest/libtest_stop_tracking.c | 34 |
20 files changed, 1871 insertions, 0 deletions
diff --git a/libtest/Makefile b/libtest/Makefile new file mode 100644 index 0000000..1bb870b --- /dev/null +++ b/libtest/Makefile @@ -0,0 +1,78 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OS = linux +# Linux: linux +# Mac OS: macos +# Windows: windows +include ../mk/$(OS).mk + + +OBJ =\ + alloc.o\ + alloc_have_custom.o\ + globals.o\ + libtest_alloc.o\ + libtest_base_pointer.o\ + libtest_check_no_leaks.o\ + libtest_expect_zeroed_on_free.o\ + libtest_force_zero_on_alloc.o\ + libtest_free.o\ + libtest_get_pagesize.o\ + libtest_start_tracking.o\ + libtest_stop_tracking.o\ + libtest_dump_stack.o\ + $(OBJ_BACKTRACE) + +HDR =\ + libtest.h\ + common.h\ + +LOBJ = $(OBJ:.o=.lo) +TOBJ = $(OBJ:.o=.to) +TEST = $(OBJ:.o=.t) + + +all: libtest.a $(TEST) +$(OBJ): $(HDR) +$(LOBJ): $(HDR) +$(TOBJ): $(HDR) +$(TEST): $(HDR) libtest.a + +.c.o: + $(C17) -fno-builtin -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS) + +.c.lo: + $(C17) -fno-builtin -fPIC -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS) + +.c.to: + $(C17) -DTEST -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS) + +.to.t: + $(C17) -o $@ $< libtest.a $(TEST_LDFLAGS) + +.c.t: + $(C17) -DTEST -o $@ $< libtest.a $(TEST_CFLAGS) $(TEST_CPPFLAGS) $(TEST_LDFLAGS) + +libtest.a: $(OBJ) + @rm -f -- $@ + $(AR) rc $@ $(OBJ) + $(AR) ts $@ > /dev/null + +check: $(TEST) + @set -ex;\ + for t in $(TEST); do\ + ./$$t;\ + done + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib + -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) + -rm -f -- *.to *.t + +.SUFFIXES: +.SUFFIXES: .lo .o .c .to .t + +.PHONY: all check install uninstall clean diff --git a/libtest/alloc.c b/libtest/alloc.c new file mode 100644 index 0000000..842f856 --- /dev/null +++ b/libtest/alloc.c @@ -0,0 +1,505 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +static void * +common_malloc(size_t n, enum memory_origin origin) +{ + struct meminfo meminfo; + + assert(n); + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = n; + meminfo.alignment_type = DEFAULT_ALIGNMENT; + meminfo.initialised_on_alloc = 0; + meminfo.origin = origin; + + return libtest_alloc(&meminfo); +} + + +static void * +common_realloc(void *old_ptr, size_t new_n, enum memory_origin origin) +{ + void *new_ptr; + size_t old_n; + + assert(new_n); + if (!old_ptr) + return common_malloc(new_n, origin); + + assert(GET_MEMINFO(old_ptr)->requested_alignment <= _Alignof(max_align_t)); + + new_ptr = common_malloc(new_n, origin); /* always change the pointer */ + if (!new_ptr) + return NULL; + + new_n = GET_MEMINFO(new_ptr)->usable_alloc_size; + old_n = GET_MEMINFO(old_ptr)->usable_alloc_size; + memcpy(new_ptr, old_ptr, new_n < old_n ? new_n : old_n); + libtest_free(old_ptr, DO_NOT_REQUIRE_ZEROED); + + return new_ptr; +} + + +static void * +common_memalign(size_t alignment, size_t size, enum memory_origin origin) +{ + struct meminfo meminfo; + + assert(size); + assert(alignment); + assert(!(alignment & (alignment - 1u))); + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = size; + meminfo.alignment_type = CUSTOM_ALIGNMENT; + meminfo.requested_alignment = alignment; + meminfo.initialised_on_alloc = 0; + meminfo.origin = origin; + + return libtest_alloc(&meminfo); +} + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wmissing-prototypes" +#endif + + +void * +(malloc)(size_t n) +{ + libtest_malloc_is_custom = 1; + assert(libtest_have_custom_free()); + + return common_malloc(n, FROM_MALLOC); +} + + +void * +(calloc)(size_t n, size_t m) +{ + struct meminfo meminfo; + + libtest_calloc_is_custom = 1; + assert(libtest_have_custom_free()); + + assert(n); + assert(m); + + if (n > SIZE_MAX / m) { + errno = ENOMEM; + return NULL; + } + n *= m; + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = n; + meminfo.alignment_type = DEFAULT_ALIGNMENT; + meminfo.initialised_on_alloc = 1; + meminfo.origin = FROM_CALLOC; + + return libtest_alloc(&meminfo); +} + + +void * +(realloc)(void *old_ptr, size_t new_n) +{ + libtest_realloc_is_custom = 1; + assert(libtest_have_custom_free()); + + return common_realloc(old_ptr, new_n, FROM_REALLOC); +} + + +void * +(reallocarray)(void *old_ptr, size_t new_n, size_t new_m) +{ + libtest_reallocarray_is_custom = 1; + assert(libtest_have_custom_free()); + + assert(new_n); + assert(new_m); + + if (new_n > SIZE_MAX / new_m) { + errno = ENOMEM; + return NULL; + } + + return common_realloc(old_ptr, new_n * new_m, FROM_REALLOCARRAY); +} + + +void * +(valloc)(size_t size) +{ + struct meminfo meminfo; + + libtest_valloc_is_custom = 1; + assert(libtest_have_custom_free()); + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = size; + meminfo.alignment_type = PAGE_ALIGNMENT; + meminfo.initialised_on_alloc = 0; + meminfo.origin = FROM_VALLOC; + + return libtest_alloc(&meminfo); +} + + +void * +(pvalloc)(size_t size) +{ + struct meminfo meminfo; + void *ptr; + size_t pagesize; + + libtest_pvalloc_is_custom = 1; + assert(libtest_have_custom_free()); + + pagesize = libtest_get_pagesize(); + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = size + (pagesize - size % pagesize) % pagesize; + meminfo.alignment_type = PAGE_ALIGNMENT; + meminfo.initialised_on_alloc = 0; + meminfo.origin = FROM_PVALLOC; + + ptr = libtest_alloc(&meminfo); + if (ptr) + GET_MEMINFO(ptr)->requested_alloc_size = size; + return ptr; +} + + +void * +(memalign)(size_t alignment, size_t size) +{ + libtest_memalign_is_custom = 1; + assert(libtest_have_custom_free()); + + return common_memalign(alignment, size, FROM_MEMALIGN); +} + + +void * +(aligned_alloc)(size_t alignment, size_t size) +{ + libtest_aligned_alloc_is_custom = 1; + assert(libtest_have_custom_free()); + + assert(alignment); + assert(!(size % alignment)); + + return common_memalign(alignment, size, FROM_ALIGNED_ALLOC); +} + + +int +(posix_memalign)(void **memptr, size_t alignment, size_t size) +{ + struct meminfo meminfo; + int err, saved_errno = errno; + void *ptr; + + libtest_posix_memalign_is_custom = 1; + assert(libtest_have_custom_free()); + + assert(size); + assert(alignment); + assert(!(alignment & (alignment - 1u))); + assert(!(alignment & (sizeof(void *) - 1u))); + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = size; + meminfo.alignment_type = CUSTOM_ALIGNMENT; + meminfo.requested_alignment = alignment; + meminfo.initialised_on_alloc = 0; + meminfo.origin = FROM_POSIX_MEMALIGN; + + ptr = libtest_alloc(&meminfo); + err = ptr ? 0 : errno; + if (!err) + *memptr = ptr; + + errno = saved_errno; + return err; +} + + +PURE +size_t +(malloc_usable_size)(void *ptr) +{ + libtest_malloc_usable_size_is_custom = 1; + assert(libtest_have_custom_malloc()); + + return ptr ? GET_MEMINFO(ptr)->usable_alloc_size : 0u; +} + + +void +(free)(void *ptr) +{ + libtest_free_is_custom = 1; + + if (ptr) { + struct meminfo *meminfo; + meminfo = GET_MEMINFO(ptr); + if (!meminfo->accept_leakage) { + assert(meminfo->origin != FROM_VALLOC); + assert(meminfo->origin != FROM_PVALLOC); + } + } + + libtest_free(ptr, REQUIRE_ZEROED); +} + + +void +(free_sized)(void *ptr, size_t size) +{ + libtest_free_sized_is_custom = 1; + + if (ptr) { + struct meminfo *meminfo; + meminfo = GET_MEMINFO(ptr); + assert(meminfo->alignment_type == DEFAULT_ALIGNMENT); + assert(meminfo->requested_alloc_size == size); + } + + libtest_free(ptr, REQUIRE_ZEROED); +} + + +void +(free_aligned_sized)(void *ptr, size_t alignment, size_t size) +{ + libtest_free_aligned_sized_is_custom = 1; + + if (ptr) { + struct meminfo *meminfo; + meminfo = GET_MEMINFO(ptr); + assert(meminfo->alignment_type == CUSTOM_ALIGNMENT); + assert(meminfo->requested_alloc_size == size); + assert(meminfo->requested_alignment == alignment); + } + + libtest_free(ptr, REQUIRE_ZEROED); +} + + +#else + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wredundant-decls" +#endif + + +void *(malloc)(size_t n); +void *(calloc)(size_t n, size_t m); +void *(realloc)(void *old_ptr, size_t new_n); +void *(reallocarray)(void *old_ptr, size_t new_n, size_t new_m); +void *(valloc)(size_t size); +void *(pvalloc)(size_t size); +void *(memalign)(size_t alignment, size_t size); +void *(aligned_alloc)(size_t alignment, size_t size); +int (posix_memalign)(void **memptr, size_t alignment, size_t size); +size_t (malloc_usable_size)(void *ptr); +void (free)(void *ptr); +void (free_sized)(void *ptr, size_t size); +void (free_aligned_sized)(void *ptr, size_t alignment, size_t size); + + +static void +fill_memory(uint8_t *mem, size_t n) +{ + size_t i; + for (i = 0u; i < n; i++) + mem[i] = (uint8_t)i; +} + + +static int +check_memory(uint8_t *mem, size_t filled, size_t zeroed) +{ + size_t i; + uint8_t bad = 0; + for (i = 0u; i < filled; i++) + bad |= (uint8_t)(mem[i] ^ (uint8_t)i); + for (i = filled; i < filled + zeroed; i++) + bad |= mem[i]; + return !bad; +} + + +#define p libtest_p___ +extern void *volatile p; +void *volatile p; + + +static void +check(int use_free) +{ + void *q; + + p = malloc(10u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 10u); + assert(malloc_usable_size(p) >= 10u); + if (use_free) + free(p); + else + free_sized(p, 10u); + + p = calloc(3u, 4u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 12u); + assert(malloc_usable_size(p) >= 12u); + if (use_free) + free(p); + else + free_sized(p, 12u); + + p = realloc(NULL, 5u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 5u); + assert(malloc_usable_size(p) >= 5u); + fill_memory(p, 5u); + + p = realloc(p, 9u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 9u); + assert(malloc_usable_size(p) >= 9u); + assert(check_memory(p, 5u, 4u)); + + p = realloc(p, 3u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 3u); + assert(malloc_usable_size(p) >= 3u); + assert(check_memory(p, 3u, 0u)); + if (use_free) + free(p); + else + free_sized(p, 3u); + + p = reallocarray(NULL, 3u, 10u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 30u); + assert(malloc_usable_size(p) >= 30u); + fill_memory(p, 30u); + + p = reallocarray(p, 10u, 10u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 100u); + assert(malloc_usable_size(p) >= 100u); + assert(check_memory(p, 30u, 70u)); + + p = reallocarray(p, 5u, 10u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 50u); + assert(malloc_usable_size(p) >= 50u); + assert(check_memory(p, 30u, 20u)); + if (use_free) + free(p); + else + free_sized(p, 50u); + + p = memalign(2u, 7u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == 2u); + assert((uintptr_t)p % 2u == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 7u); + assert(malloc_usable_size(p) >= 7u); + if (use_free) + free(p); + else + free_aligned_sized(p, 2u, 7u); + + p = aligned_alloc(2u, 4u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == 2u); + assert((uintptr_t)p % 2u == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 4u); + assert(malloc_usable_size(p) >= 4u); + if (use_free) + free(p); + else + free_aligned_sized(p, 2u, 4u); + + assert(!posix_memalign(&q, sizeof(void *), 11u)); + p = q; + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == sizeof(void *)); + assert((uintptr_t)p % 2u == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 11u); + assert(malloc_usable_size(p) >= 11u); + if (use_free) + free(p); + else + free_aligned_sized(p, sizeof(void *), 11u); +} + + +int +main(void) +{ + size_t pagesize; + + SET_UP_ALARM(); + + libtest_start_tracking(); + + check(1); + check(0); + + pagesize = libtest_get_pagesize(); + + libtest_stop_tracking(); + + p = valloc(6u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == pagesize); + assert((uintptr_t)p % (uintptr_t)pagesize == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 6u); + assert(malloc_usable_size(p) >= 6u); + /* cannot be free(3)ed */ + + p = pvalloc(8u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == pagesize); + assert((uintptr_t)p % (uintptr_t)pagesize == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 8u); + assert(malloc_usable_size(p) >= pagesize); + /* cannot be free(3)ed */ + + assert(libtest_check_no_leaks()); + return 0; + + /* TODO test failures */ +} + + +#endif diff --git a/libtest/alloc_have_custom.c b/libtest/alloc_have_custom.c new file mode 100644 index 0000000..ae67be0 --- /dev/null +++ b/libtest/alloc_have_custom.c @@ -0,0 +1,236 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wredundant-decls" +#endif + +void *(malloc)(size_t n); +void *(calloc)(size_t n, size_t m); +void *(realloc)(void *old_ptr, size_t new_n); +void *(reallocarray)(void *old_ptr, size_t new_n, size_t new_m); +void *(valloc)(size_t size); +void *(pvalloc)(size_t size); +void *(memalign)(size_t alignment, size_t size); +void *(aligned_alloc)(size_t alignment, size_t size); +int (posix_memalign)(void **memptr, size_t alignment, size_t size); +PURE size_t (malloc_usable_size)(void *ptr); +void (free)(void *ptr); +void (free_sized)(void *ptr, size_t size); +void (free_aligned_sized)(void *ptr, size_t alignment, size_t size); + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + +#define CHECK_CUSTOM_ALLOC(WHAT, ...)\ + do {\ + int r = libtest_##WHAT##_is_custom;\ + if (r < 0) {\ + void *p = (WHAT)(__VA_ARGS__);\ + free(p);\ + r = libtest_##WHAT##_is_custom;\ + if (r < 0)\ + libtest_##WHAT##_is_custom = r = 0;\ + }\ + assert(r == 0 || r == 1);\ + return r;\ + } while (0) + + +static void * +malloc_with_realloc(size_t n) +{ + void *volatile ptr = malloc(1u); + assert(ptr); + ptr = realloc(ptr, n); + assert(ptr); + return ptr; +} + + +static void * +malloc_with_reallocarray(size_t n, size_t m) +{ + void *volatile ptr = malloc(1u); + assert(ptr); + ptr = reallocarray(ptr, n, m); + assert(ptr); + return ptr; +} + + +static void * +freeable_valloc(size_t n) +{ + void *ptr = valloc(n); + if (ptr) { + GET_MEMINFO(ptr)->origin = FROM_ALIGNED_ALLOC; + GET_MEMINFO(ptr)->alignment_type = CUSTOM_ALIGNMENT; + } + return ptr; +} + + +static void * +freeable_pvalloc(size_t n) +{ + void *ptr = pvalloc(n); + if (ptr) { + GET_MEMINFO(ptr)->origin = FROM_ALIGNED_ALLOC; + GET_MEMINFO(ptr)->alignment_type = CUSTOM_ALIGNMENT; + } + return ptr; +} + + +#define libtest_malloc_with_realloc_is_custom libtest_realloc_is_custom +#define libtest_malloc_with_reallocarray_is_custom libtest_reallocarray_is_custom +#define libtest_freeable_valloc_is_custom libtest_valloc_is_custom +#define libtest_freeable_pvalloc_is_custom libtest_pvalloc_is_custom +int libtest_have_custom_malloc(void) { CHECK_CUSTOM_ALLOC(malloc, 1u); } +int libtest_have_custom_calloc(void) { CHECK_CUSTOM_ALLOC(calloc, 1u, 1u); } +int libtest_have_custom_realloc(void) { CHECK_CUSTOM_ALLOC(malloc_with_realloc, 1u); } +int libtest_have_custom_reallocarray(void) { CHECK_CUSTOM_ALLOC(malloc_with_reallocarray, 1u, 1u); } +int libtest_have_custom_valloc(void) { CHECK_CUSTOM_ALLOC(freeable_valloc, 1u); } +int libtest_have_custom_pvalloc(void) { CHECK_CUSTOM_ALLOC(freeable_pvalloc, 1u); } +int libtest_have_custom_memalign(void) { CHECK_CUSTOM_ALLOC(memalign, 1u, 1u); } +int libtest_have_custom_aligned_alloc(void) { CHECK_CUSTOM_ALLOC(aligned_alloc, 1u, 1u); } + + +int +libtest_have_custom_posix_memalign(void) +{ + int r = libtest_posix_memalign_is_custom; + if (r < 0) { + void *p = NULL; + r = (posix_memalign)(&p, sizeof(p), 1u); + if (r) + free(p); + r = libtest_posix_memalign_is_custom; + if (r < 0) + libtest_posix_memalign_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +static volatile size_t libtest_have_custom_malloc_usable_size_discard; + +int +libtest_have_custom_malloc_usable_size(void) +{ + int r = libtest_malloc_usable_size_is_custom; + if (r < 0) { + void *p = (malloc)(1u); + assert(p); + libtest_have_custom_malloc_usable_size_discard = malloc_usable_size(p); + (free)(p); + r = libtest_malloc_usable_size_is_custom; + if (r < 0) + libtest_malloc_usable_size_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +int +libtest_have_custom_free(void) +{ + int r = libtest_free_is_custom; + void *p; + if (r < 0) { + libtest_free_is_custom = 1; + p = (malloc)(1u); + assert(p); + assert(libtest_malloc_is_custom == 1); + (free)(p); + r = libtest_free_is_custom; + if (r < 0) + libtest_free_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +int +libtest_have_custom_free_sized(void) +{ + int r = libtest_free_sized_is_custom; + void *p; + if (r < 0) { + libtest_free_sized_is_custom = 1; + p = (malloc)(1u); + assert(p); + assert(libtest_malloc_is_custom == 1); + (free_sized)(p, 1u); + r = libtest_free_sized_is_custom; + if (r < 0) + libtest_free_sized_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +int +libtest_have_custom_free_aligned_sized(void) +{ + int r = libtest_free_aligned_sized_is_custom; + void *p; + if (r < 0) { + libtest_free_aligned_sized_is_custom = 1; + p = (aligned_alloc)(1u, 1u); + assert(p); + assert(libtest_aligned_alloc_is_custom == 1); + (free_aligned_sized)(p, 1u, 1u); + r = libtest_free_aligned_sized_is_custom; + if (r < 0) + libtest_free_aligned_sized_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +#else + + +#define CHECK(WHAT)\ + do {\ + fprintf(stderr, "testing %s\n", #WHAT);\ + EXPECT(WHAT() == 1);\ + } while (0) + + +int +main(void) +{ + SET_UP_ALARM(); + + CHECK(libtest_have_custom_malloc); + CHECK(libtest_have_custom_calloc); + CHECK(libtest_have_custom_realloc); + CHECK(libtest_have_custom_reallocarray); + CHECK(libtest_have_custom_valloc); + CHECK(libtest_have_custom_pvalloc); + CHECK(libtest_have_custom_memalign); + CHECK(libtest_have_custom_aligned_alloc); + CHECK(libtest_have_custom_posix_memalign); + CHECK(libtest_have_custom_malloc_usable_size); + CHECK(libtest_have_custom_free); + CHECK(libtest_have_custom_free_sized); + CHECK(libtest_have_custom_free_aligned_sized); + + return 0; +} + + +#endif diff --git a/libtest/common.h b/libtest/common.h new file mode 100644 index 0000000..070b217 --- /dev/null +++ b/libtest/common.h @@ -0,0 +1,242 @@ +/* See LICENSE file for copyright and license details. */ +#ifdef WITH_BACKTRACE +# define UNW_LOCAL_ONLY +# include <libunwind.h> +# include <elfutils/libdwfl.h> +#endif +#include <sys/mman.h> +#include <errno.h> +#include <inttypes.h> +#include <stdatomic.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libtest.h" + + +#if __STDC_VERSION__ >= 202311L +# define _Alignof alignof +# define _Alignas alignas +# define _Thread_local thread_local +#endif + + +#if defined(__GNUC__) +# define HIDDEN __attribute__((__visibility__("hidden"))) +# define CONST __attribute__((__const__)) +# define PURE __attribute__((__pure__)) +#else +# define HIDDEN +# define CONST +# define PURE +#endif + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wpadded" +#endif + + +#ifndef MAP_UNINITIALIZED +# define MAP_UNINITIALIZED 0 +#endif + + +#define ELEMSOF(A) (sizeof(A) / sizeof(*(A))) + + +#define BASE_POINTER(PTR) (*libtest_base_pointer((PTR))) +#define GET_MEMINFO(PTR) ((struct meminfo *)BASE_POINTER(PTR)) +#define SPINLOCK(LOCK) do {} while (atomic_flag_test_and_set_explicit(&(LOCK), memory_order_acquire)) +#define SPINUNLOCK(LOCK) atomic_flag_clear_explicit(&(LOCK), memory_order_release) + + +enum libtest_zero_check { + DO_NOT_REQUIRE_ZEROED, + REQUIRE_ZEROED +}; + +enum align_type { + DEFAULT_ALIGNMENT, + PAGE_ALIGNMENT, + CUSTOM_ALIGNMENT +}; + +enum memory_origin { + FROM_MALLOC, + FROM_CALLOC, + FROM_REALLOC, + FROM_REALLOCARRAY, + FROM_VALLOC, + FROM_PVALLOC, + FROM_MEMALIGN, + FROM_ALIGNED_ALLOC, + FROM_POSIX_MEMALIGN, + FROM_MMAP_FILE, + FROM_MMAP_ANON +}; + +#ifdef WITH_BACKTRACE +struct backtrace { + size_t n; + Dwarf_Addr trace[]; +}; +#endif + +struct meminfo { +#ifdef WITH_BACKTRACE + struct backtrace *backtrace; +#endif + struct meminfo *next, *prev; + void *usable_area; + size_t real_alloc_size; + size_t usable_alloc_size; + size_t requested_alloc_size; + size_t requested_alignment; + size_t actual_alignment; + enum align_type alignment_type; + int initialised_on_alloc; + enum memory_origin origin; + int accept_leakage; /* implies DO_NOT_REQUIRE_ZEROED */ +}; + + +extern volatile int libtest_malloc_is_custom; +extern volatile int libtest_calloc_is_custom; +extern volatile int libtest_realloc_is_custom; +extern volatile int libtest_reallocarray_is_custom; +extern volatile int libtest_valloc_is_custom; +extern volatile int libtest_pvalloc_is_custom; +extern volatile int libtest_memalign_is_custom; +extern volatile int libtest_aligned_alloc_is_custom; +extern volatile int libtest_posix_memalign_is_custom; +extern volatile int libtest_malloc_usable_size_is_custom; +extern volatile int libtest_free_is_custom; +extern volatile int libtest_free_sized_is_custom; +extern volatile int libtest_free_aligned_sized_is_custom; + +extern struct meminfo libtest_allocs_head; +extern struct meminfo libtest_allocs_tail; +extern int libtest_allocs_list_inited; +extern atomic_flag libtest_allocs_list_spinlock; + +extern int libtest_zero_on_alloc; +extern int libtest_expect_zeroed; +extern int libtest_malloc_accept_leakage; + +extern _Thread_local size_t libtest_malloc_internal_usage; +extern _Thread_local size_t libtest_kill_malloc_tracking; + + +HIDDEN inline void ** +libtest_base_pointer(void *ptr) +{ + uintptr_t addr = (uintptr_t)ptr - (uintptr_t)sizeof(void *); + addr -= addr % _Alignof(void *); + return (void **)addr; +} + + +HIDDEN size_t libtest_get_pagesize(void); +HIDDEN void *libtest_alloc(struct meminfo *); +HIDDEN void libtest_free(void *, enum libtest_zero_check); + +#ifdef WITH_BACKTRACE +HIDDEN void libtest_print_backtrace(FILE *, const char *indent, size_t first, const struct backtrace *); +#else +# define libtest_print_backtrace(FP, INDENT, FIRST, BACKTRACE) ((void)0) +#endif + + +void *__mmap(void *, size_t, int, int, int, off_t); +int __munmap(void *, size_t); +void *__mremap(void *, size_t, size_t, int, ...); +#define libtest_real_mmap __mmap +#define libtest_real_munmap __munmap +#define libtest_real_mremap __mremap + + +#define assert(EXPR)\ + do {\ + if (!(EXPR)) {\ + libtest_malloc_internal_usage++;\ + fprintf(stderr, "Assetion failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\ + libtest_print_backtrace(stderr, "\t", 0u, NULL);\ + exit(2);\ + }\ + } while (0) + + +#ifdef TEST +# ifdef __linux__ +# include <sys/prctl.h> +# endif +# include <sys/resource.h> +# include <sys/types.h> +# include <sys/wait.h> +# include <signal.h> +# include <string.h> +# include <unistd.h> + +# define SET_UP_ALARM()\ + do {\ + unsigned int alarm_time__ = alarm(10u);\ + if (alarm_time__ > 10u)\ + alarm(alarm_time__);\ + } while (0) + +# if defined(PR_SET_DUMPABLE) +# define INIT_TEST_ABORT()\ + do {\ + struct rlimit rl__;\ + rl__.rlim_cur = 0;\ + rl__.rlim_max = 0;\ + (void) setrlimit(RLIMIT_CORE, &rl__);\ + (void) prctl(PR_SET_DUMPABLE, 0);\ + EXPECT_ABORT(abort());\ + } while (0) +# else +# define INIT_TEST_ABORT()\ + do {\ + struct rlimit rl__;\ + rl__.rlim_cur = 0;\ + rl__.rlim_max = 0;\ + (void) setrlimit(RLIMIT_CORE, &rl__);\ + EXPECT_ABORT(abort());\ + } while (0) +# endif + +# define EXPECT__(EXPR, HOW, RETEXTRACT, RETEXPECT)\ + do {\ + pid_t pid__;\ + int status__;\ + pid__ = fork();\ + EXPECT(pid__ != -1);\ + if (pid__ == 0) {\ + (EXPR);\ + _exit(0);\ + }\ + EXPECT(waitpid(pid__, &status__, 0) == pid__);\ + EXPECT(HOW(status__));\ + EXPECT(RETEXTRACT(status__) == RETEXPECT);\ + } while (0) + +# define EXPECT_ABORT(EXPR)\ + do {\ + EXPECT__(EXPR, WIFSIGNALED, WTERMSIG, SIGABRT);\ + } while (0) + +# define EXPECT(EXPR)\ + do {\ + if (!(EXPR)) {\ + libtest_malloc_internal_usage++;\ + fprintf(stderr, "Failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\ + libtest_print_backtrace(stderr, "\t", 0u, NULL);\ + exit(1);\ + }\ + } while (0) +#endif diff --git a/libtest/config.mk b/libtest/config.mk new file mode 100644 index 0000000..b2519f0 --- /dev/null +++ b/libtest/config.mk @@ -0,0 +1,4 @@ +WITH_BACKTRACE = true + +TEST_CONFIGFILE = config_backtraces=$(WITH_BACKTRACE).mk +include $(TEST_INCLUDE_PREFIX)$(TEST_CONFIGFILE) diff --git a/libtest/config_backtraces=false.mk b/libtest/config_backtraces=false.mk new file mode 100644 index 0000000..f508390 --- /dev/null +++ b/libtest/config_backtraces=false.mk @@ -0,0 +1,10 @@ +C17 !=\ + if command -v c17 >/dev/null || ! command -v cc >/dev/null; then\ + echo c17;\ + else\ + echo cc -std=c17;\ + fi + +TEST_CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +TEST_CFLAGS = +TEST_LDFLAGS = diff --git a/libtest/config_backtraces=true.mk b/libtest/config_backtraces=true.mk new file mode 100644 index 0000000..8233aaa --- /dev/null +++ b/libtest/config_backtraces=true.mk @@ -0,0 +1,12 @@ +C17 !=\ + if command -v c17 >/dev/null || ! command -v cc >/dev/null; then\ + echo c17;\ + else\ + echo cc -std=c17;\ + fi + +TEST_CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -DWITH_BACKTRACE +TEST_CFLAGS = -g +TEST_LDFLAGS = -lunwind -ldw + +OBJ_BACKTRACE = libtest_print_backtrace.o diff --git a/libtest/globals.c b/libtest/globals.c new file mode 100644 index 0000000..9e9ec2f --- /dev/null +++ b/libtest/globals.c @@ -0,0 +1,44 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +volatile int libtest_malloc_is_custom = -1; +volatile int libtest_calloc_is_custom = -1; +volatile int libtest_realloc_is_custom = -1; +volatile int libtest_reallocarray_is_custom = -1; +volatile int libtest_valloc_is_custom = -1; +volatile int libtest_pvalloc_is_custom = -1; +volatile int libtest_memalign_is_custom = -1; +volatile int libtest_aligned_alloc_is_custom = -1; +volatile int libtest_posix_memalign_is_custom = -1; +volatile int libtest_malloc_usable_size_is_custom = -1; +volatile int libtest_free_is_custom = -1; +volatile int libtest_free_sized_is_custom = -1; +volatile int libtest_free_aligned_sized_is_custom = -1; + +struct meminfo libtest_allocs_head; +struct meminfo libtest_allocs_tail; +int libtest_allocs_list_inited = 0; +atomic_flag libtest_allocs_list_spinlock = ATOMIC_FLAG_INIT; + +int libtest_zero_on_alloc = 0; +int libtest_expect_zeroed = 0; +int libtest_malloc_accept_leakage = 1; + +_Thread_local size_t libtest_malloc_internal_usage = 0u; +_Thread_local size_t libtest_kill_malloc_tracking = 0u; + + +#else + + +CONST int +main(void) +{ + /* There isn't really anything to test here */ + return 0; +} + + +#endif diff --git a/libtest/libtest.h b/libtest/libtest.h new file mode 100644 index 0000000..3a3d963 --- /dev/null +++ b/libtest/libtest.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ + +void libtest_start_tracking(void); +void libtest_stop_tracking(void); +int libtest_check_no_leaks(void); +void libtest_force_zero_on_alloc(int); +void libtest_expect_zeroed_on_free(int); + +int libtest_have_custom_malloc(void); +int libtest_have_custom_calloc(void); +int libtest_have_custom_realloc(void); +int libtest_have_custom_reallocarray(void); +int libtest_have_custom_valloc(void); +int libtest_have_custom_pvalloc(void); +int libtest_have_custom_memalign(void); +int libtest_have_custom_aligned_alloc(void); +int libtest_have_custom_posix_memalign(void); +int libtest_have_custom_malloc_usable_size(void); +int libtest_have_custom_free(void); +int libtest_have_custom_free_sized(void); +int libtest_have_custom_free_aligned_sized(void); + +void libtest_dump_stack(const char *indent); diff --git a/libtest/libtest_alloc.c b/libtest/libtest_alloc.c new file mode 100644 index 0000000..5aa3218 --- /dev/null +++ b/libtest/libtest_alloc.c @@ -0,0 +1,208 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(WITH_BACKTRACE) && defined(__GNUC__) +# pragma GCC diagnostic ignored "-Walloca" +#endif + + +static _Thread_local int inside_mmap_anon = 0; + + +static void +meminfo_fixup(struct meminfo *meminfo) +{ + if (meminfo->alignment_type == DEFAULT_ALIGNMENT) + meminfo->requested_alignment = _Alignof(max_align_t); + else if (meminfo->alignment_type == PAGE_ALIGNMENT) + meminfo->requested_alignment = libtest_get_pagesize(); + + meminfo->actual_alignment = meminfo->requested_alignment; + meminfo->accept_leakage = !!libtest_malloc_accept_leakage; + + if (libtest_zero_on_alloc) + meminfo->initialised_on_alloc |= !meminfo->accept_leakage; +} + + +static int +add_or_enomem(size_t *augend, size_t augment) +{ + if (*augend > SIZE_MAX - augment) { + errno = ENOMEM; + return -1; + } + *augend += augment; + return 0; +} + + +static void * +mmap_anon(size_t size) +{ + void *ptr; + inside_mmap_anon = 1; + ptr = libtest_real_mmap(NULL, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, + -1, 0); + inside_mmap_anon = 0; + return ptr == MAP_FAILED ? NULL : ptr; +} + + +void * +libtest_alloc(struct meminfo *meminfo) +{ + static _Thread_local int recursion_guard = 0; + void *base_ptr, *ret_ptr; + size_t misalignment; + size_t bookkeeping; + int saved_errno; +#ifdef WITH_BACKTRACE + size_t backtrace_realignment; + size_t i, backtrace_n; + unw_cursor_t cursor; + unw_context_t context; + unw_word_t rip; + struct partial { + uintptr_t rips[8]; + struct partial *next; + } *current, head; +#endif + + assert(!inside_mmap_anon); + + saved_errno = errno; + meminfo_fixup(meminfo); +#ifdef WITH_BACKTRACE + meminfo->accept_leakage |= libtest_malloc_internal_usage > 0; +#endif + + /* Get backtrace (have to do it now to calculate `backtrace_n` for allocation) */ +#ifdef WITH_BACKTRACE + backtrace_n = 0u; + if (!libtest_malloc_accept_leakage && !libtest_malloc_internal_usage) { + libtest_malloc_internal_usage++; + if (unw_getcontext(&context) || unw_init_local(&cursor, &context)) + goto skip_backtrace; + for (current = &head, i = 0u; unw_step(&cursor) > 0; i++) { + if (i % ELEMSOF(current->rips) == 0u) + current = current->next = alloca(sizeof(*current)); + if (unw_get_reg(&cursor, UNW_REG_IP, &rip)) + goto skip_backtrace; + current->rips[i % ELEMSOF(current->rips)] = (uintptr_t)rip; + } + backtrace_n = i; + skip_backtrace: + libtest_malloc_internal_usage--; + } +#endif + + /* Calculate required alloction size */ + meminfo->real_alloc_size = meminfo->requested_alloc_size; + if (add_or_enomem(&meminfo->real_alloc_size, sizeof(struct meminfo)) || + add_or_enomem(&meminfo->real_alloc_size, _Alignof(void *) + sizeof(void *)) || + add_or_enomem(&meminfo->real_alloc_size, meminfo->actual_alignment)) + return NULL; +#ifdef WITH_BACKTRACE + if (backtrace_n) { + misalignment = sizeof(struct meminfo) % _Alignof(struct backtrace); + backtrace_realignment = misalignment ? _Alignof(struct backtrace) - misalignment : 0u; + if (add_or_enomem(&meminfo->real_alloc_size, backtrace_realignment) || + add_or_enomem(&meminfo->real_alloc_size, offsetof(struct backtrace, trace)) || + add_or_enomem(&meminfo->real_alloc_size, backtrace_n * sizeof(Dwarf_Addr))) + return NULL; + } +#endif + bookkeeping = meminfo->real_alloc_size - meminfo->requested_alloc_size; + bookkeeping -= meminfo->actual_alignment; + + /* Allocate memory */ + base_ptr = mmap_anon(meminfo->real_alloc_size); + if (!base_ptr) + return NULL; + if (meminfo->initialised_on_alloc) + memset(base_ptr, 0, meminfo->real_alloc_size); + ret_ptr = &((char *)base_ptr)[bookkeeping]; + + /* Save backtrace */ +#ifdef WITH_BACKTRACE + if (backtrace_n) { + meminfo->backtrace = (void *)&((char *)base_ptr)[sizeof(struct meminfo) + backtrace_realignment]; + meminfo->backtrace->n = backtrace_n; + for (current = &head, i = 0u; i < backtrace_n; i++) { + if (i % ELEMSOF(current->rips) == 0u) + current = current->next; + meminfo->backtrace->trace[i] = current->rips[i % ELEMSOF(current->rips)]; + } + } else { + meminfo->backtrace = NULL; + } +#endif + + /* Fix alignment */ + misalignment = (size_t)(uintptr_t)ret_ptr % meminfo->actual_alignment; + if (misalignment) + ret_ptr = &((char *)ret_ptr)[meminfo->actual_alignment - misalignment]; + + /* Store usable size */ + meminfo->usable_alloc_size = meminfo->real_alloc_size; + meminfo->usable_alloc_size -= (size_t)((char *)ret_ptr - (char *)base_ptr); + + /* Store book-keeping */ + meminfo->usable_area = ret_ptr; + memcpy(base_ptr, meminfo, sizeof(*meminfo)); + BASE_POINTER(ret_ptr) = base_ptr; + meminfo = base_ptr; + + /* Track allocation */ + if (!libtest_kill_malloc_tracking) { + assert(!recursion_guard); + recursion_guard = 1; + SPINLOCK(libtest_allocs_list_spinlock); + if (!libtest_allocs_list_inited) { + libtest_allocs_head.prev = NULL; + libtest_allocs_head.next = &libtest_allocs_tail; + libtest_allocs_tail.prev = &libtest_allocs_head; + libtest_allocs_tail.next = NULL; + libtest_allocs_list_inited = 1; + } + libtest_allocs_tail.prev->next = meminfo; + meminfo->prev = libtest_allocs_tail.prev; + meminfo->next = &libtest_allocs_tail; + SPINUNLOCK(libtest_allocs_list_spinlock); + recursion_guard = 0; + } + + /* Optionally print out trace */ +#ifdef WITH_BACKTRACE + if (meminfo->backtrace && !libtest_malloc_internal_usage && getenv("TRACE_MALLOC")) { + libtest_malloc_internal_usage++; + fprintf(stderr, "Memory allocated: %p (alloc-size=%zu, real-size=%zu, leak-allowed=%i)\n", + ret_ptr, meminfo->requested_alloc_size, meminfo->real_alloc_size, meminfo->accept_leakage); + libtest_print_backtrace(stderr, "\tat ", 0u, NULL); + fflush(stderr); + libtest_malloc_internal_usage--; + } +#endif + + errno = saved_errno; + return ret_ptr; +} + + +#else + + +CONST int +main(void) +{ + /* Tested via alloc.c */ + return 0; +} + + +#endif diff --git a/libtest/libtest_base_pointer.c b/libtest/libtest_base_pointer.c new file mode 100644 index 0000000..4902643 --- /dev/null +++ b/libtest/libtest_base_pointer.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline void **libtest_base_pointer(void *); + + +#else + + +CONST int +main(void) +{ + /* This one isn't that simple to test, but it works if other tests work :) */ + return 0; +} + + +#endif diff --git a/libtest/libtest_check_no_leaks.c b/libtest/libtest_check_no_leaks.c new file mode 100644 index 0000000..15f7e58 --- /dev/null +++ b/libtest/libtest_check_no_leaks.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +extern int libtest_suppress_leak_output; +#ifndef TEST + + +int libtest_suppress_leak_output = 0; + + +static int +check_no_memory_leaks(void) +{ + struct meminfo *mem; + int no_leaks = 1; + + SPINLOCK(libtest_allocs_list_spinlock); + + if (!libtest_allocs_list_inited) + goto out; + + libtest_kill_malloc_tracking++; + libtest_malloc_internal_usage++; + for (mem = libtest_allocs_head.next; mem->next; mem = mem->next) { + if (mem->accept_leakage) + continue; + no_leaks = 0; + if (libtest_suppress_leak_output) + continue; + fprintf(stderr, "Memory leak: %p (alloc-size=%zu)\n", + mem->usable_area, mem->requested_alloc_size); +#ifdef WITH_BACKTRACE + if (mem->backtrace) + libtest_print_backtrace(stderr, "\tat ", 0u, mem->backtrace); +#endif + fflush(stderr); + } + libtest_malloc_internal_usage--; + libtest_kill_malloc_tracking--; + +out: + SPINUNLOCK(libtest_allocs_list_spinlock); + return no_leaks; +} + + +int +libtest_check_no_leaks(void) +{ + /* TODO check file descriptor leaks */ + return check_no_memory_leaks(); +} + + +#else + + +int +main(void) +{ + void *p; + + SET_UP_ALARM(); + + libtest_start_tracking(); + assert(p = malloc(1u)); + libtest_suppress_leak_output = 1; + EXPECT(!libtest_check_no_leaks()); + libtest_suppress_leak_output = 0; + free(p); + libtest_stop_tracking(); + EXPECT(libtest_check_no_leaks()); + + return 0; +} + + +#endif diff --git a/libtest/libtest_dump_stack.c b/libtest/libtest_dump_stack.c new file mode 100644 index 0000000..ca0c9c7 --- /dev/null +++ b/libtest/libtest_dump_stack.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#ifndef WITH_BACKTRACE +# if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wattributes" +# endif +CONST +#endif +void +libtest_dump_stack(const char *indent) +{ +#ifndef WITH_BACKTRACE + (void) indent; +#else + libtest_print_backtrace(stderr, indent, 1u, NULL); +#endif +} + + +#else + + +CONST int +main(void) +{ + /* How would one even test this, and what would be the point? */ + return 0; +} + + +#endif diff --git a/libtest/libtest_expect_zeroed_on_free.c b/libtest/libtest_expect_zeroed_on_free.c new file mode 100644 index 0000000..959ad07 --- /dev/null +++ b/libtest/libtest_expect_zeroed_on_free.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_expect_zeroed_on_free(int v) +{ + libtest_expect_zeroed = v; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + EXPECT(libtest_expect_zeroed == 0); + libtest_expect_zeroed_on_free(1); + EXPECT(libtest_expect_zeroed == 1); + libtest_expect_zeroed_on_free(0); + EXPECT(libtest_expect_zeroed == 0); + + return 0; +} + + +#endif diff --git a/libtest/libtest_force_zero_on_alloc.c b/libtest/libtest_force_zero_on_alloc.c new file mode 100644 index 0000000..907eabc --- /dev/null +++ b/libtest/libtest_force_zero_on_alloc.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_force_zero_on_alloc(int v) +{ + libtest_zero_on_alloc = v; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + EXPECT(libtest_zero_on_alloc == 0); + libtest_force_zero_on_alloc(1); + EXPECT(libtest_zero_on_alloc == 1); + libtest_force_zero_on_alloc(0); + EXPECT(libtest_zero_on_alloc == 0); + + return 0; +} + + +#endif diff --git a/libtest/libtest_free.c b/libtest/libtest_free.c new file mode 100644 index 0000000..398994b --- /dev/null +++ b/libtest/libtest_free.c @@ -0,0 +1,94 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_free(void *ptr, enum libtest_zero_check zero_checking) +{ + struct meminfo *mem; + int saved_errno = errno; + int unmap_err, memory_zeroed; + uint8_t *usable_area; + size_t i; +#ifdef WITH_BACKTRACE + static _Thread_local int inside_free = 0; +#endif + + /* free(3) can be called with NULL */ + if (!ptr) + return; + + /* Optionally print out trace */ +#ifdef WITH_BACKTRACE + if (!inside_free && getenv("PRETRACE_FREE")) { + inside_free = 1; + fprintf(stderr, "Deallocating: %p\n", ptr); + libtest_print_backtrace(stderr, "\tat ", 0u, NULL); + fflush(stderr); + inside_free = 0; + } +#endif + + /* Get the start of the allocation, which + * is where the book-keeping is located */ + mem = GET_MEMINFO(ptr); + + /* It is illegal to use free(3) on memory allocated with mmap(3)/mmap(2) */ + assert(mem->origin != FROM_MMAP_FILE); + assert(mem->origin != FROM_MMAP_ANON); + + /* Delist allocation */ + if (!libtest_kill_malloc_tracking) { + SPINLOCK(libtest_allocs_list_spinlock); + mem->prev->next = mem->next; + mem->next->prev = mem->prev; + SPINUNLOCK(libtest_allocs_list_spinlock); + } + + /* Check memory is zeroed */ + if (zero_checking && libtest_expect_zeroed && !mem->accept_leakage) { + usable_area = mem->usable_area; + memory_zeroed = 1; + for (i = 0u; i < mem->usable_alloc_size; i++) { + if (usable_area[i]) { + memory_zeroed = 0; + break; + } + } + assert(memory_zeroed); + } + + /* Optionally print out trace */ +#ifdef WITH_BACKTRACE + if (!inside_free && getenv("TRACE_MALLOC")) { + inside_free = 1; + fprintf(stderr, "Memory deallocated: %p\n (alloc-size=%zu, real-size=%zu)", + ptr, mem->requested_alloc_size, mem->real_alloc_size); + if (getenv("TRACE_FREE") && !getenv("PRETRACE_FREE")) + libtest_print_backtrace(stderr, "\tat ", 0u, NULL); + fflush(stderr); + inside_free = 0; + } +#endif + + /* Deallocate memory */ + unmap_err = libtest_real_munmap(mem, mem->real_alloc_size); + assert(!unmap_err); + + errno = saved_errno; +} + + +#else + + +CONST int +main(void) +{ + /* Tested via alloc.c */ + return 0; +} + + +#endif diff --git a/libtest/libtest_get_pagesize.c b/libtest/libtest_get_pagesize.c new file mode 100644 index 0000000..52adbdc --- /dev/null +++ b/libtest/libtest_get_pagesize.c @@ -0,0 +1,37 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +size_t +libtest_get_pagesize(void) +{ + long int r; + int saved_errno = errno; + if ((r = sysconf(_SC_PAGESIZE)) <= 0) + if ((r = sysconf(_SC_PAGE_SIZE)) <= 0) + r = 4096L; + errno = saved_errno; + return (size_t)r; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + +#ifdef __linux__ + errno = 0; + EXPECT(libtest_get_pagesize() == 4096u); + EXPECT(!errno); +#endif + + return 0; +} + + +#endif diff --git a/libtest/libtest_print_backtrace.c b/libtest/libtest_print_backtrace.c new file mode 100644 index 0000000..8fa0fd5 --- /dev/null +++ b/libtest/libtest_print_backtrace.c @@ -0,0 +1,122 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(__linux__) +# define HAVE_LINE_INFO +#endif + + +void +libtest_print_backtrace(FILE *fp, const char *indent, size_t first, const struct backtrace *backtrace) +{ + static _Thread_local int recursion_guard = 0; + int saved_errno; + unw_word_t rip; + unw_cursor_t cursor; + unw_context_t context; + Dwarf_Addr ip; + size_t i; +#if defined(HAVE_LINE_INFO) + Dwfl_Callbacks callbacks; + char *debuginfo_path = NULL; + Dwfl *dwfl = NULL; + Dwfl_Line *line = NULL; + Dwfl_Module *module = NULL; + int lineno = 0; /* initialised for compiler happiness */ + const char *filename = NULL; + const char *funcname = NULL; +#endif + + if (recursion_guard) + return; + saved_errno = errno; + recursion_guard = 1; + libtest_malloc_internal_usage++; + + if (!backtrace) { + if (unw_getcontext(&context)) + goto out; + if (unw_init_local(&cursor, &context)) + goto out; + } + +#if defined(__linux__) + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.find_elf = &dwfl_linux_proc_find_elf; + callbacks.find_debuginfo = &dwfl_standard_find_debuginfo; + callbacks.section_address = NULL; + callbacks.debuginfo_path = &debuginfo_path; + + dwfl = dwfl_begin(&callbacks); + if (dwfl) { + if (dwfl_linux_proc_report(dwfl, getpid()) || + dwfl_report_end(dwfl, NULL, NULL)) { + dwfl_end(dwfl); + dwfl = NULL; + } + } +#endif + + for (i = 0u; backtrace ? i < backtrace->n : unw_step(&cursor) > 0; i++) { + if (backtrace) { + ip = backtrace->trace[i]; + } else { + if (unw_get_reg(&cursor, UNW_REG_IP, &rip)) + break; + ip = (Dwarf_Addr)rip; + } + if (i < first) + continue; + +#if defined(HAVE_LINE_INFO) + 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); +# ifdef USE_BASENAMES_IN_BACKTRACES + if (strrchr(filename, '/')) + filename = &strrchr(filename, '/')[1]; +# endif + } + } +#endif + +#if defined(HAVE_LINE_INFO) + fprintf(fp, "%s0x%016"PRIxPTR": %s", indent, (uintptr_t)ip, funcname ? funcname : "???"); + if (line) + fprintf(fp, " (%s:%i)\n", filename, lineno); + else + fprintf(fp, "\n"); +#else + fprintf(fp, "%s0x%016"PRIxPTR"\n", indent, (uintptr_t)ip); +#endif + } + +#if defined(HAVE_LINE_INFO) + if (dwfl) + dwfl_end(dwfl); +#endif + +out: + libtest_malloc_internal_usage--; + recursion_guard = 0; + errno = saved_errno; +} + + +#else + + +CONST int +main(void) +{ + /* How would one even test this, and what would be the point? */ + return 0; +} + + +#endif diff --git a/libtest/libtest_start_tracking.c b/libtest/libtest_start_tracking.c new file mode 100644 index 0000000..070fab9 --- /dev/null +++ b/libtest/libtest_start_tracking.c @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_start_tracking(void) +{ + libtest_malloc_accept_leakage = 0; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + EXPECT(libtest_malloc_accept_leakage == 1); + libtest_start_tracking(); + EXPECT(libtest_malloc_accept_leakage == 0); + + return 0; +} + + +#endif diff --git a/libtest/libtest_stop_tracking.c b/libtest/libtest_stop_tracking.c new file mode 100644 index 0000000..29bb2aa --- /dev/null +++ b/libtest/libtest_stop_tracking.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_stop_tracking(void) +{ + libtest_malloc_accept_leakage = 1; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + EXPECT(libtest_malloc_accept_leakage == 1); + + libtest_malloc_accept_leakage = 0; + + EXPECT(libtest_malloc_accept_leakage == 0); + + libtest_stop_tracking(); + EXPECT(libtest_malloc_accept_leakage == 1); + + return 0; +} + + +#endif |
