From fad4a05967ad711360b32a9b2c51e7be45b3317e Mon Sep 17 00:00:00 2001 From: sosnin-vladimir Date: Tue, 11 Oct 2022 01:43:58 +0300 Subject: [PATCH] Add tests for malloc_info, malloc_stats_print, mallinfo2, malloc_iterate, malloc_enable, malloc_disable, mallopt, malloc_backtrace. Implemented by Olefirenko Egor, Polyakov Maxim, Frolikov Boris Signed-off-by: sosnin-vladimir --- libc-test/src/functional/BUILD.gn | 4 + libc-test/src/functional/test-iterate.c | 194 ++++++++++++++ libc-test/src/functional/test-mallinfo.c | 122 +++++++++ .../src/functional/test-malloc-api-common.h | 33 +++ .../src/functional/test-malloc-backtrace.c | 27 ++ .../src/functional/test-malloc-disable.c | 72 +++++ .../functional/test-malloc-info-stats-print.h | 248 ++++++++++++++++++ libc-test/src/functional/test-malloc-info.c | 152 +++++++++++ .../src/functional/test-malloc-stats-common.h | 132 ++++++++++ .../src/functional/test-malloc-stats-print.c | 91 +++++++ libc-test/src/functional/test-mallopt.c | 27 ++ .../src/functional/test_src_functional.gni | 10 + libc-test/test_template.gni | 6 + 13 files changed, 1118 insertions(+) create mode 100644 libc-test/src/functional/test-iterate.c create mode 100644 libc-test/src/functional/test-mallinfo.c create mode 100644 libc-test/src/functional/test-malloc-api-common.h create mode 100644 libc-test/src/functional/test-malloc-backtrace.c create mode 100644 libc-test/src/functional/test-malloc-disable.c create mode 100644 libc-test/src/functional/test-malloc-info-stats-print.h create mode 100644 libc-test/src/functional/test-malloc-info.c create mode 100644 libc-test/src/functional/test-malloc-stats-common.h create mode 100644 libc-test/src/functional/test-malloc-stats-print.c create mode 100644 libc-test/src/functional/test-mallopt.c diff --git a/libc-test/src/functional/BUILD.gn b/libc-test/src/functional/BUILD.gn index 902ea979..77bf4678 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 00000000..d64bee06 --- /dev/null +++ b/libc-test/src/functional/test-iterate.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include +#include +#include +#include + +#include "test-malloc-api-common.h" + +#define BARRIER_HEIGHT 2 +#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, + 2 * 1024, + 64 * 1024, + 512 * 1024, + 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) { + ret = -1; + } + } + 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, BARRIER_HEIGHT); + pthread_barrier_init(&routine_iterated, NULL, BARRIER_HEIGHT); + 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 00000000..edf24075 --- /dev/null +++ b/libc-test/src/functional/test-mallinfo.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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 00000000..0fac1284 --- /dev/null +++ b/libc-test/src/functional/test-malloc-api-common.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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 00000000..e8802e9f --- /dev/null +++ b/libc-test/src/functional/test-malloc-backtrace.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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 00000000..4e153001 --- /dev/null +++ b/libc-test/src/functional/test-malloc-disable.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "test-malloc-api-common.h" + +#define BARRIER_HEIGHT 2 + +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, BARRIER_HEIGHT); + pthread_barrier_init(&routine_allocated, NULL, BARRIER_HEIGHT); + 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) { + 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 00000000..73d383bf --- /dev/null +++ b/libc-test/src/functional/test-malloc-info-stats-print.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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 STATS_BUFFER_SIZE 4096 + +typedef struct { + char stats_after_allocations[STATS_BUFFER_SIZE]; + char stats_after_free[STATS_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 *output, const char *thread_id, malloc_thread_stats_t *stats); + +static int populate_total_free_heap_space(const char *output, long long *total_free_heap_space); + +static int is_thread_in_output(const char *output, const char *thread_id); + +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, STATS_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 (is_thread_in_output(test_results->stats_after_free, test_results->threads[i])) { + 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 00000000..fa4505ae --- /dev/null +++ b/libc-test/src/functional/test-malloc-info.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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 xmlNodePtr find_thread_in_document(xmlDocPtr doc_ptr, const char *thread_id) +{ + xmlNodePtr root_element = xmlDocGetRootElement(doc_ptr); + if (strcmp(thread_id, "abandoned") == 0) { + return find_child_node("abandoned", root_element); + } + return find_child_node_with_attr("thread", "id", thread_id, find_child_node("threads", root_element)); +} + +static int populate_thread_stats(const char *output, const char *thread_id, malloc_thread_stats_t *stats) +{ + xmlDocPtr doc_ptr = xmlParseDoc((const xmlChar *) output); + if (doc_ptr == NULL) { + return 0; + } + xmlNodePtr thread_root = find_thread_in_document(doc_ptr, thread_id); + 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 *output, long long *total_free_heap_space) +{ + xmlDocPtr doc_ptr = xmlParseDoc((const xmlChar *) output); + 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; +} + +static int is_thread_in_output(const char *output, const char *thread_id) +{ + xmlDocPtr doc_ptr = xmlParseDoc((const xmlChar *) output); + if (doc_ptr == NULL) { + return 0; + } + int result = find_thread_in_document(doc_ptr, thread_id) != NULL; + xmlFreeDoc(doc_ptr); + return result; +} 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 00000000..cfdbc0c7 --- /dev/null +++ b/libc-test/src/functional/test-malloc-stats-common.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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 00000000..a93ea406 --- /dev/null +++ b/libc-test/src/functional/test-malloc-stats-print.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "test-malloc-info-stats-print.h" + +#define THREAD_DATA_REGEX_LEN (MAX_TID_LEN + 43) +#define REGEX_NMATCH 1 + +static void stderr_stats_cb(void) +{ + malloc_stats_print(print_to_file, stderr, ""); +} + +static int parse_amount(const 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 const char *find_thread_in_output(const char *output, const char *thread_id) +{ + char thread_data_regex_s[THREAD_DATA_REGEX_LEN + 1]; + snprintf(thread_data_regex_s, THREAD_DATA_REGEX_LEN, "^%s([[:space:]]+[[:digit:]]+){3}[[:space:]]*$", thread_id); + regex_t thread_data_regex; + if (regcomp(&thread_data_regex, thread_data_regex_s, REG_EXTENDED | REG_NEWLINE) != 0) { + t_error("Failed to compile regex %s", thread_data_regex_s); + return NULL; + } + + regmatch_t pmatch[REGEX_NMATCH]; + int match_result = regexec(&thread_data_regex, output, REGEX_NMATCH, pmatch, 0); + regfree(&thread_data_regex); + if (match_result != 0) { + return NULL; + } + return output + pmatch[0].rm_so; +} + +static int populate_thread_stats(const char *output, const char *thread_id, malloc_thread_stats_t *stats) +{ + const char *thread_data_start = find_thread_in_output(output, thread_id); + if (thread_data_start == NULL) { + t_error("Failed to find thread id %s in output", thread_id); + return 0; + } + + thread_data_start += strlen(thread_id); + int result = 1; + result &= parse_amount(&thread_data_start, &stats->total_allocated_memory); + result &= parse_amount(&thread_data_start, &stats->total_mmapped_memory); + result &= parse_amount(&thread_data_start, &stats->mmapped_regions); + + return result; +} + +static int populate_total_free_heap_space(const char *output, long long *total_free_heap_space) +{ + const char *free_heap_space_start = strstr(output, "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); +} + +static int is_thread_in_output(const char *output, const char *thread_id) +{ + return find_thread_in_output(output, thread_id) != NULL; +} \ No newline at end of file diff --git a/libc-test/src/functional/test-mallopt.c b/libc-test/src/functional/test-mallopt.c new file mode 100644 index 00000000..4a9e5c42 --- /dev/null +++ b/libc-test/src/functional/test-mallopt.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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 85d07543..d968f6cf 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 c0b2061e..f9f6fc6d 100644 --- a/libc-test/test_template.gni +++ b/libc-test/test_template.gni @@ -130,6 +130,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") { -- GitLab