From: Pu Hu <[email protected]> Add a selftest that stresses kprobe handling around page fault paths. This reproducer consists of: - A kprobe module that probes folio_wait_bit_common() - A userspace program that repeatedly triggers file-backed page faults
This is primarily intended to reproduce arm64 kprobe stability issues related to XOL slot execution and fault handling during single-stepping. Signed-off-by: Pu Hu <[email protected]> --- tools/testing/selftests/kprobe/.gitignore | 2 + tools/testing/selftests/kprobe/Makefile | 75 +++++++++++++ tools/testing/selftests/kprobe/fault_stress.c | 105 ++++++++++++++++++ .../selftests/kprobe/kprobe_folio_stress.c | 70 ++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 tools/testing/selftests/kprobe/.gitignore create mode 100644 tools/testing/selftests/kprobe/Makefile create mode 100644 tools/testing/selftests/kprobe/fault_stress.c create mode 100644 tools/testing/selftests/kprobe/kprobe_folio_stress.c diff --git a/tools/testing/selftests/kprobe/.gitignore b/tools/testing/selftests/kprobe/.gitignore new file mode 100644 index 000000000000..5fdb3ee02cf1 --- /dev/null +++ b/tools/testing/selftests/kprobe/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +fault_stress diff --git a/tools/testing/selftests/kprobe/Makefile b/tools/testing/selftests/kprobe/Makefile new file mode 100644 index 000000000000..39f20de5370c --- /dev/null +++ b/tools/testing/selftests/kprobe/Makefile @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for kprobe fault stress test +# + +ifneq ($(KERNELRELEASE),) + +# This part is used by kernel Kbuild when building the external module: +# +# make -C $(KDIR) M=$(CURDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules +# +obj-m += kprobe_folio_stress.o + +ccflags-y += -g -O0 -fno-omit-frame-pointer + +else + +# This part is used when running make directly in this directory. +# +# make KDIR=$BUILD_DIR ARCH=$ARCH CROSS_COMPILE=$CROSS_COMPILE all +# + +PWD := $(shell pwd) +ARCH ?= arm64 +CROSS_COMPILE ?= aarch64-dumpstack-linux-gnu- +KDIR ?= /lib/modules/$(shell uname -r)/build +DEST_PATH ?= $(PWD)/out +Q ?= @ + +# Kernel module +KP_MOD := kprobe_folio_stress +KP_KO := $(KP_MOD).ko + +# Userspace test program +UNIT_TEST := fault_stress +UNIT_TEST_SRC := fault_stress.c + +# Userspace CFLAGS. +# These options make userspace stacks easier to unwind. +USER_CFLAGS := -static -g -O0 -fno-omit-frame-pointer -fasynchronous-unwind-tables +USER_LIBS := -lm -lpthread + +.PHONY: all modules user install clean + +all: modules user + +modules: + $(Q)$(MAKE) -C $(KDIR) \ + M=$(PWD) \ + ARCH=$(ARCH) \ + CROSS_COMPILE=$(CROSS_COMPILE) \ + modules + +user: + $(Q)$(CROSS_COMPILE)gcc \ + $(USER_CFLAGS) \ + $(UNIT_TEST_SRC) \ + -o $(UNIT_TEST) \ + $(USER_LIBS) + +install: all + $(Q)mkdir -p $(DEST_PATH) + $(Q)cp -f $(KP_KO) $(DEST_PATH)/ + $(Q)cp -f $(UNIT_TEST) $(DEST_PATH)/ + +clean: + $(Q)$(MAKE) -C $(KDIR) \ + M=$(PWD) \ + ARCH=$(ARCH) \ + CROSS_COMPILE=$(CROSS_COMPILE) \ + clean + $(Q)rm -f $(UNIT_TEST) + $(Q)rm -rf $(DEST_PATH) + +endif diff --git a/tools/testing/selftests/kprobe/fault_stress.c b/tools/testing/selftests/kprobe/fault_stress.c new file mode 100644 index 000000000000..160e172dfa59 --- /dev/null +++ b/tools/testing/selftests/kprobe/fault_stress.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fault_stress.c - Userspace program to trigger file-backed page faults + * + * This program creates a file and repeatedly maps/unmaps it while + * accessing memory, triggering file-backed page faults. It's designed + * to work with kprobe-folio-stress.c to stress kprobe handling. + */ + +#define _GNU_SOURCE +#include <fcntl.h> +#include <pthread.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#define FILE_SIZE (256UL * 1024 * 1024) +#define NR_THREADS 8 + +static void deep_call(int n) +{ + volatile char buf[4096]; + + memset((void *)buf, n, sizeof(buf)); + + if (n > 0) + deep_call(n - 1); + else + sched_yield(); +} + +static void *worker(void *arg) +{ + const char *path = arg; + int fd; + char *map; + unsigned long i; + volatile unsigned long sum = 0; + + fd = open(path, O_RDONLY); + if (fd < 0) { + perror("open"); + return NULL; + } + + map = mmap(NULL, FILE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + perror("mmap"); + close(fd); + return NULL; + } + + for (;;) { + /* + * Drop the pages backing this mapping from the current + * process. Subsequent accesses are more likely to trigger + * file-backed page faults again. + */ + madvise(map, FILE_SIZE, MADV_DONTNEED); + + for (i = 0; i < FILE_SIZE; i += 4096 * 17) { + sum += map[i]; + deep_call(64); + } + } + + munmap(map, FILE_SIZE); + close(fd); + return NULL; +} + +int main(void) +{ + pthread_t th[NR_THREADS]; + const char *path = "/tmp/fault_stress_file"; + int fd; + int i; + + fd = open(path, O_CREAT | O_RDWR, 0644); + if (fd < 0) { + perror("open file"); + return 1; + } + + if (ftruncate(fd, FILE_SIZE) < 0) { + perror("ftruncate"); + close(fd); + return 1; + } + + close(fd); + + for (i = 0; i < NR_THREADS; i++) + pthread_create(&th[i], NULL, worker, (void *)path); + + for (i = 0; i < NR_THREADS; i++) + pthread_join(th[i], NULL); + + return 0; +} diff --git a/tools/testing/selftests/kprobe/kprobe_folio_stress.c b/tools/testing/selftests/kprobe/kprobe_folio_stress.c new file mode 100644 index 000000000000..181a89f4c495 --- /dev/null +++ b/tools/testing/selftests/kprobe/kprobe_folio_stress.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * kprobe_folio_stress.c - kprobe stress test for page fault handling + * + * This module installs a kprobe on folio_wait_bit_common() and is + * intended to be used together with fault_stress.c to stress kprobe + * handling around fault paths. + * + * Primary use case: reproduce arm64 kprobe stability issues related to + * XOL slot execution and fault handling during single-stepping. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/kprobes.h> +#include <linux/sched.h> +#include <linux/atomic.h> +#include <linux/ratelimit.h> +#include <linux/kallsyms.h> + + +static atomic64_t kp_hit_count = ATOMIC64_INIT(0); + +static int folio_wait_bit_common_handler( + struct kprobe *p, struct pt_regs *regs) +{ + unsigned long hit; + + hit = atomic64_inc_return(&kp_hit_count); + + pr_info("kp_folio: hit=%lu comm=%s tgid=%d tid=%d\n", + hit, current->comm, current->tgid, current->pid); + + return 0; +} + +static struct kprobe kp_folio_probe = { + .symbol_name = "folio_wait_bit_common", + .pre_handler = folio_wait_bit_common_handler, +}; + +static int __init kprobe_folio_init(void) +{ + int ret; + + ret = register_kprobe(&kp_folio_probe); + if (ret < 0) { + pr_err("kp_folio: register_kprobe failed, ret=%d\n", ret); + return ret; + } + + pr_info("kp_folio: kprobe registered at %pS, addr=%px\n", + kp_folio_probe.addr, kp_folio_probe.addr); + + return 0; +} + +static void __exit kprobe_folio_exit(void) +{ + unregister_kprobe(&kp_folio_probe); + pr_info("kp_folio: kprobe unregistered, total hits=%lld\n", + atomic64_read(&kp_hit_count)); +} + +module_init(kprobe_folio_init); +module_exit(kprobe_folio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Pu Hu <[email protected]>"); +MODULE_DESCRIPTION("kprobe stress test for folio_wait_bit_common"); -- 2.43.0
