Introduce a new selftest that loads an eBPF program that stores the number of vmx exit counts per vcpu per vm. A process is created per vm_create to load a separate eBPF program to collect its own stats unique to the pid. This test aims to serve as a proof-of-concept and example for using eBPF to collect stats that are not provided by the other stats interfaces such as kvm_binary_stats. Since there will be no further stats being added to kvm_binary_stats, developers can use this selftest as a reference for writing their own eBPF program + selftest to collect whatever stat they may need for debugging/monitoring. Signed-off-by: Kevin Cheng <chengkev@xxxxxxxxxx> --- tools/testing/selftests/kvm/Makefile | 4 +- tools/testing/selftests/kvm/build_ebpf.sh | 5 + .../testing/selftests/kvm/kvm_vmx_exit_ebpf.c | 128 ++++++++++++++++++ .../selftests/kvm/kvm_vmx_exit_ebpf_kern.c | 74 ++++++++++ 4 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/kvm/build_ebpf.sh create mode 100644 tools/testing/selftests/kvm/kvm_vmx_exit_ebpf.c create mode 100644 tools/testing/selftests/kvm/kvm_vmx_exit_ebpf_kern.c 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"; -- 2.39.1.456.gfc5497dd1b-goog