Add some test cases to test the function and performance of some kallsyms interfaces, such as kallsyms_lookup_name. It also calculates the compression rate of the kallsyms compression algorithm for the current symbol set. Start self-test automatically after system startup. Example of output content: (prefix 'kallsyms_selftest:' is omitted) start There are 174101 symbols in total: -------------------------------------------------------------- | | compressed size | original size | ratio(%) | |--------------------------------------------------------------| | no '\0' | 1785569 | 3750649 | 47.60 | | with '\0' | 1959670 | 3924750 | 49.93 | -------------------------------------------------------------- kallsyms_lookup_name() looked up 174101 symbols The time spent on each symbol is (ns): min=5350, max=985150, avg=295517 kallsyms_on_each_symbol() lookup vmap: 15806120 ns kallsyms_on_each_symbol() traverse vmap: 15817140 ns kallsyms_on_each_match_symbol() lookup vmap: 32840 ns kallsyms_on_each_match_symbol() traverse vmap: 567370 ns Signed-off-by: Zhen Lei <thunder.leizhen@xxxxxxxxxx> --- init/Kconfig | 13 ++ kernel/Makefile | 1 + kernel/kallsyms_selftest.c | 243 +++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 kernel/kallsyms_selftest.c diff --git a/init/Kconfig b/init/Kconfig index 532362fcfe31fd3..2fcace3b9f063bf 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1716,6 +1716,19 @@ config KALLSYMS symbolic stack backtraces. This increases the size of the kernel somewhat, as all symbols have to be loaded into the kernel image. +config KALLSYMS_SELFTEST + bool "Test the function and performance of some interfaces in kallsyms" + depends on KALLSYMS + default n + help + Test the function and performance of some interfaces, such as + kallsyms_lookup_name. It also calculates the compression rate of the + kallsyms compression algorithm for the current symbol set. + + Start self-test automatically after system startup. Suggest executing + "dmesg | grep kallsyms_selftest" to collect test results. "finish" is + displayed in the last line, indicating that the test is complete. + config KALLSYMS_ALL bool "Include all symbols in kallsyms" depends on DEBUG_KERNEL && KALLSYMS diff --git a/kernel/Makefile b/kernel/Makefile index 318789c728d3290..122a5fed457bd98 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -68,6 +68,7 @@ endif obj-$(CONFIG_UID16) += uid16.o obj-$(CONFIG_MODULE_SIG_FORMAT) += module_signature.o obj-$(CONFIG_KALLSYMS) += kallsyms.o +obj-$(CONFIG_KALLSYMS_SELFTEST) += kallsyms_selftest.o obj-$(CONFIG_BSD_PROCESS_ACCT) += acct.o obj-$(CONFIG_CRASH_CORE) += crash_core.o obj-$(CONFIG_KEXEC_CORE) += kexec_core.o diff --git a/kernel/kallsyms_selftest.c b/kernel/kallsyms_selftest.c new file mode 100644 index 000000000000000..d89d6b2f5dd763e --- /dev/null +++ b/kernel/kallsyms_selftest.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test the function and performance of kallsyms + * + * Copyright (C) Huawei Technologies Co., Ltd., 2022 + * + * Authors: Zhen Lei <thunder.leizhen@xxxxxxxxxx> Huawei + */ + +#define pr_fmt(fmt) "kallsyms_selftest: " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kallsyms.h> +#include <linux/kthread.h> +#include <linux/sched/clock.h> +#include <linux/vmalloc.h> + +#include "kallsyms_internal.h" + + +struct test_stat { + int min; + int max; + int cnt; + u64 sum; + unsigned long addr; +}; + + +static int match_symbol(void *data, unsigned long addr) +{ + struct test_stat *stat = (struct test_stat *)data; + + stat->cnt++; + stat->addr = addr; + + if (stat->cnt == stat->max) + return 1; + + return 0; +} + +static void test_kallsyms_on_each_match_symbol(void) +{ + u64 t0, t1; + unsigned long flags; + struct test_stat stat; + + stat.cnt = 0; + stat.max = 1; + stat.addr = 0; + local_irq_save(flags); + t0 = sched_clock(); + kallsyms_on_each_match_symbol(match_symbol, "vmap", &stat); + t1 = sched_clock(); + local_irq_restore(flags); + if (stat.addr != (unsigned long)vmap || stat.cnt != 1) { + pr_info("kallsyms_on_each_match_symbol() test failed\n"); + return; + } + pr_info("kallsyms_on_each_match_symbol() lookup vmap: %lld ns\n", t1 - t0); + + stat.cnt = 0; + stat.max = INT_MAX; + stat.addr = 0; + local_irq_save(flags); + t0 = sched_clock(); + kallsyms_on_each_match_symbol(match_symbol, "vmap", &stat); + t1 = sched_clock(); + local_irq_restore(flags); + if (stat.addr != (unsigned long)vmap || stat.cnt != 1) { + pr_info("kallsyms_on_each_match_symbol() test failed\n"); + return; + } + pr_info("kallsyms_on_each_match_symbol() traverse vmap: %lld ns\n", t1 - t0); +} + +static int find_symbol(void *data, const char *name, + struct module *mod, unsigned long addr) +{ + struct test_stat *stat = (struct test_stat *)data; + + if (strcmp(name, "vmap") == 0) { + stat->cnt++; + stat->addr = addr; + } + + if (stat->cnt == stat->max) + return 1; + + return 0; +} + +static void test_kallsyms_on_each_symbol(void) +{ + u64 t0, t1; + unsigned long flags; + struct test_stat stat; + + stat.cnt = 0; + stat.sum = 1; + stat.addr = 0; + local_irq_save(flags); + t0 = sched_clock(); + kallsyms_on_each_symbol(find_symbol, &stat); + t1 = sched_clock(); + local_irq_restore(flags); + if (stat.addr != (unsigned long)vmap || stat.cnt != 1) { + pr_info("kallsyms_on_each_symbol() test failed\n"); + return; + } + pr_info("kallsyms_on_each_symbol() lookup vmap: %lld ns\n", t1 - t0); + + stat.cnt = 0; + stat.max = INT_MAX; + stat.addr = 0; + local_irq_save(flags); + t0 = sched_clock(); + kallsyms_on_each_symbol(find_symbol, &stat); + t1 = sched_clock(); + local_irq_restore(flags); + if (stat.addr != (unsigned long)vmap || stat.cnt != 1) { + pr_info("kallsyms_on_each_symbol() test failed\n"); + return; + } + pr_info("kallsyms_on_each_symbol() traverse vmap: %lld ns\n", t1 - t0); +} + +static int lookup_name(void *data, const char *name, struct module *mod, unsigned long addr) +{ + u64 t0, t1, t; + unsigned long flags; + struct test_stat *stat = (struct test_stat *)data; + + local_irq_save(flags); + t0 = sched_clock(); + (void)kallsyms_lookup_name(name); + t1 = sched_clock(); + local_irq_restore(flags); + + t = t1 - t0; + if (t < stat->min) + stat->min = t; + + if (t > stat->max) + stat->max = t; + + stat->cnt++; + stat->sum += t; + + return 0; +} + +static void test_kallsyms_lookup_name(void) +{ + struct test_stat stat; + + stat.min = INT_MAX; + stat.max = 0; + stat.cnt = 0; + stat.sum = 0; + kallsyms_on_each_symbol(lookup_name, &stat); + pr_info("kallsyms_lookup_name() looked up %d symbols\n", stat.cnt); + pr_info("The time spent on each symbol is (ns): min=%d, max=%d, avg=%lld\n", + stat.min, stat.max, stat.sum / stat.cnt); + + stat.addr = kallsyms_lookup_name("vmap"); + if (stat.addr != (unsigned long)vmap) + pr_info("kallsyms_lookup_name() test failed\n"); +} + +static int stat_symbol_len(void *data, const char *name, + struct module *mod, unsigned long addr) +{ + *(u32 *)data += strlen(name); + + return 0; +} + +static void test_kallsyms_compression_ratio(void) +{ + int i; + const u8 *name; + u32 pos; + u32 ratio, total_size, total_len = 0; + + kallsyms_on_each_symbol(stat_symbol_len, &total_len); + + pos = kallsyms_num_syms - 1; + name = &kallsyms_names[kallsyms_markers[pos >> 8]]; + for (i = 0; i <= (pos & 0xff); i++) + name = name + (*name) + 1; + + /* The length and string terminator are not counted */ + total_size = (name - kallsyms_names) - (kallsyms_num_syms * 2); + pr_info("There are %d symbols in total:\n", kallsyms_num_syms); + pr_info(" --------------------------------------------------------------\n"); + pr_info("| | compressed size | original size | ratio(%%) |\n"); + pr_info("|--------------------------------------------------------------|\n"); + ratio = 10000ULL * total_size / total_len; + pr_info("| no '\\0' | %10d | %10d | %2d.%-2d |\n", + total_size, total_len, ratio / 100, ratio % 100); + total_size += kallsyms_num_syms; + total_len += kallsyms_num_syms; + ratio = 10000ULL * total_size / total_len; + pr_info("| with '\\0' | %10d | %10d | %2d.%-2d |\n", + total_size, total_len, ratio / 100, ratio % 100); + pr_info(" --------------------------------------------------------------\n"); + pr_info("\n"); +} + +static int test_entry(void *p) +{ + do { + schedule_timeout(5 * HZ); + } while (system_state != SYSTEM_RUNNING); + + pr_info("start\n"); + test_kallsyms_compression_ratio(); + test_kallsyms_lookup_name(); + test_kallsyms_on_each_symbol(); + test_kallsyms_on_each_match_symbol(); + pr_info("finish\n"); + + return 0; +} + +static int __init kallsyms_test_init(void) +{ + struct task_struct *t; + + t = kthread_create(test_entry, NULL, "kallsyms_test"); + if (IS_ERR(t)) { + pr_info("Create kallsyms selftest task failed\n"); + return PTR_ERR(t); + } + kthread_bind(t, 0); + wake_up_process(t); + + return 0; +} +late_initcall(kallsyms_test_init); -- 2.25.1