diff mbox series

[v5,4/4] Kselftest for module text allocation benchmarking

Message ID 1536792940-8294-5-git-send-email-rick.p.edgecombe@intel.com (mailing list archive)
State New, archived
Headers show
Series KASLR feature to randomize each loadable module | expand

Commit Message

Rick Edgecombe Sept. 12, 2018, 10:55 p.m. UTC
This adds a test module in lib/, and a script in kselftest that does
benchmarking on the allocation of memory in the module space. Performance here
would have some small impact on kernel module insertions, BPF JIT insertions
and kprobes. In the case of KASLR features for the module space, this module
can be used to measure the allocation performance of different configurations.
This module needs to be compiled into the kernel because module_alloc is not
exported.

With some modification to the code, as explained in the comments, it can be
enabled to measure TLB flushes as well.

There are two tests in the module. One allocates until failure in order to
test module capacity and the other times allocating space in the module area.
They both use module sizes that roughly approximate the distribution of in-tree
X86_64 modules.

You can control the number of modules used in the tests like this:
echo m1000>/dev/mod_alloc_test

Run the test for module capacity like:
echo t1>/dev/mod_alloc_test

The other test will measure the allocation time, and for CONFG_X86_64 and
CONFIG_RANDOMIZE_BASE, also give data on how often the “backup area" is used.

Run the test for allocation time and backup area usage like:
echo t2>/dev/mod_alloc_test
The output will be something like this:
num		all(ns)		last(ns)
1000		1083		1099
Last module in backup count = 0
Total modules in backup     = 0
>1 module in backup count   = 0

To run a suite of allocation time tests for a collection of module numbers you can run:
tools/testing/selftests/bpf/test_mod_alloc.sh

Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
 lib/Kconfig.debug                             |  10 +
 lib/Makefile                                  |   1 +
 lib/test_mod_alloc.c                          | 446 ++++++++++++++++++++++++++
 tools/testing/selftests/bpf/test_mod_alloc.sh |  29 ++
 4 files changed, 486 insertions(+)
 create mode 100644 lib/test_mod_alloc.c
 create mode 100755 tools/testing/selftests/bpf/test_mod_alloc.sh

Comments

kernel test robot Sept. 13, 2018, 10:36 a.m. UTC | #1
Hi Rick,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on linus/master]
[also build test ERROR on v4.19-rc3 next-20180913]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Rick-Edgecombe/KASLR-feature-to-randomize-each-loadable-module/20180913-172302
config: sh-allmodconfig (attached as .config)
compiler: sh4-linux-gnu-gcc (Debian 7.2.0-11) 7.2.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        GCC_VERSION=7.2.0 make.cross ARCH=sh 

All error/warnings (new ones prefixed by >>):

   lib/test_mod_alloc.c: In function 'do_check':
