diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 1750f91dd936..d9f56ccbc7bb 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -129,6 +129,7 @@ TEST_GEN_PROGS_x86_64 += set_memory_region_test
 TEST_GEN_PROGS_x86_64 += steal_time
 TEST_GEN_PROGS_x86_64 += kvm_binary_stats_test
 TEST_GEN_PROGS_x86_64 += system_counter_offset_test
+TEST_GEN_PROGS_x86_64 += kvm_vmx_exit_ebpf
 
 # Compiled outputs used by test targets
 TEST_GEN_PROGS_EXTENDED_x86_64 += x86_64/nx_huge_pages_test
@@ -176,6 +177,7 @@ TEST_GEN_PROGS_riscv += set_memory_region_test
 TEST_GEN_PROGS_riscv += kvm_binary_stats_test
 
 TEST_PROGS += $(TEST_PROGS_$(ARCH_DIR))
+TEST_PROGS := build_ebpf.sh
 TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH_DIR))
 TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH_DIR))
 LIBKVM += $(LIBKVM_$(ARCH_DIR))
@@ -208,7 +210,7 @@ no-pie-option := $(call try-run, echo 'int main(void) { return 0; }' | \
 pgste-option = $(call try-run, echo 'int main(void) { return 0; }' | \
 	$(CC) -Werror -Wl$(comma)--s390-pgste -x c - -o "$$TMP",-Wl$(comma)--s390-pgste)
 
-LDLIBS += -ldl
+LDLIBS += -ldl -L$(top_srcdir)/tools/lib/bpf -lbpf -lelf -lz
 LDFLAGS += -pthread $(no-pie-option) $(pgste-option)
 
 LIBKVM_C := $(filter %.c,$(LIBKVM))
diff --git a/tools/testing/selftests/kvm/build_ebpf.sh b/tools/testing/selftests/kvm/build_ebpf.sh
new file mode 100644
index 000000000000..b8038b0a0da5
--- /dev/null
+++ b/tools/testing/selftests/kvm/build_ebpf.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I . -c kvm_vmx_exit_ebpf_kern.c
+        -o kvm_vmx_exit_ebpf_kern.o
+make -C ../../../lib/bpf || exit
diff --git a/tools/testing/selftests/kvm/kvm_vmx_exit_ebpf.c b/tools/testing/selftests/kvm/kvm_vmx_exit_ebpf.c
new file mode 100644
index 000000000000..a4bd2c549207
--- /dev/null
+++ b/tools/testing/selftests/kvm/kvm_vmx_exit_ebpf.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <bpf/bpf.h>
+#include <../bpf/libbpf.h>
+#include <linux/btf.h>
+
+#include "test_util.h"
+
+#include "kvm_util.h"
+#include "linux/kvm.h"
+
+#define VCPU_ID         0
+
+struct stats_map_key {
+	__u32 pid;
+	__u32 vcpu_id;
+	__u32 exit_reason;
+};
+
+static void guest_code(void)
+{
+	__asm__ __volatile__("cpuid");
+}
+
+int main(int argc, char **argv)
+{
+	if (argc < 2) {
+		fprintf(stderr, "Expected arguments: <number_of_vms>\n");
+		return EXIT_FAILURE;
+	}
+	int n = atoi(argv[1]);
+
+	for (int i = 0; i < n; i++) {
+		if (fork() == 0) {
+			struct kvm_vm *vm;
+			struct kvm_vcpu *vcpu;
+
+			vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+
+			// BPF userspace code
+			struct bpf_object *obj;
+			struct bpf_program *prog;
+			struct bpf_map *map_obj;
+			struct bpf_link *link = NULL;
+
+			obj = bpf_object__open_file("kvm_vmx_exit_ebpf_kern.o", NULL);
+			if (libbpf_get_error(obj)) {
+				fprintf(stderr, "ERROR: opening BPF object file failed\n");
+				return 0;
+			}
+
+			map_obj = bpf_object__find_map_by_name(obj, "vmx_exit_map");
+			if (!map_obj) {
+				fprintf(stderr, "ERROR: loading of vmx BPF map failed\n");
+				goto cleanup;
+			}
+
+			struct bpf_map *pid_map = bpf_object__find_map_by_name(obj, "pid_map");
+
+			if (!pid_map) {
+				fprintf(stderr, "ERROR: loading of pid BPF map failed\n");
+				goto cleanup;
+			}
+
+			/* load BPF program */
+			if (bpf_object__load(obj)) {
+				fprintf(stderr, "ERROR: loading BPF object file failed\n");
+				goto cleanup;
+			}
+
+			__u32 userspace_pid = (__u32)getpid();
+			__u32 val = (__u32)getpid();
+
+			bpf_map_update_elem(bpf_map__fd(pid_map), &userspace_pid, &val, 0);
+
+			prog = bpf_object__find_program_by_name(obj, "bpf_exit_prog");
+			if (libbpf_get_error(prog)) {
+				fprintf(stderr, "ERROR: finding a prog in obj file failed\n");
+				goto cleanup;
+			}
+
+			link = bpf_program__attach(prog);
+			if (libbpf_get_error(link)) {
+				fprintf(stderr, "ERROR: bpf_program__attach failed\n");
+				link = NULL;
+				goto cleanup;
+			}
+
+			for (int j = 0; j < 10000; j++)
+				vcpu_run(vcpu);
+
+			struct stats_map_key key = {
+				.pid = 0,
+				.vcpu_id = 0,
+				.exit_reason = 18,
+			};
+
+
+			struct stats_map_key next_key, lookup_key;
+
+			lookup_key = key;
+			while (bpf_map_get_next_key(bpf_map__fd(map_obj), &lookup_key, &next_key)
+				 == 0) {
+				int count;
+
+				bpf_map_lookup_elem(bpf_map__fd(map_obj), &next_key, &count);
+				fprintf(stdout, "exit reason: '%d'\ncount: %d\npid: %d\n",
+						next_key.exit_reason, count, next_key.pid);
+				lookup_key = next_key;
+			}
+
+cleanup:
+			bpf_link__destroy(link);
+			bpf_object__close(obj);
+			kvm_vm_free(vm);
+		}
+	}
+
+	for (int i = 0; i < n; i++)
+		wait(NULL);
+	return 0;
+}
diff --git a/tools/testing/selftests/kvm/kvm_vmx_exit_ebpf_kern.c b/tools/testing/selftests/kvm/kvm_vmx_exit_ebpf_kern.c
new file mode 100644
index 000000000000..b9c076f93171
--- /dev/null
+++ b/tools/testing/selftests/kvm/kvm_vmx_exit_ebpf_kern.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/bpf.h>
+#include <stdint.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+
+struct kvm_vcpu {
+	int vcpu_id;
+};
+
+struct vmx_args {
+	__u64 pad;
+	unsigned int exit_reason;
+	__u32 isa;
+	struct kvm_vcpu *vcpu;
+};
+
+struct stats_map_key {
+	__u32 pid;
+	__u32 vcpu_id;
+	__u32 exit_reason;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 1024);
+	__type(key, struct stats_map_key);
+	__type(value, int);
+} vmx_exit_map SEC(".maps");
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, __u32);
+} pid_map SEC(".maps");
+
+
+SEC("tracepoint/kvm/kvm_exit")
+int bpf_exit_prog(struct vmx_args *ctx)
+{
+	__u32 curr_pid = (bpf_get_current_pid_tgid() >> 32);
+
+	__u32 *userspace_pid = bpf_map_lookup_elem(&pid_map, &curr_pid);
+
+	if (!userspace_pid || *userspace_pid != curr_pid)
+		return 0;
+
+	struct kvm_vcpu *vcpu = ctx->vcpu;
+	int _vcpu_id = BPF_CORE_READ(vcpu, vcpu_id);
+
+	struct stats_map_key key = {
+		.pid = (bpf_get_current_pid_tgid() >> 32),
+		.vcpu_id = _vcpu_id,
+		.exit_reason = ctx->exit_reason,
+	};
+
+	int *value = bpf_map_lookup_elem(&vmx_exit_map, &key);
+
+	if (value) {
+		*value = *value + 1;
+		bpf_map_update_elem(&vmx_exit_map, &key, value, BPF_ANY);
+	} else {
+		int temp = 1;
+
+		bpf_map_update_elem(&vmx_exit_map, &key, &temp, BPF_ANY);
+	}
+
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
