diff --git a/libc-test/src/functional/BUILD.gn b/libc-test/src/functional/BUILD.gn index 902ea97968d12b052ffe8916f8a18227c83b3431..77bf4678de3adeab64d32c3ac919d7fb04ea5dd9 100644 --- a/libc-test/src/functional/BUILD.gn +++ b/libc-test/src/functional/BUILD.gn @@ -1,6 +1,10 @@ import("../../test_template.gni") import("test_src_functional.gni") +if (is_standard_system) { + functional_list += malloc_stats_list +} + foreach(s, functional_list) { test_unittest(s) { target_dir = "functional" diff --git a/libc-test/src/functional/test-iterate.c b/libc-test/src/functional/test-iterate.c new file mode 100644 index 0000000000000000000000000000000000000000..6ad290f5399166896dd0067a9e67c1fb1cef43e1 --- /dev/null +++ b/libc-test/src/functional/test-iterate.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include +#include +#include + +#include "test-malloc-api-common.h" + +#define ALLOCATIONS_NUMBER 8 +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +typedef struct iterate_arg_s +{ + uintptr_t allocs[ALLOCATIONS_NUMBER]; + size_t allocs_reported_number[ALLOCATIONS_NUMBER]; + size_t allocs_actual_sizes[ALLOCATIONS_NUMBER]; + size_t reported_sizes[ALLOCATIONS_NUMBER]; +} iterate_arg_t; + +typedef struct { + uintptr_t *base; + size_t size; +} allocations_info_t; + +static const size_t allocs_sizes[ALLOCATIONS_NUMBER] = { + 8, + 2048, + 65536, + 524288, + 2*1024*1024, + 8*1024*1024, + 16*1024*1024, + 32*1024*1024 +}; + +void iterate_callback(void* base, size_t size, void* data) { + iterate_arg_t* iterate_arg = (iterate_arg_t*)data; + uintptr_t end; + if (__builtin_add_overflow((uintptr_t)base, size, &end)) { + return; + } + + for (size_t i = 0; i < ALLOCATIONS_NUMBER; ++i) { + if (iterate_arg->allocs[i] >= (uintptr_t)base && iterate_arg->allocs[i] < end) { + iterate_arg->allocs_reported_number[i]++; + uintptr_t max_size = end - iterate_arg->allocs[i]; + iterate_arg->reported_sizes[i] = MIN(size, max_size); + } + } +} + +void fill_allocations_info(const iterate_arg_t *iterate_arg, allocations_info_t *allocations_info) { + size_t min_idx, max_idx; + uintptr_t min_val=UINTPTR_MAX, max_val=0; + + const uintptr_t *allocs = iterate_arg->allocs; + + for (size_t i = 0; i < ALLOCATIONS_NUMBER; ++i) { + if (allocs[i] > max_val) { + max_val = allocs[i]; + max_idx = i; + } + if (allocs[i] < min_val) { + min_val = allocs[i]; + min_idx = i; + } + } + + allocations_info->base = (void *)allocs[min_idx]; + allocations_info->size = allocs[max_idx] - allocs[min_idx] + allocs_sizes[max_idx]; +} + +void make_allocations(iterate_arg_t *iterate_arg) { + uintptr_t *allocs = iterate_arg->allocs; + size_t *allocs_actual_sizes = iterate_arg->allocs_actual_sizes; + + for (size_t i = 0; i < ALLOCATIONS_NUMBER; ++i) { + allocs[i] = (uintptr_t)malloc(allocs_sizes[i]); + allocs_actual_sizes[i] = malloc_usable_size((void*)allocs[i]); + } +} + +void free_allocations(iterate_arg_t *iterate_arg) { + uintptr_t *allocs = iterate_arg->allocs; + + for (size_t i = 0; i < ALLOCATIONS_NUMBER; ++i) { + free((void*)allocs[i]); + } +} + +int iterate_wrapper(iterate_arg_t *iterate_arg) { + int ret = 0; + allocations_info_t allocations_info; + fill_allocations_info(iterate_arg, &allocations_info); + malloc_iterate(allocations_info.base, allocations_info.size, iterate_callback, iterate_arg); + + for (size_t i = 0; i < ALLOCATIONS_NUMBER; ++i) { + if (iterate_arg->allocs_reported_number[i] != 1) { +// printf("Error: failed on %p %lu %lu\n", (void*)iterate_arg->allocs[i], iterate_arg->allocs_actual_sizes[i], iterate_arg->allocs_reported_number[i]); + ret = -1; + } else { +// printf("Ok on %p %lu %lu\n", (void*)iterate_arg->allocs[i], iterate_arg->allocs_actual_sizes[i], iterate_arg->allocs_reported_number[i]); + } + } + return ret; +} + +pthread_barrier_t routine_allocated; +pthread_barrier_t routine_iterated; + +void *allocate_routine(void *vargp) { + iterate_arg_t *iterate_arg = (iterate_arg_t *)vargp; + make_allocations(iterate_arg); + pthread_barrier_wait(&routine_allocated); + pthread_barrier_wait(&routine_iterated); + return NULL; +} + +void *abandoned_allocate_routine(void *vargp) { + iterate_arg_t *iterate_arg = (iterate_arg_t *)vargp; + make_allocations(iterate_arg); + return NULL; +} + +int test_iterate_main_thread(void) { + int ret; + iterate_arg_t iterate_arg = {{0}, {0}, {0}, {0}}; + make_allocations(&iterate_arg); + ret = iterate_wrapper(&iterate_arg); + free_allocations(&iterate_arg); + return ret; +} + +int test_iterate_another_thread(void) { + int ret; + iterate_arg_t iterate_arg_routine = {{0}, {0}, {0}, {0}}; + pthread_barrier_init(&routine_allocated, NULL, 2); + pthread_barrier_init(&routine_iterated, NULL, 2); + pthread_t thread_id; + pthread_create(&thread_id, NULL, allocate_routine, (void *)&iterate_arg_routine); + pthread_barrier_wait(&routine_allocated); + ret = iterate_wrapper(&iterate_arg_routine); + free_allocations(&iterate_arg_routine); + pthread_barrier_wait(&routine_iterated); + return ret; +} + +int test_iterate_over_abandoned_allocs(void) { + int ret; + iterate_arg_t iterate_arg_routine = {{0}, {0}, {0}, {0}}; + pthread_t thread_id; + pthread_create(&thread_id, NULL, abandoned_allocate_routine, (void *)&iterate_arg_routine); + pthread_join(thread_id, NULL); + ret = iterate_wrapper(&iterate_arg_routine); + free_allocations(&iterate_arg_routine); + return ret; +} + +int main() { + int ret = 0; + + ret = check_and_report("Testing iterate main thread", test_iterate_main_thread); + + ret = -(ret || check_and_report("Testing iterate another thread", test_iterate_another_thread)); + + ret = -(ret || check_and_report("Testing iterate over abandoned allocations", test_iterate_over_abandoned_allocs)); + + + return ret; +} diff --git a/libc-test/src/functional/test-mallinfo.c b/libc-test/src/functional/test-mallinfo.c new file mode 100644 index 0000000000000000000000000000000000000000..4b86a0601d8f6b2f23f63284a00782e643563ecd --- /dev/null +++ b/libc-test/src/functional/test-mallinfo.c @@ -0,0 +1,110 @@ +#include "test-malloc-stats-common.h" + +static int stats_from_mallinfo(malloc_thread_stats_t *stats, long long *total_free_heap_space, int use_mallinfo2) +{ + if (use_mallinfo2) { + struct mallinfo2 info = mallinfo2(); + *stats = (malloc_thread_stats_t) {info.hblks, info.hblkhd, info.uordblks}; + *total_free_heap_space = info.fordblks; + return 1; + } + struct mallinfo info = mallinfo(); + if (info.hblks < 0 || info.hblkhd < 0 || info.uordblks < 0 || info.fordblks < 0) { + t_error("struct mallinfo contains negative numbers\n"); + return 0; + } + *stats = (malloc_thread_stats_t) {info.hblks, info.hblkhd, info.uordblks}; + *total_free_heap_space = info.fordblks; + return 1; +} + +static int test_main_thread(int use_mallinfo2) +{ + malloc_thread_stats_t total_stats = {0}; + + void *ptrs[SIZES_COUNT]; + for (size_t i = 0; i < SIZES_COUNT; i++) { + ptrs[i] = malloc(sizes[i]); + } + long long free_heap_space_after_allocations = 0; + int result = stats_from_mallinfo(&total_stats, &free_heap_space_after_allocations, use_mallinfo2); + result &= validate_total_allocated(&total_stats); + for (size_t i = 0; i < SIZES_COUNT; i++) { + free(ptrs[i]); + } + long long free_heap_space_after_free = 0; + result &= stats_from_mallinfo(&total_stats, &free_heap_space_after_free, use_mallinfo2); + result &= validate_all_freed(&total_stats); + result &= expect_greater_equal( + free_heap_space_after_free, + free_heap_space_after_allocations, + "free heap space after free", + "free heap space after allocations" + ); + return result; +} + +static int test_different_threads(int use_mallinfo2) +{ + malloc_thread_stats_t total_stats = {0}; + + pthread_barrier_t alloc_barrier, free_barrier; + if (pthread_barrier_init(&alloc_barrier, NULL, SIZES_COUNT + 1)) { + return 0; + } + if (pthread_barrier_init(&free_barrier, NULL, SIZES_COUNT + 1)) { + return 0; + } + + thread_data_t thread_data[SIZES_COUNT]; + for (size_t i = 0; i < SIZES_COUNT; i++) { + thread_data[i] = (thread_data_t) {sizes[i], &alloc_barrier, &free_barrier, 0}; + } + pthread_t threads[SIZES_COUNT]; + for (size_t i = 0; i < SIZES_COUNT; i++) { + pthread_create(&threads[i], NULL, allocate_wait_free, &thread_data[i]); + } + pthread_barrier_wait(&alloc_barrier); + long long free_heap_space_after_allocations = 0; + int result = stats_from_mallinfo(&total_stats, &free_heap_space_after_allocations, use_mallinfo2); + result &= validate_total_allocated(&total_stats); + + pthread_barrier_wait(&free_barrier); + for (size_t i = 0; i < SIZES_COUNT; i++) { + pthread_join(threads[i], NULL); + } + long long free_heap_space_after_free = 0; + result &= stats_from_mallinfo(&total_stats, &free_heap_space_after_free, use_mallinfo2); + result &= validate_all_freed(&total_stats); + result &= expect_greater_equal( + free_heap_space_after_free, + free_heap_space_after_allocations, + "free heap space after free", + "free heap space after allocations" + ); + return result; +} + +static int test_and_report( + int (*test_func)(int), + int test_func_arg, + const char *message +) +{ + t_printf("%s...", message); + if (!test_func(test_func_arg)) { + t_error("Failed!\n"); + return 0; + } + t_printf("Success\n"); + return 1; +} + +int main(void) +{ + int result = test_and_report(test_main_thread, 0, "Testing mallinfo main thread"); + result &= test_and_report(test_main_thread, 1, "Testing mallinfo2 main thread"); + result &= test_and_report(test_different_threads, 0, "Testing mallinfo different threads"); + result &= test_and_report(test_different_threads, 1, "Testing mallinfo2 different threads"); + return result == 0; +} \ No newline at end of file diff --git a/libc-test/src/functional/test-malloc-api-common.h b/libc-test/src/functional/test-malloc-api-common.h new file mode 100644 index 0000000000000000000000000000000000000000..0f2f2d2128861d2a7d64635baff24b82ffdab1ce --- /dev/null +++ b/libc-test/src/functional/test-malloc-api-common.h @@ -0,0 +1,18 @@ +#ifndef TEST_ITERATE_DISABLE_COMMON_H +#define TEST_ITERATE_DISABLE_COMMON_H + +#include "test.h" + +int check_and_report(const char *message, int (*check_func)(void)) +{ + t_printf("%s...", message); + int ret = check_func(); + if (ret == 0) { + t_printf("Success\n"); + } else { + t_error("Failed\n"); + } + return ret; +} + +#endif //TEST_ITERATE_DISABLE_COMMON_H \ No newline at end of file diff --git a/libc-test/src/functional/test-malloc-backtrace.c b/libc-test/src/functional/test-malloc-backtrace.c new file mode 100644 index 0000000000000000000000000000000000000000..e7ed293b2a99066ac2bcc324c84e3922da4cb85d --- /dev/null +++ b/libc-test/src/functional/test-malloc-backtrace.c @@ -0,0 +1,12 @@ +#include +#include "test-malloc-api-common.h" + +static int test_backtrace() +{ + return malloc_backtrace(NULL, NULL, 0) != 0; +} + +int main() +{ + return check_and_report("Testing malloc_backtrace stub", test_backtrace); +} \ No newline at end of file diff --git a/libc-test/src/functional/test-malloc-disable.c b/libc-test/src/functional/test-malloc-disable.c new file mode 100644 index 0000000000000000000000000000000000000000..ccfbb3c7d2725ed0a24ac8acb95682e0c041a6d6 --- /dev/null +++ b/libc-test/src/functional/test-malloc-disable.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test-malloc-api-common.h" + +pthread_barrier_t routine_disabled; +pthread_barrier_t routine_allocated; + +const size_t SLEEP_TIME_SECONDS = 1; + +void *disable_routine(void *vargp) { + malloc_disable(); + pthread_barrier_wait(&routine_disabled); + sleep(SLEEP_TIME_SECONDS); + malloc_enable(); + pthread_barrier_wait(&routine_allocated); + return NULL; +} + +int test_malloc_while_disabled(void) { + int ret = 0; + pthread_barrier_init(&routine_disabled, NULL, 2); + pthread_barrier_init(&routine_allocated, NULL, 2); + pthread_t thread_id; + pthread_create(&thread_id, NULL, disable_routine, NULL); + pthread_barrier_wait(&routine_disabled); + time_t start = time(0); + int *x = malloc(sizeof(int)); + pthread_barrier_wait(&routine_allocated); + time_t end = time(0); + size_t seconds = end - start; + if (seconds < SLEEP_TIME_SECONDS) { +// printf("elapsed time: %zu, required at least %zu", seconds, SLEEP_TIME_SECONDS); + ret = -1; + } + free(x); + pthread_join(thread_id, NULL); + + return ret; +} + + +int main() { + int ret = 0; + + ret = check_and_report("Testing malloc while disabled", test_malloc_while_disabled); + + return ret; +} diff --git a/libc-test/src/functional/test-malloc-info-stats-print.h b/libc-test/src/functional/test-malloc-info-stats-print.h new file mode 100644 index 0000000000000000000000000000000000000000..d4473b0737f9be7e5e831b961cb702a624fe6649 --- /dev/null +++ b/libc-test/src/functional/test-malloc-info-stats-print.h @@ -0,0 +1,237 @@ +#ifndef TEST_MALLOC_STATS_H +#define TEST_MALLOC_STATS_H + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include "test-malloc-stats-common.h" +#include "test.h" + +#define MAX_TID_LEN 32 +#define BUFFER_SIZE 4096 + +typedef struct { + char stats_after_allocations[BUFFER_SIZE]; + char stats_after_free[BUFFER_SIZE]; + char threads[SIZES_COUNT][MAX_TID_LEN + 1]; +} test_results_t; + +static void stderr_stats_cb(void); + +static int populate_thread_stats(const char *input, const char *thread_id, malloc_thread_stats_t *stats); + +static int populate_total_free_heap_space(const char *input, long long *total_free_heap_space); + +static void print_to_file(void *fp, const char *s) +{ + fputs(s, fp); +} + +int stats_to_buffer(char *buffer) +{ + fflush(stderr); + int err_pipe[2]; + int saved_stderr = dup(STDERR_FILENO); + if (pipe(err_pipe) != 0) { + perror("Can't create pipe"); + return 0; + } + dup2(err_pipe[1], STDERR_FILENO); + close(err_pipe[1]); + stderr_stats_cb(); + fflush(stderr); + read(err_pipe[0], buffer, BUFFER_SIZE); + dup2(saved_stderr, STDERR_FILENO); + return 1; +} + + +static test_results_t get_main_thread_test_results(void) +{ + test_results_t test_results = {{0}, + {0}, + {{0}}}; + + snprintf(test_results.threads[0], MAX_TID_LEN, "%d", (pid_t) syscall(__NR_gettid)); + + void *ptrs[SIZES_COUNT] = {0}; + for (size_t i = 0; i < SIZES_COUNT; i++) { + ptrs[i] = malloc(sizes[i]); + } + stats_to_buffer(test_results.stats_after_allocations); + for (size_t i = 0; i < SIZES_COUNT; i++) { + free(ptrs[i]); + } + stats_to_buffer(test_results.stats_after_free); + return test_results; +} + +static test_results_t get_different_threads_test_results(void) +{ + test_results_t test_results = {{0}, + {0}, + {{0}}}; + pthread_barrier_t alloc_barrier, free_barrier; + if (pthread_barrier_init(&alloc_barrier, NULL, SIZES_COUNT + 1)) { + return test_results; + } + if (pthread_barrier_init(&free_barrier, NULL, SIZES_COUNT + 1)) { + return test_results; + } + + thread_data_t thread_data[SIZES_COUNT]; + for (size_t i = 0; i < SIZES_COUNT; i++) { + thread_data[i] = (thread_data_t) {sizes[i], &alloc_barrier, &free_barrier, 0}; + } + pthread_t threads[SIZES_COUNT]; + for (size_t i = 0; i < SIZES_COUNT; i++) { + pthread_create(&threads[i], NULL, allocate_wait_free, &thread_data[i]); + } + pthread_barrier_wait(&alloc_barrier); + + for (size_t i = 0; i < SIZES_COUNT; i++) { + snprintf(test_results.threads[i], MAX_TID_LEN, "%d", thread_data[i].self_id); + } + stats_to_buffer(test_results.stats_after_allocations); + + pthread_barrier_wait(&free_barrier); + for (size_t i = 0; i < SIZES_COUNT; i++) { + pthread_join(threads[i], NULL); + } + stats_to_buffer(test_results.stats_after_free); + return test_results; +} + +static void *allocate_and_abandon(void *arg) +{ + void **allocs = arg; + for (size_t i = 0; i < SIZES_COUNT; i++) { + allocs[i] = malloc(sizes[i]); + } + return NULL; +} + +static test_results_t get_abandoned_test_results(void) +{ + test_results_t test_results = {{0}, + {0}, + {{0}}}; + pthread_t t; + void *allocs[SIZES_COUNT] = {0}; + pthread_create(&t, NULL, allocate_and_abandon, &allocs); + pthread_join(t, NULL); + stats_to_buffer(test_results.stats_after_allocations); + for (size_t i = 0; i < SIZES_COUNT; i++) { + free(allocs[i]); + } + stats_to_buffer(test_results.stats_after_free); + return test_results; +} + +static int validate_main_thread_test_results(test_results_t *test_results) +{ + malloc_thread_stats_t stats_after_allocations; + malloc_thread_stats_t stats_after_free; + populate_thread_stats(test_results->stats_after_allocations, test_results->threads[0], &stats_after_allocations); + populate_thread_stats(test_results->stats_after_free, test_results->threads[0], &stats_after_free); + int result = validate_total_allocated(&stats_after_allocations); + result &= validate_all_freed(&stats_after_free); + return result; +} + +static int validate_allocated_size(size_t size, malloc_thread_stats_t *stats) +{ + int result = expect_greater_equal(stats->total_allocated_memory, size, "allocated memory", "size"); + if (size >= MMAP_THRESHOLD) { + result &= expect_greater_equal(stats->total_mmapped_memory, size, "mmapped memory", "size"); + result &= expect_equal(stats->mmapped_regions, 1, "mmapped regions"); + } + return result; +} + +static int validate_different_threads_test_results(test_results_t *test_results) +{ + int result = 1; + for (size_t i = 0; i < SIZES_COUNT; i++) { + malloc_thread_stats_t thread_stats; + result &= populate_thread_stats(test_results->stats_after_allocations, test_results->threads[i], &thread_stats); + result &= validate_allocated_size(sizes[i], &thread_stats); + if (strstr(test_results->stats_after_free, test_results->threads[i]) != NULL) { + t_error("Thread %s did not disappear from output\n", test_results->threads[i]); + result = 0; + } + } + + malloc_thread_stats_t abandoned_stats; + result &= populate_thread_stats(test_results->stats_after_free, "abandoned", &abandoned_stats); + result &= validate_all_freed(&abandoned_stats); + + long long free_heap_space_after_allocations = 0; + long long free_heap_space_after_free = 0; + result &= populate_total_free_heap_space(test_results->stats_after_allocations, &free_heap_space_after_allocations); + result &= populate_total_free_heap_space(test_results->stats_after_free, &free_heap_space_after_free); + result &= expect_greater_equal( + free_heap_space_after_free, + free_heap_space_after_allocations, + "free heap space after free", + "free heap space after allocations" + ); + return result; +} + +static int validate_abandoned_test_results(test_results_t *test_results) +{ + malloc_thread_stats_t stats_after_allocations; + malloc_thread_stats_t stats_after_free; + populate_thread_stats(test_results->stats_after_allocations, "abandoned", &stats_after_allocations); + populate_thread_stats(test_results->stats_after_free, "abandoned", &stats_after_free); + int result = validate_total_allocated(&stats_after_allocations); + result &= validate_all_freed(&stats_after_free); + return result; +} + +static int validate_and_report( + test_results_t *test_results, + int (*validate_test_results_func)(test_results_t *), + const char *message +) +{ + t_printf("%s...", message); + if (!validate_test_results_func(test_results)) { + t_error("Failed!\n"); + return 0; + } + t_printf("Success\n"); + return 1; +} + +int main(void) +{ + test_results_t main_thread_test_results = get_main_thread_test_results(); + test_results_t different_threads_test_results = get_different_threads_test_results(); + test_results_t abandoned_test_results = get_abandoned_test_results(); + int result = validate_and_report( + &main_thread_test_results, + validate_main_thread_test_results, + "Testing allocations in main thread" + ); + result &= validate_and_report( + &different_threads_test_results, + validate_different_threads_test_results, + "Testing allocations in different threads" + ); + result &= validate_and_report( + &abandoned_test_results, + validate_abandoned_test_results, + "Testing abandoned allocations" + ); + return result == 0; +} + +#endif //TEST_MALLOC_STATS_H diff --git a/libc-test/src/functional/test-malloc-info.c b/libc-test/src/functional/test-malloc-info.c new file mode 100644 index 0000000000000000000000000000000000000000..39f26d5f9ffa925dd6aacdef3077878141fb6b98 --- /dev/null +++ b/libc-test/src/functional/test-malloc-info.c @@ -0,0 +1,126 @@ +#include + +#include "test-malloc-info-stats-print.h" +#include "libxml/parser.h" + +static const xmlChar *get_text_from_children(xmlNodePtr children) +{ + for (xmlNodePtr child_node = children; child_node != NULL; child_node = child_node->next) { + if (child_node->type == XML_TEXT_NODE) { + return child_node->content; + } + } + return NULL; +} + + +static const xmlChar *get_attribute(const char *attr_name, xmlNodePtr node) +{ + for (xmlAttrPtr curr_attr = node->properties; curr_attr != NULL; curr_attr = curr_attr->next) { + if (xmlStrEqual(curr_attr->name, (const xmlChar *) attr_name)) { + return get_text_from_children(curr_attr->children); + } + } + return NULL; +} + +static xmlNodePtr +find_child_node_with_attr(const char *name, const char *attr_name, const char *attr_value, xmlNodePtr parent) +{ + if (parent == NULL) { + return NULL; + } + for (xmlNodePtr curr_node = parent->children; curr_node != NULL; curr_node = curr_node->next) { + if (curr_node->type == XML_ELEMENT_NODE && xmlStrEqual(curr_node->name, (xmlChar *) name)) { + if (attr_name == NULL) { + return curr_node; + } + if (xmlStrEqual(get_attribute(attr_name, curr_node), (const xmlChar *) attr_value)) { + return curr_node; + } + } + } + return NULL; +} + +static xmlNodePtr find_child_node(const char *name, xmlNodePtr parent) +{ + return find_child_node_with_attr(name, NULL, NULL, parent); +} + +static const char *get_node_text(xmlNodePtr node_ptr) +{ + if (node_ptr == NULL) { + return NULL; + } + return (const char *) get_text_from_children(node_ptr->children); +} + +static void stderr_stats_cb(void) +{ + malloc_info(0, stderr); +} + +static long long parse_amount(const char *s) +{ + if (s == NULL) { + return -1; + } + char *end_ptr; + long long result = strtoll(s, &end_ptr, 10); + if (end_ptr != s + strlen(s)) { + return -1; + } + if (result < 0) { + return -1; + } + return result; +} + +static int populate_thread_stats(const char *input, const char *thread_id, malloc_thread_stats_t *stats) +{ + xmlDocPtr doc_ptr = xmlParseDoc((const xmlChar *) input); + if (doc_ptr == NULL) { + return 0; + } + xmlNodePtr root_element = xmlDocGetRootElement(doc_ptr); + xmlNodePtr thread_root; + if (strcmp(thread_id, "abandoned") == 0) { + thread_root = find_child_node("abandoned", root_element); + } else { + xmlNodePtr threads = find_child_node("threads", root_element); + thread_root = find_child_node_with_attr("thread", "id", thread_id, threads); + } + long long total_allocated_memory = + parse_amount(get_node_text(find_child_node("total_allocated_memory", thread_root))); + long long total_mmapped_memory = + parse_amount(get_node_text(find_child_node("total_mmapped_memory", thread_root))); + long long mmapped_regions = + parse_amount(get_node_text(find_child_node("mmapped_regions", thread_root))); + xmlFreeDoc(doc_ptr); + + if (total_allocated_memory == -1 || total_mmapped_memory == -1 || mmapped_regions == -1) { + return 0; + } + stats->total_allocated_memory = total_allocated_memory; + stats->total_mmapped_memory = total_mmapped_memory; + stats->mmapped_regions = mmapped_regions; + return 1; +} + +static int populate_total_free_heap_space(const char *input, long long *total_free_heap_space) +{ + xmlDocPtr doc_ptr = xmlParseDoc((const xmlChar *) input); + if (doc_ptr == NULL) { + return 0; + } + xmlNodePtr heap_space_root = find_child_node("total_free_heap_space", xmlDocGetRootElement(doc_ptr)); + long long total_free_heap_space_parsed = parse_amount(get_node_text(heap_space_root)); + xmlFreeDoc(doc_ptr); + + if (total_free_heap_space_parsed == -1) { + return 0; + } + *total_free_heap_space = total_free_heap_space_parsed; + return 1; +} \ No newline at end of file diff --git a/libc-test/src/functional/test-malloc-stats-common.h b/libc-test/src/functional/test-malloc-stats-common.h new file mode 100644 index 0000000000000000000000000000000000000000..adfbc968ffe2637f7cb6c8eeea395d4cf1cd84a7 --- /dev/null +++ b/libc-test/src/functional/test-malloc-stats-common.h @@ -0,0 +1,113 @@ +#ifndef TEST_MALLOC_STATS_COMMON_H +#define TEST_MALLOC_STATS_COMMON_H + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include "test.h" + +#define SIZES_COUNT 11 +#define SIZE_ALIGN (8*sizeof(size_t)) +#define MMAP_THRESHOLD (0x1c00*SIZE_ALIGN) - OVERHEAD +#define LIST_OVERHEAD (2*sizeof(void*)) +#define OVERHEAD (2*sizeof(size_t) + sizeof(void*) + LIST_OVERHEAD) + +static size_t sizes[SIZES_COUNT] = {23, 32, + 256, 3072, + 3584, 229376, + 262144, 327680, + 8 * 1024 * 1024, 16 * 1024 * 1024, + 32 * 1024 * 1024}; + +typedef struct { + long long mmapped_regions; + long long total_mmapped_memory; + long long total_allocated_memory; +} malloc_thread_stats_t; + +typedef struct { + size_t alloc_size; + pthread_barrier_t *alloc_barrier; + pthread_barrier_t *free_barrier; + pid_t self_id; +} thread_data_t; + +static malloc_thread_stats_t get_total_from_test_sizes() +{ + malloc_thread_stats_t total_stats = {0}; + for (size_t i = 0; i < SIZES_COUNT; i++) { + if (sizes[i] > MMAP_THRESHOLD) { + total_stats.total_mmapped_memory += sizes[i]; + total_stats.mmapped_regions++; + } + total_stats.total_allocated_memory += sizes[i]; + } + return total_stats; +} + +static int expect_greater_equal(long long amt1, long long amt2, const char *amt1_name, const char *amt2_name) +{ + if (amt1 >= amt2) { + return 1; + } + t_error("Expected %s(value: %lld) to be >= %s(value: %lld)\n", amt1_name, amt1, amt2_name, amt2); + return 0; +} + +static int expect_equal(long long amt, long long value, const char *amt_name) +{ + if (amt == value) { + return 1; + } + t_error("Expected %s(value: %lld) to be %lld\n", amt_name, amt, value); + return 0; +} + +static int validate_total_allocated(malloc_thread_stats_t *total_stats) +{ + malloc_thread_stats_t total_from_test_sizes = get_total_from_test_sizes(); + + int result = expect_greater_equal( + total_stats->total_allocated_memory, + total_from_test_sizes.total_allocated_memory, + "allocated memory", + "total memory from test sizes" + ); + result &= expect_greater_equal( + total_stats->total_mmapped_memory, + total_from_test_sizes.total_mmapped_memory, + "mmapped memory", + "total large memory from test sizes" + ); + result &= expect_equal( + total_stats->mmapped_regions, + total_from_test_sizes.mmapped_regions, + "mmapped regions" + ); + return result; +} + +static int validate_all_freed(malloc_thread_stats_t *total_stats) +{ + int result = expect_equal(total_stats->total_allocated_memory, 0, "allocated memory"); + result &= expect_equal(total_stats->total_mmapped_memory, 0, "mmapped memory"); + result &= expect_equal(total_stats->mmapped_regions, 0, "mmapped regions"); + return result; +} + +static void *allocate_wait_free(void *arg) +{ + thread_data_t *thread_data = arg; + thread_data->self_id = syscall(__NR_gettid); + void *alloc = malloc(thread_data->alloc_size); + pthread_barrier_wait(thread_data->alloc_barrier); + pthread_barrier_wait(thread_data->free_barrier); + free(alloc); + return NULL; +} + +#endif //TEST_MALLOC_STATS_COMMON_H diff --git a/libc-test/src/functional/test-malloc-stats-print.c b/libc-test/src/functional/test-malloc-stats-print.c new file mode 100644 index 0000000000000000000000000000000000000000..bc8bf4cab1b52dc11fc4759403745110c7a41c4a --- /dev/null +++ b/libc-test/src/functional/test-malloc-stats-print.c @@ -0,0 +1,46 @@ +#include "test-malloc-info-stats-print.h" + +static void stderr_stats_cb(void) +{ + malloc_stats_print(print_to_file, stderr, ""); +} + +static int parse_amount(char **s, long long *destination) +{ + char *end_ptr = NULL; + long long result = strtoll(*s, &end_ptr, 10); + if (end_ptr == *s) { + return 0; + } + *s = end_ptr; + if ((!isspace(*end_ptr) && *end_ptr != '\n' && *end_ptr != '\0') || result < 0) { + return 0; + } + *destination = result; + return 1; +} + +static int populate_thread_stats(const char *input, const char *thread_id, malloc_thread_stats_t *stats) +{ + char *thread_id_start = strstr(input, thread_id); + if (thread_id_start == NULL) { + return 0; + } + thread_id_start += strlen(thread_id); + int result = 1; + result &= parse_amount(&thread_id_start, &stats->total_allocated_memory); + result &= parse_amount(&thread_id_start, &stats->total_mmapped_memory); + result &= parse_amount(&thread_id_start, &stats->mmapped_regions); + + return result; +} + +static int populate_total_free_heap_space(const char *input, long long *total_free_heap_space) +{ + char *free_heap_space_start = strstr(input, "total free heap space:"); + if (free_heap_space_start == NULL) { + return 0; + } + free_heap_space_start += strlen("total free heap space:"); + return parse_amount(&free_heap_space_start, total_free_heap_space); +} diff --git a/libc-test/src/functional/test-mallopt.c b/libc-test/src/functional/test-mallopt.c new file mode 100644 index 0000000000000000000000000000000000000000..c7eab1f4e4c203f64f98c28d8a62beb290955fc5 --- /dev/null +++ b/libc-test/src/functional/test-mallopt.c @@ -0,0 +1,12 @@ +#include +#include "test-malloc-api-common.h" + +static int test_mallopt() +{ + return mallopt(0, 0) != 0; +} + +int main() +{ + return check_and_report("Testing mallopt stub", test_mallopt); +} \ No newline at end of file diff --git a/libc-test/src/functional/test_src_functional.gni b/libc-test/src/functional/test_src_functional.gni index 85d0754386a4ce1b7586812b73fead5168254be1..d968f6cf7c6156fba9d46c92aae0f682f6e5b937 100644 --- a/libc-test/src/functional/test_src_functional.gni +++ b/libc-test/src/functional/test_src_functional.gni @@ -79,3 +79,13 @@ functional_list = [ "wcstol", "dlclose_reset", ] + +malloc_stats_list = [ + "test-malloc-stats-print", + "test-malloc-info", + "test-mallinfo", + "test-iterate", + "test-malloc-disable", + "test-malloc-backtrace", + "test-mallopt", +] diff --git a/libc-test/test_template.gni b/libc-test/test_template.gni index a8bb2a60c1e22ed08e2c54253d968ce4e19a9c44..74305c6dc9fe1065fd6eeb42149f5e3d33c89d48 100644 --- a/libc-test/test_template.gni +++ b/libc-test/test_template.gni @@ -129,6 +129,12 @@ template("test_unittest") { ldflags += [ "-Wl,-rpath=src/functional" ] libs += [ "//${root_out_dir}/${test_lib_dir}/libtls_init_dso.so" ] } + + if (target_name == "test-malloc-info") { + include_dirs += [ "//third_party/libxml2/include" ] + lib_dirs = [ "//${root_out_dir}/thirdparty/libxml2" ] + libs += [ "xml2.z" ] + } } if (target_dir == "functionalext/fortify") { diff --git a/musl_config.gni b/musl_config.gni index bfc1d9a2b3971f571116e918470b2899769e203e..0c55c4e072eccf9dd0d949a1efa3c4d705d459ab 100644 --- a/musl_config.gni +++ b/musl_config.gni @@ -56,6 +56,7 @@ declare_args() { if (!is_standard_system) { enable_musl_log = false } + musl_iterate_and_stats_api = true musl_secure_level = 1 } diff --git a/musl_src.gni b/musl_src.gni index 876f5b648c7d848dd4bc5de4b506e86cf7ed5c66..083500fbcac49619a2b445315fdb5022a3f23768 100644 --- a/musl_src.gni +++ b/musl_src.gni @@ -515,7 +515,7 @@ musl_src_file = [ "src/malloc/malloc.c", "src/malloc/malloc_random.c", "src/malloc/malloc_usable_size.c", - "src/malloc/mallocng/mallinfo.c", + "src/malloc/stats.c", "src/malloc/memalign.c", "src/malloc/posix_memalign.c", "src/math/__cos.c", @@ -2017,6 +2017,7 @@ musl_src_porting_file = [ "include/info/application_target_sdk_version.h", "include/info/device_api_version.h", "include/info/fatal_message.h", + "include/malloc.h", "include/pthread.h", "include/fcntl.h", "include/poll.h", @@ -2065,6 +2066,8 @@ musl_src_porting_file = [ "src/linux/reboot.c", "src/linux/tgkill.c", "src/malloc/malloc.c", + "src/malloc/memalign.c", + "src/malloc/stats.c", "src/malloc/malloc_random.c", "src/multibyte/wcsnrtombs.c", "src/network/inet_legacy.c", @@ -2124,6 +2127,8 @@ musl_src_porting_file = [ "src/ldso/arm/dlvsym.s", "src/ldso/riscv64/dlvsym.s", "src/ldso/x86_64/dlvsym.s", + "src/thread/pthread_getspecific.c", + "src/thread/pthread_setspecific.c", ] musl_inc_hook_files = [ diff --git a/musl_template.gni b/musl_template.gni index a5eb0a8a30ad5f3b3dd29cad6d2113296a2fffaf..f83752ed144abd481ebcc3e33a24877d6e1b525d 100644 --- a/musl_template.gni +++ b/musl_template.gni @@ -280,6 +280,10 @@ template("musl_libs") { defines += [ "MALLOC_SECURE_ALL" ] } + if (musl_iterate_and_stats_api) { + defines += [ "MUSL_ITERATE_AND_STATS_API" ] + } + foreach(s, sources_orig) { sources += [ "${target_out_dir}/${musl_ported_dir}/${s}" ] } @@ -386,6 +390,11 @@ template("musl_libs") { "src/env/__stack_chk_fail.c", ] + defines = [] + if (musl_iterate_and_stats_api) { + defines += [ "MUSL_ITERATE_AND_STATS_API" ] + } + if (musl_arch == "arm") { sources_orig += [ "src/thread/${musl_arch}/__set_thread_area.c" ] } else if (musl_arch == "aarch64") { diff --git a/porting/linux/user/include/malloc.h b/porting/linux/user/include/malloc.h new file mode 100644 index 0000000000000000000000000000000000000000..f2f26a6569f16567244d0690f0cc442b6771b5be --- /dev/null +++ b/porting/linux/user/include/malloc.h @@ -0,0 +1,68 @@ +#ifndef _MALLOC_H +#define _MALLOC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define __NEED_size_t +#define __NEED_ssize_t +#define __NEED_uintptr_t + +#include +#include + +void *malloc (size_t); +void *calloc (size_t, size_t); +void *realloc (void *, size_t); +void free (void *); +void *valloc (size_t); +void *memalign(size_t, size_t); + +size_t malloc_usable_size(void *); + +struct mallinfo { + int arena; + int ordblks; + int smblks; + int hblks; + int hblkhd; + int usmblks; + int fsmblks; + int uordblks; + int fordblks; + int keepcost; +}; + +struct mallinfo mallinfo(void); + +struct mallinfo2 { + size_t arena; + size_t ordblks; + size_t smblks; + size_t hblks; + size_t hblkhd; + size_t usmblks; + size_t fsmblks; + size_t uordblks; + size_t fordblks; + size_t keepcost; +}; + +struct mallinfo2 mallinfo2(void); + +int malloc_iterate(void* base, size_t size, void (*callback)(void* base, size_t size, void* arg), void* arg); +void malloc_disable(void); +void malloc_enable(void); + +int malloc_info(int options, FILE* fp); +void malloc_stats_print(void (*write_cb) (void *, const char *), void *cbopaque, const char *opts); + +int mallopt(int param, int value); +ssize_t malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/porting/linux/user/src/env/__libc_start_main.c b/porting/linux/user/src/env/__libc_start_main.c index 65a446a9a0211e7ac910adfdb9c7990a374da8bb..54ec4611b655970ef63138e51636c6bfbb231a8f 100644 --- a/porting/linux/user/src/env/__libc_start_main.c +++ b/porting/linux/user/src/env/__libc_start_main.c @@ -6,8 +6,13 @@ #include "syscall.h" #include "atomic.h" #include "libc.h" +#include "pthread.h" +#include "sys/mman.h" +#include "malloc_impl.h" #include "pthread_impl.h" +extern pthread_key_t occupied_bin_key; + static void dummy(void) {} weak_alias(dummy, _init); @@ -95,6 +100,15 @@ static int libc_start_main_stage2(int (*main)(int,char **,char **), int argc, ch #endif errno = 0; +#ifdef MUSL_ITERATE_AND_STATS_API + __init_occupied_bin_key_once(); + occupied_bin_t *occupied_bin = internal_calloc(sizeof(occupied_bin_t), 1); + if (occupied_bin == NULL) return ENOMEM; + pthread_setspecific(occupied_bin_key, occupied_bin); +#endif + libc.initialized = 1; + + /* Pass control to the application */ exit(main(argc, argv, envp)); return 0; diff --git a/porting/linux/user/src/internal/libc.h b/porting/linux/user/src/internal/libc.h index 468fe12450c322430d3e1a2aab45972af4b1c40e..4797d1b9862ad68860d31f24bc447bfa45aa7f2d 100644 --- a/porting/linux/user/src/internal/libc.h +++ b/porting/linux/user/src/internal/libc.h @@ -36,6 +36,7 @@ struct __libc { int can_do_threads; int threaded; int secure; + int initialized; volatile int threads_minus_1; size_t *auxv; struct tls_module *tls_head; diff --git a/porting/linux/user/src/internal/malloc_impl.h b/porting/linux/user/src/internal/malloc_impl.h old mode 100644 new mode 100755 index 717899fa41b8eba85b6870fca970fbcea0f6244d..e893cd4d2e454a8ec0e4277c75182fad0dd5803d --- a/porting/linux/user/src/internal/malloc_impl.h +++ b/porting/linux/user/src/internal/malloc_impl.h @@ -10,11 +10,24 @@ hidden void __malloc_donate(char *, char *); hidden void *__memalign(size_t, size_t); +typedef struct occupied_bin_s { + struct chunk *head, *tail; + volatile int lock[2]; +} occupied_bin_t; + struct chunk { size_t psize, csize; +#ifdef MUSL_ITERATE_AND_STATS_API + occupied_bin_t *bin; +#endif + #ifdef MALLOC_RED_ZONE size_t usize; size_t state; +#endif +#ifdef MUSL_ITERATE_AND_STATS_API + size_t flag; + struct chunk *next_occupied, *prev_occupied; #endif struct chunk *next, *prev; }; @@ -28,17 +41,38 @@ struct bin { #endif }; +#ifdef MUSL_ITERATE_AND_STATS_API +typedef void (*malloc_iterate_callback)(void* base, size_t size, void* arg); + +hidden occupied_bin_t *__get_occupied_bin(struct __pthread *p); +hidden occupied_bin_t *__get_current_occupied_bin(); +hidden void __merge_bin_chunks(occupied_bin_t *target_bin, occupied_bin_t *source_bin); +hidden void __init_occupied_bin_key_once(void); +hidden void __push_chunk(struct chunk *c); +hidden void __pop_chunk(struct chunk *c); +#endif + #define SIZE_MASK (-SIZE_ALIGN) -#ifndef MALLOC_RED_ZONE -#define SIZE_ALIGN (4*sizeof(size_t)) -#define OVERHEAD (2*sizeof(size_t)) + +#ifdef MUSL_ITERATE_AND_STATS_API +#define OCCUPIED_LIST_OVERHEAD (2*sizeof(void*)) +#define ITERATE_AND_STATS_OVERHEAD (sizeof(size_t) + sizeof(void*) + OCCUPIED_LIST_OVERHEAD) #else +#define ITERATE_AND_STATS_OVERHEAD (0) +#endif + +#ifndef MALLOC_RED_ZONE #define SIZE_ALIGN (8*sizeof(size_t)) -#define OVERHEAD (4*sizeof(size_t)) +#define OVERHEAD (2*sizeof(size_t) + ITERATE_AND_STATS_OVERHEAD) +#else +#define SIZE_ALIGN (16*sizeof(size_t)) +#define OVERHEAD (4*sizeof(size_t) + ITERATE_AND_STATS_OVERHEAD) #endif + + #define MMAP_THRESHOLD (0x1c00*SIZE_ALIGN) #ifndef MALLOC_RED_ZONE -#define DONTCARE 16 +#define DONTCARE OVERHEAD #else #define DONTCARE OVERHEAD #define POINTER_USAGE (2*sizeof(void *)) @@ -101,4 +135,4 @@ hidden void chunk_checksum_set(struct chunk *c); hidden int chunk_checksum_check(struct chunk *c); #endif -#endif +#endif \ No newline at end of file diff --git a/porting/linux/user/src/malloc/malloc.c b/porting/linux/user/src/malloc/malloc.c old mode 100644 new mode 100755 index d35ba97677f0f2d53a3bcf6485b4248f4763c63b..4e7b7048054afa3e0b1c8825be79e4cc16cfc436 --- a/porting/linux/user/src/malloc/malloc.c +++ b/porting/linux/user/src/malloc/malloc.c @@ -1,21 +1,59 @@ #define _GNU_SOURCE #include -#include #include #include #include #include +#include #include "libc.h" #include "atomic.h" #include "pthread_impl.h" #include "malloc_impl.h" #include "malloc_random.h" -#include #if defined(__GNUC__) && defined(__PIC__) #define inline inline __attribute__((always_inline)) #endif +#ifdef MUSL_ITERATE_AND_STATS_API +pthread_key_t occupied_bin_key; + +occupied_bin_t detached_occupied_bin; + +static pthread_once_t occupied_bin_key_is_initialized = PTHREAD_ONCE_INIT; + +static void occupied_bin_destructor(void *occupied_bin) +{ + internal_free(occupied_bin); +} + +static void init_occupied_bin_key(void) +{ + pthread_key_create(&occupied_bin_key, occupied_bin_destructor); +} + +void __init_occupied_bin_key_once(void) +{ + pthread_once(&occupied_bin_key_is_initialized, init_occupied_bin_key); +} + +occupied_bin_t *__get_occupied_bin(struct __pthread *p) +{ + __init_occupied_bin_key_once(); + return p->tsd[occupied_bin_key]; +} + +occupied_bin_t *__get_current_occupied_bin() +{ + return __get_occupied_bin(__pthread_self()); +} + +/* Usable memory only, excluding overhead for chunks */ +size_t total_heap_space = 0; +volatile int total_heap_space_inc_lock[2]; +volatile int pop_merge_lock[2]; +#endif + #ifdef HOOK_ENABLE void *__libc_malloc(size_t); void __libc_free(void *p); @@ -65,6 +103,175 @@ static inline void unlock(volatile int *lk) } } +#ifdef MUSL_ITERATE_AND_STATS_API +void __merge_bin_chunks(occupied_bin_t *target_bin, occupied_bin_t *source_bin) +{ + if (!libc.initialized) { + return; + } + lock(pop_merge_lock); + lock(target_bin->lock); + lock(source_bin->lock); + + if (target_bin->head == NULL) { + target_bin->head = source_bin->head; + target_bin->tail = source_bin->tail; + } else { + target_bin->tail->next_occupied = source_bin->head; + if (source_bin->head != NULL) { + source_bin->head->prev_occupied = target_bin->tail; + target_bin->tail = source_bin->tail; + } + } + + for (struct chunk *c = source_bin->head; c != NULL; c = c->next_occupied) { + c->bin = target_bin; + } + + unlock(source_bin->lock); + unlock(target_bin->lock); + unlock(pop_merge_lock); +} + +void __push_chunk(struct chunk *c) +{ + c->prev_occupied = c->next_occupied = NULL; + c->bin = NULL; + if (!libc.initialized) { + return; + } + occupied_bin_t *occupied_bin = __get_current_occupied_bin(); + c->bin = occupied_bin; + if (c->bin == NULL) { + return; + } + lock(occupied_bin->lock); + + if (occupied_bin->head != NULL) { + occupied_bin->head->prev_occupied = c; + c->next_occupied = occupied_bin->head; + } else { + occupied_bin->tail = c; + } + occupied_bin->head = c; + + c->flag = 0x00; + unlock(occupied_bin->lock); +} + +void __pop_chunk(struct chunk *c) +{ + if (!libc.initialized) { + return; + } + lock(pop_merge_lock); + occupied_bin_t *occupied_bin = c->bin; + if (occupied_bin == NULL) { + unlock(pop_merge_lock); + return; + } + lock(occupied_bin->lock); + + if (c == occupied_bin->head) { + occupied_bin->head = c->next_occupied; + } else { + c->prev_occupied->next_occupied = c->next_occupied; + } + if (c == occupied_bin->tail) { + occupied_bin->tail = c->prev_occupied; + } else { + c->next_occupied->prev_occupied = c->prev_occupied; + } + unlock(occupied_bin->lock); + unlock(pop_merge_lock); +} +#endif + +void malloc_disable(void) +{ +#ifdef MUSL_ITERATE_AND_STATS_API + lock(mal.free_lock); + lock(total_heap_space_inc_lock); + for (size_t i = 0; i < 64; ++i) { + lock(mal.bins[i].lock); + } + __tl_lock(); + struct __pthread *self, *it; + self = it = __pthread_self(); + do { + occupied_bin_t *occupied_bin = __get_occupied_bin(it); + lock(occupied_bin->lock); + it = it->next; + } while (it != self); + lock(detached_occupied_bin.lock); +#endif +} + +void malloc_enable(void) +{ +#ifdef MUSL_ITERATE_AND_STATS_API + struct __pthread *self, *it; + self = it = __pthread_self(); + do { + occupied_bin_t *occupied_bin = __get_occupied_bin(it); + unlock(occupied_bin->lock); + it = it->next; + } while (it != self); + unlock(detached_occupied_bin.lock); + __tl_unlock(); + for (size_t i = 0; i < 64; ++i) { + unlock(mal.bins[i].lock); + } + unlock(total_heap_space_inc_lock); + unlock(mal.free_lock); +#endif +} + +#ifdef MUSL_ITERATE_AND_STATS_API +typedef struct iterate_info_s { + uintptr_t start_ptr; + uintptr_t end_ptr; + malloc_iterate_callback callback; + void *arg; +} iterate_info_t; + +static void malloc_iterate_visitor(void *block, size_t block_size, void *arg) +{ + iterate_info_t *iterate_info = (iterate_info_t *)arg; + if ((uintptr_t)block >= iterate_info->start_ptr && (uintptr_t)block < iterate_info->end_ptr) { + iterate_info->callback(block, block_size, iterate_info->arg); + } +} + +static void malloc_iterate_occupied_bin(occupied_bin_t *occupied_bin, iterate_info_t *iterate_info) +{ + for (struct chunk *c = occupied_bin->head; c != NULL; c = c->next_occupied) { + malloc_iterate_visitor(CHUNK_TO_MEM(c), CHUNK_SIZE(c) - OVERHEAD, iterate_info); + } +} +#endif + +int malloc_iterate(void* base, size_t size, void (*callback)(void* base, size_t size, void* arg), void* arg) +{ +#ifdef MUSL_ITERATE_AND_STATS_API + uintptr_t ptr = (uintptr_t)base; + uintptr_t end_ptr = ptr + size; + iterate_info_t iterate_info = {ptr, end_ptr, callback, arg}; + + struct __pthread *self, *it; + self = it = __pthread_self(); + do { + occupied_bin_t *occupied_bin = __get_occupied_bin(it); + malloc_iterate_occupied_bin(occupied_bin, &iterate_info); + it = it->next; + } while (it != self); + + malloc_iterate_occupied_bin(&detached_occupied_bin, &iterate_info); + +#endif + return 0; +} + static inline void lock_bin(int i) { lock(mal.bins[i].lock); @@ -253,9 +460,16 @@ static struct chunk *expand_heap(size_t n) lock(heap_lock); +#ifdef MUSL_ITERATE_AND_STATS_API + lock(total_heap_space_inc_lock); +#endif + p = __expand_heap(&n); if (!p) { unlock(heap_lock); +#ifdef MUSL_ITERATE_AND_STATS_API + unlock(total_heap_space_inc_lock); +#endif return 0; } @@ -285,6 +499,11 @@ static struct chunk *expand_heap(size_t n) chunk_checksum_set(w); #endif +#ifdef MUSL_ITERATE_AND_STATS_API + total_heap_space += n - OVERHEAD; + unlock(total_heap_space_inc_lock); +#endif + unlock(heap_lock); return w; @@ -473,6 +692,7 @@ void *internal_malloc(size_t n) c = (void *)(base + SIZE_ALIGN - OVERHEAD); c->csize = len - (SIZE_ALIGN - OVERHEAD); c->psize = SIZE_ALIGN - OVERHEAD; + #ifdef MALLOC_RED_ZONE c->state = M_STATE_MMAP | M_STATE_USED; c->usize = user_size; @@ -480,6 +700,9 @@ void *internal_malloc(size_t n) chunk_poison_set(c); } chunk_checksum_set(c); +#endif +#ifdef MUSL_ITERATE_AND_STATS_API + __push_chunk(c); #endif return CHUNK_TO_MEM(c); } @@ -531,6 +754,9 @@ void *internal_malloc(size_t n) c->state &= ~M_RZ_POISON; } chunk_checksum_set(c); +#endif +#ifdef MUSL_ITERATE_AND_STATS_API + __push_chunk(c); #endif return CHUNK_TO_MEM(c); } @@ -1044,6 +1270,18 @@ void internal_free(void *p) if (!p) return; struct chunk *self = MEM_TO_CHUNK(p); +#ifdef MUSL_ITERATE_AND_STATS_API +#if 0 + lock(pop_merge_lock); + if (self->flag == 0x01) { + unlock(pop_merge_lock); + return; + } + self->flag = 0x01; + unlock(pop_merge_lock); +#endif + __pop_chunk(self); +#endif #ifdef MALLOC_RED_ZONE /* This is not a valid chunk for freeing */ @@ -1083,6 +1321,23 @@ void __malloc_donate(char *start, char *end) c->usize = POINTER_USAGE; c->state = M_STATE_BRK; chunk_checksum_set(c); +#endif +#ifdef MUSL_ITERATE_AND_STATS_API + lock(total_heap_space_inc_lock); + total_heap_space += CHUNK_SIZE(c) - OVERHEAD; #endif __bin_chunk(c); +#ifdef MUSL_ITERATE_AND_STATS_API + unlock(total_heap_space_inc_lock); +#endif } + +int mallopt(int param, int value) +{ + return 0; +} + +ssize_t malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count) +{ + return 0; +} \ No newline at end of file diff --git a/porting/linux/user/src/malloc/memalign.c b/porting/linux/user/src/malloc/memalign.c index aba959f88afcaf3dca12f440f5feb3d1bc113495..a2fc0332d9cc72bb9811adbf2e3718a116c45f5c 100644 --- a/porting/linux/user/src/malloc/memalign.c +++ b/porting/linux/user/src/malloc/memalign.c @@ -30,6 +30,11 @@ void *__memalign(size_t align, size_t len) struct chunk *c = MEM_TO_CHUNK(mem); struct chunk *n = MEM_TO_CHUNK(new); +#ifdef MUSL_ITERATE_AND_STATS_API + __pop_chunk(c); + __push_chunk(n); +#endif + if (IS_MMAPPED(c)) { /* Apply difference between aligned and original * address to the "extra" field of mmapped chunk. diff --git a/porting/linux/user/src/malloc/stats.c b/porting/linux/user/src/malloc/stats.c new file mode 100755 index 0000000000000000000000000000000000000000..88cd834a42b83cb6cb981b4ae460bd047922209a --- /dev/null +++ b/porting/linux/user/src/malloc/stats.c @@ -0,0 +1,270 @@ +#include +#include +#include +#include "pthread_impl.h" +#include "malloc_impl.h" + +#ifdef MUSL_ITERATE_AND_STATS_API +#define STAT_PRINTF_MAX_LEN 255 +#define ALLOCATOR_VERSION 1 + +typedef void (write_cb_fun)(void *, const char *); + +typedef enum { + TABLE, XML +} print_mode; + +typedef struct { + size_t mmapped_regions; + size_t total_mmapped_memory; + size_t total_allocated_memory; + size_t total_allocated_heap_space; +} malloc_stats_t; + +extern size_t total_heap_space; + +extern occupied_bin_t detached_occupied_bin; + +static void stat_printf(write_cb_fun *write_cb, void *write_cb_arg, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + char buf[STAT_PRINTF_MAX_LEN + 1]; + vsnprintf(buf, STAT_PRINTF_MAX_LEN, fmt, args); + write_cb(write_cb_arg, buf); + va_end(args); +} + +static void print_thread_stats_table( + write_cb_fun *write_cb, + void *write_cb_arg, + struct __pthread *thread, + malloc_stats_t *stats +) +{ + stat_printf( + write_cb, + write_cb_arg, + "%-11d %-23zu %-20zu %-20zu\n", + thread->tid, + stats->total_allocated_memory, + stats->total_mmapped_memory, + stats->mmapped_regions + ); +} + +static void print_amount_xml(write_cb_fun *write_cb, void *write_cb_arg, const char *name, size_t value) +{ + stat_printf(write_cb, write_cb_arg, "<%s>%zu\n", name, value, name); +} + +static void print_thread_specific_amounts_xml(write_cb_fun *write_cb, void *write_cb_arg, malloc_stats_t *stats) +{ + print_amount_xml(write_cb, write_cb_arg, "total_allocated_memory", stats->total_allocated_memory); + print_amount_xml(write_cb, write_cb_arg, "total_mmapped_memory", stats->total_mmapped_memory); + print_amount_xml(write_cb, write_cb_arg, "mmapped_regions", stats->mmapped_regions); +} + +static void print_thread_stats_xml( + write_cb_fun *write_cb, + void *write_cb_arg, + struct __pthread *thread, + malloc_stats_t *stats +) +{ + stat_printf(write_cb, write_cb_arg, "\n", thread->tid); + print_thread_specific_amounts_xml(write_cb, write_cb_arg, stats); + stat_printf(write_cb, write_cb_arg, "\n"); +} + +static malloc_stats_t add_up_chunks(occupied_bin_t *occupied_bin) +{ + malloc_stats_t stats = {0, 0, 0, 0}; + for (struct chunk *c = occupied_bin->head; c != NULL; c = c->next_occupied) { + size_t chunk_memory = CHUNK_SIZE(c) - OVERHEAD; + stats.total_allocated_memory += chunk_memory; + if (IS_MMAPPED(c)) { + stats.mmapped_regions++; + stats.total_mmapped_memory += chunk_memory; + } else { + stats.total_allocated_heap_space += chunk_memory; + } + } + return stats; +} + +static size_t print_threads(write_cb_fun *write_cb, void *write_cb_arg, print_mode mode) +{ + size_t total_allocated_heap_space = 0; + struct __pthread *self, *it; + self = it = __pthread_self(); + do { + malloc_stats_t stats = add_up_chunks(__get_occupied_bin(it)); + total_allocated_heap_space += stats.total_allocated_heap_space; + if (mode == TABLE) { + print_thread_stats_table(write_cb, write_cb_arg, it, &stats); + } else { + print_thread_stats_xml(write_cb, write_cb_arg, it, &stats); + } + it = it->next; + } while (it != self); + + return total_allocated_heap_space; +} + +static void print_abandoned_stats_table(write_cb_fun *write_cb, void *write_cb_arg, malloc_stats_t *stats) +{ + stat_printf( + write_cb, + write_cb_arg, + "%s\n%-11s %-23zu %-20zu %-20zu\n", + "---------", + "abandoned", + stats->total_allocated_memory, + stats->total_mmapped_memory, + stats->mmapped_regions + ); +} + +static void print_abandoned_stats_xml(write_cb_fun *write_cb, void *write_cb_arg, malloc_stats_t *stats) +{ + stat_printf(write_cb, write_cb_arg, "\n"); + print_thread_specific_amounts_xml(write_cb, write_cb_arg, stats); + stat_printf(write_cb, write_cb_arg, "\n"); +} + +static size_t print_abandoned(write_cb_fun *write_cb, void *write_cb_arg, print_mode mode) +{ + malloc_stats_t stats = add_up_chunks(&detached_occupied_bin); + if (mode == TABLE) { + print_abandoned_stats_table(write_cb, write_cb_arg, &stats); + } else { + print_abandoned_stats_xml(write_cb, write_cb_arg, &stats); + } + return stats.total_allocated_heap_space; +} + +static void print_total_free_heap_space( + write_cb_fun *write_cb, + void *write_cb_arg, + size_t total_allocated_heap_space, + print_mode mode +) +{ + if (mode == TABLE) { + stat_printf(write_cb, write_cb_arg, "\n"); + for (size_t i = 0; i < 7; i++) { + stat_printf( + write_cb, + write_cb_arg, + "-----------" + ); + } + stat_printf( + write_cb, + write_cb_arg, + "\ntotal free heap space: %zu\n", + total_heap_space - total_allocated_heap_space + ); + } else { + print_amount_xml( + write_cb, + write_cb_arg, + "total_free_heap_space", + total_heap_space - total_allocated_heap_space + ); + } +} + +static void print_to_file(void *fp, const char *s) +{ + fputs(s, fp); +} + +static void add_stats(malloc_stats_t *destination, const malloc_stats_t *source) +{ + destination->total_allocated_memory += source->total_allocated_memory; + destination->total_mmapped_memory += source->total_mmapped_memory; + destination->mmapped_regions += source->mmapped_regions; + destination->total_allocated_heap_space += source->total_allocated_heap_space; +} +#endif + +int malloc_info(int options, FILE* fp) +{ +#ifdef MUSL_ITERATE_AND_STATS_API + if (options != 0) { + errno = EINVAL; + return -1; + } + malloc_disable(); + stat_printf(print_to_file, fp, "\n"); + stat_printf(print_to_file, fp, "\n", ALLOCATOR_VERSION); + stat_printf(print_to_file, fp, "\n"); + size_t total_allocated_heap_space = print_threads(print_to_file, fp, XML); + stat_printf(print_to_file, fp, "\n"); + total_allocated_heap_space += print_abandoned(print_to_file, fp, XML); + print_total_free_heap_space(print_to_file, fp, total_allocated_heap_space, XML); + stat_printf(print_to_file, fp, "\n"); + malloc_enable(); +#endif + return 0; +} + +void malloc_stats_print(void (*write_cb) (void *, const char *), void *cbopaque, const char *opts) +{ +#ifdef MUSL_ITERATE_AND_STATS_API + malloc_disable(); + stat_printf( + write_cb, + cbopaque, + "%-11s %-23s %-20s %-20s\n", + "thread_id", + "total_allocated_memory", + "total_mmapped_memory", + "mmapped_regions" + ); + size_t total_allocated_heap_space = print_threads(write_cb, cbopaque, TABLE); + total_allocated_heap_space += print_abandoned(write_cb, cbopaque, TABLE); + print_total_free_heap_space(write_cb, cbopaque, total_allocated_heap_space, TABLE); + malloc_enable(); +#endif +} + +struct mallinfo2 mallinfo2(void) +{ +#ifdef MUSL_ITERATE_AND_STATS_API + malloc_disable(); + malloc_stats_t shared_stats = {0, 0, 0, 0}; + struct __pthread *self, *it; + self = it = __pthread_self(); + do { + malloc_stats_t stats = add_up_chunks(__get_occupied_bin(it)); + add_stats(&shared_stats, &stats); + it = it->next; + } while (it != self); + malloc_stats_t abandoned_stats = add_up_chunks(&detached_occupied_bin); + add_stats(&shared_stats, &abandoned_stats); + + struct mallinfo2 res = { + .hblks = shared_stats.mmapped_regions, + .hblkhd = shared_stats.total_mmapped_memory, + .uordblks = shared_stats.total_allocated_memory, + .fordblks = total_heap_space - shared_stats.total_allocated_heap_space + }; + malloc_enable(); + return res; +#endif + return (struct mallinfo2){}; +} + +struct mallinfo mallinfo(void) +{ + struct mallinfo2 mallinfo2_res = mallinfo2(); + return (struct mallinfo) { + .hblks = (int) mallinfo2_res.hblks, + .hblkhd = (int) mallinfo2_res.hblkhd, + .uordblks = (int) mallinfo2_res.uordblks, + .fordblks = (int) mallinfo2_res.fordblks, + }; +} \ No newline at end of file diff --git a/porting/linux/user/src/thread/pthread_create.c b/porting/linux/user/src/thread/pthread_create.c index 7e20ecb07fd46576ae158a643016afdb15c14274..86f83a54412a7ee6cff21781d0a38a389d8775c6 100644 --- a/porting/linux/user/src/thread/pthread_create.c +++ b/porting/linux/user/src/thread/pthread_create.c @@ -4,12 +4,18 @@ #include "stdio_impl.h" #include "libc.h" #include "lock.h" +#include "malloc_impl.h" #include #include #include #include #include +#ifdef MUSL_ITERATE_AND_STATS_API +extern pthread_key_t occupied_bin_key; +extern occupied_bin_t detached_occupied_bin; +#endif + void log_print(const char* info,...) { va_list ap; @@ -145,8 +151,6 @@ _Noreturn void __pthread_exit(void *result) f(x); } - __pthread_tsd_run_dtors(); - /* Access to target the exiting thread with syscalls that use * its kernel tid is controlled by killlock. For detached threads, * any use past this point would have undefined behavior, but for @@ -158,6 +162,13 @@ _Noreturn void __pthread_exit(void *result) __block_app_sigs(&set); __tl_lock(); +#ifdef MUSL_ITERATE_AND_STATS_API + occupied_bin_t *self_tsd = __get_occupied_bin(self); + __merge_bin_chunks(&detached_occupied_bin, self_tsd); +#endif + __pthread_tsd_run_dtors(); + + #ifdef RESERVE_SIGNAL_STACK __pthread_release_signal_stack(); #endif @@ -400,6 +411,15 @@ int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict att new->CANARY = self->CANARY; new->sysinfo = self->sysinfo; +#ifdef MUSL_ITERATE_AND_STATS_API + /* Initialize malloc tsd */ + __init_occupied_bin_key_once(); + occupied_bin_t *occupied_bin = internal_calloc(sizeof(occupied_bin_t), 1); + if (occupied_bin == NULL) goto fail; + new->tsd[occupied_bin_key] = occupied_bin; + new->tsd_used = 1; +#endif + /* Setup argument structure for the new thread on its stack. * It's safe to access from the caller only until the thread * list is unlocked. */ diff --git a/porting/linux/user/src/thread/pthread_getspecific.c b/porting/linux/user/src/thread/pthread_getspecific.c new file mode 100644 index 0000000000000000000000000000000000000000..d9342a560f7bc6c9341b27911ae353d03a78fb1f --- /dev/null +++ b/porting/linux/user/src/thread/pthread_getspecific.c @@ -0,0 +1,11 @@ +#include "pthread_impl.h" +#include + +static void *__pthread_getspecific(pthread_key_t k) +{ + struct pthread *self = __pthread_self(); + return self->tsd[k]; +} + +weak_alias(__pthread_getspecific, pthread_getspecific); +weak_alias(__pthread_getspecific, tss_get); diff --git a/porting/linux/user/src/thread/pthread_setspecific.c b/porting/linux/user/src/thread/pthread_setspecific.c new file mode 100644 index 0000000000000000000000000000000000000000..55e46a899378f4f896e570ad8edbacbe569cbcf8 --- /dev/null +++ b/porting/linux/user/src/thread/pthread_setspecific.c @@ -0,0 +1,12 @@ +#include "pthread_impl.h" + +int pthread_setspecific(pthread_key_t k, const void *x) +{ + struct pthread *self = __pthread_self(); + /* Avoid unnecessary COW */ + if (self->tsd[k] != x) { + self->tsd[k] = (void *)x; + self->tsd_used = 1; + } + return 0; +}