>> lib/test_mod_alloc.c:129:15: error: 'MODULES_VADDR' undeclared (first use in this function); did you mean 'MODULE_SOFTDEP'?
      if (start < MODULES_VADDR ||
                  ^~~~~~~~~~~~~
                  MODULE_SOFTDEP
   lib/test_mod_alloc.c:129:15: note: each undeclared identifier is reported only once for each function it appears in
>> lib/test_mod_alloc.c:130:26: error: 'MODULES_LEN' undeclared (first use in this function); did you mean 'MODULE_ALIGN'?
       end > MODULES_VADDR + MODULES_LEN) {
                             ^~~~~~~~~~~
                             MODULE_ALIGN
   lib/test_mod_alloc.c: In function 'device_file_write':
>> lib/test_mod_alloc.c:404:2: warning: ignoring return value of 'copy_from_user', declared with attribute warn_unused_result [-Wunused-result]
     copy_from_user(buf, user_buf, count);
     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> lib/test_mod_alloc.c:407:3: warning: ignoring return value of 'kstrtol', declared with attribute warn_unused_result [-Wunused-result]
      kstrtol(buf+1, 10, &new_mod_cnt);
      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   lib/test_mod_alloc.c:422:3: warning: ignoring return value of 'kstrtol', declared with attribute warn_unused_result [-Wunused-result]
      kstrtol(buf + 1, 10, &iter);
      ^~~~~~~~~~~~~~~~~~~~~~~~~~~

vim +129 lib/test_mod_alloc.c

    89	
    90	static int do_check(void *ptr, unsigned long size)
    91	{
    92		int i;
    93		unsigned long start = (unsigned long) ptr;
    94		unsigned long end = calc_end(ptr, size);
    95		unsigned long sum = 0;
    96		unsigned long addr;
    97	
    98		if (!start)
    99			return 1;
   100	
   101		for (i = 0; i < check_alloc_cnt; i++) {
   102			struct check_alloc *cur_alloc = &(check_allocs[i]);
   103	
   104			/* overlap end */
   105			if (start >= cur_alloc->start && start < cur_alloc->vm_end) {
   106				pr_info("overlap end\n");
   107				return 1;
   108			}
   109	
   110			/* overlap start */
   111			if (end >= cur_alloc->start && end < cur_alloc->start) {
   112				pr_info("overlap start\n");
   113				return 1;
   114			}
   115	
   116			/* overlap whole thing */
   117			if (start <= cur_alloc->start && end > cur_alloc->vm_end) {
   118				pr_info("overlap whole thing\n");
   119				return 1;
   120			}
   121	
   122			/* inside */
   123			if (start >= cur_alloc->start && end < cur_alloc->vm_end) {
   124				pr_info("inside\n");
   125				return 1;
   126			}
   127	
   128			/* bounds */
 > 129			if (start < MODULES_VADDR ||
 > 130				end > MODULES_VADDR + MODULES_LEN) {
   131				pr_info("out of bounds\n");
   132				return 1;
   133			}
   134			for (addr = cur_alloc->start;
   135				addr < cur_alloc->real_end;
   136				addr += PAGE_SIZE) {
   137				sum += *((unsigned long *) addr);
   138			}
   139			if (sum != 0)
   140				pr_info("Memory was not zeroed\n");
   141	
   142			kasan_check_read((void *)cur_alloc->start,
   143				cur_alloc->vm_end - cur_alloc->start - PAGE_SIZE);
   144		}
   145		return 0;
   146	}
   147	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
kernel test robot Sept. 13, 2018, 9:50 p.m. UTC | #2
Hi Rick,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on linus/master]
[also build test ERROR on v4.19-rc3 next-20180913]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Rick-Edgecombe/KASLR-feature-to-randomize-each-loadable-module/20180913-172302
config: openrisc-allyesconfig (attached as .config)
compiler: or1k-linux-gcc (GCC) 6.0.0 20160327 (experimental)
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=openrisc 

All errors (new ones prefixed by >>):

   lib/test_mod_alloc.c: In function 'do_check':
>> lib/test_mod_alloc.c:129:15: error: 'MODULES_VADDR' undeclared (first use in this function)
      if (start < MODULES_VADDR ||
                  ^~~~~~~~~~~~~
   lib/test_mod_alloc.c:129:15: note: each undeclared identifier is reported only once for each function it appears in
>> lib/test_mod_alloc.c:130:26: error: 'MODULES_LEN' undeclared (first use in this function)
       end > MODULES_VADDR + MODULES_LEN) {
                             ^~~~~~~~~~~
   lib/test_mod_alloc.c: In function 'device_file_write':
   lib/test_mod_alloc.c:404:2: warning: ignoring return value of 'copy_from_user', declared with attribute warn_unused_result [-Wunused-result]
     copy_from_user(buf, user_buf, count);
     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   lib/test_mod_alloc.c:407:3: warning: ignoring return value of 'kstrtol', declared with attribute warn_unused_result [-Wunused-result]
      kstrtol(buf+1, 10, &new_mod_cnt);
      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   lib/test_mod_alloc.c:422:3: warning: ignoring return value of 'kstrtol', declared with attribute warn_unused_result [-Wunused-result]
      kstrtol(buf + 1, 10, &iter);
      ^~~~~~~~~~~~~~~~~~~~~~~~~~~

vim +/MODULES_VADDR +129 lib/test_mod_alloc.c

    89	
    90	static int do_check(void *ptr, unsigned long size)
    91	{
    92		int i;
    93		unsigned long start = (unsigned long) ptr;
    94		unsigned long end = calc_end(ptr, size);
    95		unsigned long sum = 0;
    96		unsigned long addr;
    97	
    98		if (!start)
    99			return 1;
   100	
   101		for (i = 0; i < check_alloc_cnt; i++) {
   102			struct check_alloc *cur_alloc = &(check_allocs[i]);
   103	
   104			/* overlap end */
   105			if (start >= cur_alloc->start && start < cur_alloc->vm_end) {
   106				pr_info("overlap end\n");
   107				return 1;
   108			}
   109	
   110			/* overlap start */
   111			if (end >= cur_alloc->start && end < cur_alloc->start) {
   112				pr_info("overlap start\n");
   113				return 1;
   114			}
   115	
   116			/* overlap whole thing */
   117			if (start <= cur_alloc->start && end > cur_alloc->vm_end) {
   118				pr_info("overlap whole thing\n");
   119				return 1;
   120			}
   121	
   122			/* inside */
   123			if (start >= cur_alloc->start && end < cur_alloc->vm_end) {
   124				pr_info("inside\n");
   125				return 1;
   126			}
   127	
   128			/* bounds */
 > 129			if (start < MODULES_VADDR ||
 > 130				end > MODULES_VADDR + MODULES_LEN) {
   131				pr_info("out of bounds\n");
   132				return 1;
   133			}
   134			for (addr = cur_alloc->start;
   135				addr < cur_alloc->real_end;
   136				addr += PAGE_SIZE) {
   137				sum += *((unsigned long *) addr);
   138			}
   139			if (sum != 0)
   140				pr_info("Memory was not zeroed\n");
   141	
   142			kasan_check_read((void *)cur_alloc->start,
   143				cur_alloc->vm_end - cur_alloc->start - PAGE_SIZE);
   144		}
   145		return 0;
   146	}
   147	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox series

Patch

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 4966c4f..c6c147c 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1883,6 +1883,16 @@  config TEST_BPF
 
 	  If unsure, say N.
 
+config TEST_MOD_ALLOC
+	bool "Tests for module allocator/vmalloc"
+	help
+	  This builds the "test_mod_alloc" module that performs performance
+	  and functional tests on the module text section allocator. The module
+	  uses X86_64 module text sizes for simulations, for other architectures
+	  it will be less accurate.
+
+	  If unsure, say N.
+
 config FIND_BIT_BENCHMARK
 	tristate "Test find_bit functions"
 	help
diff --git a/lib/Makefile b/lib/Makefile
index ca3f7eb..3d5923e 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -58,6 +58,7 @@  UBSAN_SANITIZE_test_ubsan.o := y
 obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o
 obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o
 obj-$(CONFIG_TEST_LKM) += test_module.o
+obj-$(CONFIG_TEST_MOD_ALLOC) += test_mod_alloc.o
 obj-$(CONFIG_TEST_OVERFLOW) += test_overflow.o
 obj-$(CONFIG_TEST_RHASHTABLE) += test_rhashtable.o
 obj-$(CONFIG_TEST_SORT) += test_sort.o
diff --git a/lib/test_mod_alloc.c b/lib/test_mod_alloc.c
new file mode 100644
index 0000000..71c146e
--- /dev/null
+++ b/lib/test_mod_alloc.c
@@ -0,0 +1,446 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/kasan.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/moduleloader.h>
+#include <linux/random.h>
+#include <linux/uaccess.h>
+
+#include <linux/vmalloc.h>
+
+struct mod { int filesize; int coresize; int initsize; };
+
+/* ==== Begin optional logging ==== */
+
+/*
+ * Note: for more accurate test results add this to mm/vmalloc.c:
+ * void debug_purge_vmap_area_lazy(void)
+ * {
+ *	purge_vmap_area_lazy();
+ * }
+ * and replace the below with:
+ * extern void debug_purge_vmap_area_lazy(void);
+ */
+static void debug_purge_vmap_area_lazy(void)
+{
+}
+
+
+/*
+ * Note: In order to get an accurate count for the tlb flushes triggered in
+ * vmalloc, create a counter in vmalloc.c: with this method signature and export
+ * it. Then replace the below with: __purge_vmap_area_lazy
+ * extern unsigned long get_tlb_flushes_vmalloc(void);
+ */
+static unsigned long get_tlb_flushes_vmalloc(void)
+{
+	return 0;
+}
+
+/* ==== End optional logging ==== */
+
+
+#define MAX_ALLOC_CNT 20000
+#define ITERS 1000
+
+struct vm_alloc {
+	void *core;
+	unsigned long core_size;
+	void *init;
+};
+
+struct check_alloc {
+	unsigned long start;
+	unsigned long vm_end;
+	unsigned long real_end;
+};
+
+static struct vm_alloc *allocs_vm;
+
+static struct check_alloc *check_allocs;
+static int check_alloc_cnt;
+
+static long mod_cnt = 100;
+
+/* This may be different for non-x86 */
+static unsigned long calc_end(void *start, unsigned long size)
+{
+	unsigned long startl = (unsigned long) start;
+
+	return startl + PAGE_ALIGN(size) + PAGE_SIZE;
+}
+
+static void reset_cur_allocs(void)
+{
+	check_alloc_cnt = 0;
+}
+
+static void add_check_alloc(void *start, unsigned long size)
+{
+	check_allocs[check_alloc_cnt].start = (unsigned long) start;
+	check_allocs[check_alloc_cnt].vm_end = calc_end(start, size);
+	check_allocs[check_alloc_cnt].real_end = size;
+	check_alloc_cnt++;
+}
+
+static int do_check(void *ptr, unsigned long size)
+{
+	int i;
+	unsigned long start = (unsigned long) ptr;
+	unsigned long end = calc_end(ptr, size);
+	unsigned long sum = 0;
+	unsigned long addr;
+
+	if (!start)
+		return 1;
+
+	for (i = 0; i < check_alloc_cnt; i++) {
+		struct check_alloc *cur_alloc = &(check_allocs[i]);
+
+		/* overlap end */
+		if (start >= cur_alloc->start && start < cur_alloc->vm_end) {
+			pr_info("overlap end\n");
+			return 1;
+		}
+
+		/* overlap start */
+		if (end >= cur_alloc->start && end < cur_alloc->start) {
+			pr_info("overlap start\n");
+			return 1;
+		}
+
+		/* overlap whole thing */
+		if (start <= cur_alloc->start && end > cur_alloc->vm_end) {
+			pr_info("overlap whole thing\n");
+			return 1;
+		}
+
+		/* inside */
+		if (start >= cur_alloc->start && end < cur_alloc->vm_end) {
+			pr_info("inside\n");
+			return 1;
+		}
+
+		/* bounds */
+		if (start < MODULES_VADDR ||
+			end > MODULES_VADDR + MODULES_LEN) {
+			pr_info("out of bounds\n");
+			return 1;
+		}
+		for (addr = cur_alloc->start;
+			addr < cur_alloc->real_end;
+			addr += PAGE_SIZE) {
+			sum += *((unsigned long *) addr);
+		}
+		if (sum != 0)
+			pr_info("Memory was not zeroed\n");
+
+		kasan_check_read((void *)cur_alloc->start,
+			cur_alloc->vm_end - cur_alloc->start - PAGE_SIZE);
+	}
+	return 0;
+}
+
+
+const static int core_hist[10] = {1, 5, 21, 46, 141, 245, 597, 2224, 1875, 0};
+const static int init_hist[10] = {0, 0, 0, 0, 10, 19, 70, 914, 3906, 236};
+const static int file_hist[10] = {6, 20, 55, 86, 286, 551, 918, 2024, 1028,
+					181};
+
+const static int bins[10] = {5000000, 2000000, 1000000, 500000, 200000, 100000,
+		50000, 20000, 10000, 5000};
+/*
+ * Rough approximation of the X86_64 module size distribution.
+ */
+static int get_mod_rand_size(const int *hist)
+{
+	int area_under = get_random_long() % 5155;
+	int i;
+	int last_bin = bins[0] + 1;
+	int sum = 0;
+
+	for (i = 0; i <= 9; i++) {
+		sum += hist[i];
+		if (area_under <= sum)
+			return bins[i]
+				+ (get_random_long() % (last_bin - bins[i]));
+		last_bin = bins[i];
+	}
+	return 4096;
+}
+
+static struct mod get_rand_module(void)
+{
+	struct mod ret;
+
+	ret.coresize = get_mod_rand_size(core_hist);
+	ret.initsize = get_mod_rand_size(init_hist);
+	ret.filesize = get_mod_rand_size(file_hist);
+	return ret;
+}
+
+static void do_test_alloc_fail(void)
+{
+	struct vm_alloc *cur_alloc;
+	struct mod cur_mod;
+	void *file;
+	int mod_n, free_mod_n;
+	unsigned long fail = 0;
+	int iter;
+
+	for (iter = 0; iter < ITERS; iter++) {
+		pr_info("Running iteration: %d\n", iter);
+		memset(allocs_vm, 0, mod_cnt * sizeof(struct vm_alloc));
+		reset_cur_allocs();
+		debug_purge_vmap_area_lazy();
+		for (mod_n = 0; mod_n < mod_cnt; mod_n++) {
+			cur_mod = get_rand_module();
+			cur_alloc = &allocs_vm[mod_n];
+
+			/* Allocate */
+			file = vmalloc(cur_mod.filesize);
+			cur_alloc->core = module_alloc(cur_mod.coresize);
+
+			/* Check core allocation postion is good */
+			if (do_check(cur_alloc->core, cur_mod.coresize)) {
+				pr_info("Check failed core:%d\n", mod_n);
+				break;
+			}
+			/* Add core position for future checking */
+			add_check_alloc(cur_alloc->core, cur_mod.coresize);
+
+			cur_alloc->init = module_alloc(cur_mod.initsize);
+
+			/* Check init position */
+			if (do_check(cur_alloc->init, cur_mod.initsize)) {
+				pr_info("Check failed init:%d\n", mod_n);
+				break;
+			}
+
+			/* Clean up everything except core */
+			if (!cur_alloc->core || !cur_alloc->init) {
+				fail++;
+				vfree(file);
+				if (cur_alloc->init)
+					vfree(cur_alloc->init);
+				break;
+			}
+			vfree(cur_alloc->init);
+			vfree(file);
+		}
+
+		/* Clean up core sizes */
+		for (free_mod_n = 0; free_mod_n < mod_n; free_mod_n++) {
+			cur_alloc = &allocs_vm[free_mod_n];
+			if (cur_alloc->core)
+				vfree(cur_alloc->core);
+		}
+	}
+	pr_info("Failures(%ld modules):%lu\n", mod_cnt, fail);
+}
+
+#if defined(CONFIG_X86_64) && defined(CONFIG_RANDOMIZE_BASE)
+static int is_in_backup(void *addr)
+{
+	return (unsigned long)addr >= MODULES_VADDR + MODULES_RAND_LEN;
+}
+#else
+static int is_in_backup(void *addr)
+{
+	return 0;
+}
+#endif
+
+static void do_test_last_perf(void)
+{
+	struct vm_alloc *cur_alloc;
+	struct mod cur_mod;
+	void *file;
+	int mod_n, mon_n_free;
+	unsigned long fail = 0;
+	int iter;
+	ktime_t start, diff;
+	ktime_t total_last = 0;
+	ktime_t total_all = 0;
+
+	/*
+	 * The number of last core allocations for each iteration that were
+	 * allocated in the backup area.
+	 */
+	int last_in_bk = 0;
+
+	/*
+	 * The total number of core allocations that were in the backup area for
+	 * all iterations.
+	 */
+	int total_in_bk = 0;
+
+	/* The number of iterations where the count was more than 1 */
+	int cnt_more_than_1 = 0;
+
+	/*
+	 * The number of core allocations that were in the backup area for the
+	 * current iteration.
+	 */
+	int cur_in_bk = 0;
+
+	unsigned long before_tlbs;
+	unsigned long tlb_cnt_total;
+	unsigned long tlb_cur;
+	unsigned long total_tlbs = 0;
+
+	pr_info("Starting %d iterations of %ld modules\n", ITERS, mod_cnt);
+
+	for (iter = 0; iter < ITERS; iter++) {
+		debug_purge_vmap_area_lazy();
+		before_tlbs = get_tlb_flushes_vmalloc();
+		memset(allocs_vm, 0, mod_cnt * sizeof(struct vm_alloc));
+		tlb_cnt_total = 0;
+		cur_in_bk = 0;
+		for (mod_n = 0; mod_n < mod_cnt; mod_n++) {
+			/* allocate how the module allocator allocates */
+
+			cur_mod = get_rand_module();
+			cur_alloc = &allocs_vm[mod_n];
+			file = vmalloc(cur_mod.filesize);
+
+			tlb_cur = get_tlb_flushes_vmalloc();
+
+			start = ktime_get();
+			cur_alloc->core = module_alloc(cur_mod.coresize);
+			diff = ktime_get() - start;
+
+			cur_alloc->init = module_alloc(cur_mod.initsize);
+
+			/* Collect metrics */
+			if (is_in_backup(cur_alloc->core)) {
+				cur_in_bk++;
+				if (mod_n == mod_cnt - 1)
+					last_in_bk++;
+			}
+			total_all += diff;
+
+			if (mod_n == mod_cnt - 1)
+				total_last += diff;
+
+			tlb_cnt_total += get_tlb_flushes_vmalloc() - tlb_cur;
+
+			/* If there is a failure, quit. init/core freed later */
+			if (!cur_alloc->core || !cur_alloc->init) {
+				fail++;
+				vfree(file);
+				break;
+			}
+			/* Init sections do not last long so free here */
+			vfree(cur_alloc->init);
+			cur_alloc->init = NULL;
+			vfree(file);
+		}
+
+		/* Collect per iteration metrics */
+		total_in_bk += cur_in_bk;
+		if (cur_in_bk > 1)
+			cnt_more_than_1++;
+		total_tlbs += get_tlb_flushes_vmalloc() - before_tlbs;
+
+		/* Collect per iteration metrics */
+		for (mon_n_free = 0; mon_n_free < mod_cnt; mon_n_free++) {
+			cur_alloc = &allocs_vm[mon_n_free];
+			vfree(cur_alloc->init);
+			vfree(cur_alloc->core);
+		}
+	}
+
+	if (fail)
+		pr_info("There was an alloc failure, results invalid!\n");
+
+	pr_info("num\t\tall(ns)\t\tlast(ns)");
+	pr_info("%ld\t\t%llu\t\t%llu\n", mod_cnt,
+					total_all / (ITERS * mod_cnt),
+					total_last / ITERS);
+
+	if (IS_ENABLED(CONFIG_X86_64) && IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
+		pr_info("Last module in backup count = %d\n", last_in_bk);
+		pr_info("Total modules in backup     = %d\n", total_in_bk);
+		pr_info(">1 module in backup count   = %d\n", cnt_more_than_1);
+	}
+	/*
+	 * This will usually hide info when the instrumentation is not in place.
+	 */
+	if (tlb_cnt_total)
+		pr_info("TLB Flushes: %lu\n", tlb_cnt_total);
+}
+
+static void do_test(int test)
+{
+	switch (test) {
+	case 1:
+		do_test_alloc_fail();
+		break;
+	case 2:
+		do_test_last_perf();
+		break;
+	default:
+		pr_info("Unknown test\n");
+	}
+}
+
+static ssize_t device_file_write(struct file *filp, const char *user_buf,
+				size_t count, loff_t *offp)
+{
+	char buf[100];
+	long iter;
+	long new_mod_cnt;
+
+	if (count >= sizeof(buf) - 1) {
+		pr_info("Command too long\n");
+		return count;
+	}
+
+	copy_from_user(buf, user_buf, count);
+	buf[count] = 0;
+	if (buf[0] == 'm') {
+		kstrtol(buf+1, 10, &new_mod_cnt);
+		if (new_mod_cnt > 0 && new_mod_cnt <= MAX_ALLOC_CNT) {
+			pr_info("New module count: %ld\n", new_mod_cnt);
+			mod_cnt = new_mod_cnt;
+			if (allocs_vm)
+				vfree(allocs_vm);
+			allocs_vm = vmalloc(sizeof(struct vm_alloc) * mod_cnt);
+
+			if (check_allocs)
+				vfree(check_allocs);
+			check_allocs = vmalloc(sizeof(struct check_alloc)
+						* mod_cnt);
+		} else
+			pr_info("more than %d not supported\n", MAX_ALLOC_CNT);
+	} else if (buf[0] == 't') {
+		kstrtol(buf + 1, 10, &iter);
+		do_test(iter);
+	} else {
+		pr_info("Unknown command\n");
+	}
+
+	return count;
+}
+
+static const char *dv_name = "mod_alloc_test";
+const static struct file_operations test_mod_alloc_fops = {
+	.owner	= THIS_MODULE,
+	.write	= device_file_write,
+};
+
+static int __init mod_alloc_test_init(void)
+{
+	debugfs_create_file(dv_name, 0400, NULL, NULL, &test_mod_alloc_fops);
+
+	return 0;
+}
+
+MODULE_LICENSE("GPL");
+
+module_init(mod_alloc_test_init);
diff --git a/tools/testing/selftests/bpf/test_mod_alloc.sh b/tools/testing/selftests/bpf/test_mod_alloc.sh
new file mode 100755
index 0000000..e9aea57
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_mod_alloc.sh
@@ -0,0 +1,29 @@ 
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+UNMOUNT_DEBUG_FS=0
+if ! mount | grep -q debugfs; then
+	if mount -t debugfs none /sys/kernel/debug/; then
+		UNMOUNT_DEBUG_FS=1
+	else
+		echo "Could not mount debug fs."
+		exit 1
+	fi
+fi
+
+if [ ! -e /sys/kernel/debug/mod_alloc_test ]; then
+	echo "Test module not found, did you build kernel with TEST_MOD_ALLOC?"
+	exit 1
+fi
+
+echo "Beginning module_alloc performance tests."
+
+for i in `seq 1000 1000 8000`; do
+	echo m$i>/sys/kernel/debug/mod_alloc_test
+	echo t2>/sys/kernel/debug/mod_alloc_test
+done
+
+echo "Module_alloc performance tests ended."
+
+if [ $UNMOUNT_DEBUG_FS -eq 1 ]; then
+	umount /sys/kernel/debug/
+fi