diff options
Diffstat (limited to 'tools/verification')
-rw-r--r-- | tools/verification/rv/Makefile | 141 | ||||
-rw-r--r-- | tools/verification/rv/README.txt | 38 | ||||
-rw-r--r-- | tools/verification/rv/include/in_kernel.h | 3 | ||||
-rw-r--r-- | tools/verification/rv/include/rv.h | 12 | ||||
-rw-r--r-- | tools/verification/rv/include/trace.h | 16 | ||||
-rw-r--r-- | tools/verification/rv/include/utils.h | 8 | ||||
-rw-r--r-- | tools/verification/rv/src/in_kernel.c | 698 | ||||
-rw-r--r-- | tools/verification/rv/src/rv.c | 188 | ||||
-rw-r--r-- | tools/verification/rv/src/trace.c | 133 | ||||
-rw-r--r-- | tools/verification/rv/src/utils.c | 47 |
10 files changed, 1284 insertions, 0 deletions
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile new file mode 100644 index 000000000000..3d0f3888a58c --- /dev/null +++ b/tools/verification/rv/Makefile @@ -0,0 +1,141 @@ +NAME := rv +# Follow the kernel version +VERSION := $(shell cat VERSION 2> /dev/null || make -sC ../../.. kernelversion | grep -v make) + +# From libtracefs: +# Makefiles suck: This macro sets a default value of $(2) for the +# variable named by $(1), unless the variable has been set by +# environment or command line. This is necessary for CC and AR +# because make sets default values, so the simpler ?= approach +# won't work as expected. +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +# Allow setting CC and AR, or setting CROSS_COMPILE as a prefix. +$(call allow-override,CC,$(CROSS_COMPILE)gcc) +$(call allow-override,AR,$(CROSS_COMPILE)ar) +$(call allow-override,STRIP,$(CROSS_COMPILE)strip) +$(call allow-override,PKG_CONFIG,pkg-config) +$(call allow-override,LD_SO_CONF_PATH,/etc/ld.so.conf.d/) +$(call allow-override,LDCONFIG,ldconfig) + +INSTALL = install +MKDIR = mkdir +FOPTS := -flto=auto -ffat-lto-objects -fexceptions -fstack-protector-strong \ + -fasynchronous-unwind-tables -fstack-clash-protection +WOPTS := -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -Wno-maybe-uninitialized + +TRACEFS_HEADERS := $$($(PKG_CONFIG) --cflags libtracefs) + +CFLAGS := -O -g -DVERSION=\"$(VERSION)\" $(FOPTS) $(MOPTS) $(WOPTS) $(TRACEFS_HEADERS) $(EXTRA_CFLAGS) -I include +LDFLAGS := -ggdb $(EXTRA_LDFLAGS) +LIBS := $$($(PKG_CONFIG) --libs libtracefs) + +SRC := $(wildcard src/*.c) +HDR := $(wildcard src/*.h) +OBJ := $(SRC:.c=.o) +DIRS := src +FILES := Makefile README.txt +CEXT := bz2 +TARBALL := $(NAME)-$(VERSION).tar.$(CEXT) +TAROPTS := -cvjf $(TARBALL) +BINDIR := /usr/bin +DATADIR := /usr/share +DOCDIR := $(DATADIR)/doc +MANDIR := $(DATADIR)/man +LICDIR := $(DATADIR)/licenses +SRCTREE := $(or $(BUILD_SRC),$(CURDIR)) + +# If running from the tarball, man pages are stored in the Documentation +# dir. If running from the kernel source, man pages are stored in +# Documentation/tools/rv/. +ifneq ($(wildcard Documentation/.*),) +DOCSRC = Documentation/ +else +DOCSRC = $(SRCTREE)/../../../Documentation/tools/rv/ +endif + +LIBTRACEEVENT_MIN_VERSION = 1.5 +LIBTRACEFS_MIN_VERSION = 1.3 + +.PHONY: all warnings show_warnings +all: warnings rv + +TEST_LIBTRACEEVENT = $(shell sh -c "$(PKG_CONFIG) --atleast-version $(LIBTRACEEVENT_MIN_VERSION) libtraceevent > /dev/null 2>&1 || echo n") +ifeq ("$(TEST_LIBTRACEEVENT)", "n") +WARNINGS = show_warnings +MISSING_LIBS += echo "** libtraceevent version $(LIBTRACEEVENT_MIN_VERSION) or higher"; +MISSING_PACKAGES += "libtraceevent-devel" +MISSING_SOURCE += echo "** https://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git/ "; +endif + +TEST_LIBTRACEFS = $(shell sh -c "$(PKG_CONFIG) --atleast-version $(LIBTRACEFS_MIN_VERSION) libtracefs > /dev/null 2>&1 || echo n") +ifeq ("$(TEST_LIBTRACEFS)", "n") +WARNINGS = show_warnings +MISSING_LIBS += echo "** libtracefs version $(LIBTRACEFS_MIN_VERSION) or higher"; +MISSING_PACKAGES += "libtracefs-devel" +MISSING_SOURCE += echo "** https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git/ "; +endif + +define show_dependencies + @echo "********************************************"; \ + echo "** NOTICE: Failed build dependencies"; \ + echo "**"; \ + echo "** Required Libraries:"; \ + $(MISSING_LIBS) \ + echo "**"; \ + echo "** Consider installing the latest libtracefs from your"; \ + echo "** distribution, e.g., 'dnf install $(MISSING_PACKAGES)' on Fedora,"; \ + echo "** or from source:"; \ + echo "**"; \ + $(MISSING_SOURCE) \ + echo "**"; \ + echo "********************************************" +endef + +show_warnings: + $(call show_dependencies); + +ifneq ("$(WARNINGS)", "") +ERROR_OUT = $(error Please add the necessary dependencies) + +warnings: $(WARNINGS) + $(ERROR_OUT) +endif + +rv: $(OBJ) + $(CC) -o rv $(LDFLAGS) $(OBJ) $(LIBS) + +.PHONY: install +install: doc_install + $(MKDIR) -p $(DESTDIR)$(BINDIR) + $(INSTALL) rv -m 755 $(DESTDIR)$(BINDIR) + $(STRIP) $(DESTDIR)$(BINDIR)/rv + +.PHONY: clean tarball +clean: doc_clean + @test ! -f rv || rm rv + @test ! -f $(TARBALL) || rm -f $(TARBALL) + @rm -rf *~ $(OBJ) *.tar.$(CEXT) + +tarball: clean + rm -rf $(NAME)-$(VERSION) && mkdir $(NAME)-$(VERSION) + echo $(VERSION) > $(NAME)-$(VERSION)/VERSION + cp -r $(DIRS) $(FILES) $(NAME)-$(VERSION) + mkdir $(NAME)-$(VERSION)/Documentation/ + cp -rp $(SRCTREE)/../../../Documentation/tools/rv/* $(NAME)-$(VERSION)/Documentation/ + tar $(TAROPTS) --exclude='*~' $(NAME)-$(VERSION) + rm -rf $(NAME)-$(VERSION) + +.PHONY: doc doc_clean doc_install +doc: + $(MAKE) -C $(DOCSRC) + +doc_clean: + $(MAKE) -C $(DOCSRC) clean + +doc_install: + $(MAKE) -C $(DOCSRC) install diff --git a/tools/verification/rv/README.txt b/tools/verification/rv/README.txt new file mode 100644 index 000000000000..e96be0dfff59 --- /dev/null +++ b/tools/verification/rv/README.txt @@ -0,0 +1,38 @@ +RV: Runtime Verification + +Runtime Verification (RV) is a lightweight (yet rigorous) method that +complements classical exhaustive verification techniques (such as model +checking and theorem proving) with a more practical approach for +complex systems. + +The rv tool is the interface for a collection of monitors that aim +analysing the logical and timing behavior of Linux. + +Installing RV + +RV depends on the following libraries and tools: + + - libtracefs + - libtraceevent + +It also depends on python3-docutils to compile man pages. + +For development, we suggest the following steps for compiling rtla: + + $ git clone git://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git + $ cd libtraceevent/ + $ make + $ sudo make install + $ cd .. + $ git clone git://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git + $ cd libtracefs/ + $ make + $ sudo make install + $ cd .. + $ cd $rv_src + $ make + $ sudo make install + +For further information, please see rv manpage and the kernel documentation: + Runtime Verification: + Documentation/trace/rv/runtime-verification.rst diff --git a/tools/verification/rv/include/in_kernel.h b/tools/verification/rv/include/in_kernel.h new file mode 100644 index 000000000000..3090638c8d71 --- /dev/null +++ b/tools/verification/rv/include/in_kernel.h @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-2.0 +int ikm_list_monitors(void); +int ikm_run_monitor(char *monitor, int argc, char **argv); diff --git a/tools/verification/rv/include/rv.h b/tools/verification/rv/include/rv.h new file mode 100644 index 000000000000..770fd6da3610 --- /dev/null +++ b/tools/verification/rv/include/rv.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define MAX_DESCRIPTION 1024 +#define MAX_DA_NAME_LEN 24 + +struct monitor { + char name[MAX_DA_NAME_LEN]; + char desc[MAX_DESCRIPTION]; + int enabled; +}; + +int should_stop(void); diff --git a/tools/verification/rv/include/trace.h b/tools/verification/rv/include/trace.h new file mode 100644 index 000000000000..8d89e8c303fa --- /dev/null +++ b/tools/verification/rv/include/trace.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <tracefs.h> + +struct trace_instance { + struct tracefs_instance *inst; + struct tep_handle *tep; + struct trace_seq *seq; +}; + +int trace_instance_init(struct trace_instance *trace, char *name); +int trace_instance_start(struct trace_instance *trace); +void trace_instance_destroy(struct trace_instance *trace); + +int collect_registered_events(struct tep_event *event, struct tep_record *record, + int cpu, void *context); diff --git a/tools/verification/rv/include/utils.h b/tools/verification/rv/include/utils.h new file mode 100644 index 000000000000..f24ae8282bd2 --- /dev/null +++ b/tools/verification/rv/include/utils.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define MAX_PATH 1024 + +void debug_msg(const char *fmt, ...); +void err_msg(const char *fmt, ...); + +extern int config_debug; diff --git a/tools/verification/rv/src/in_kernel.c b/tools/verification/rv/src/in_kernel.c new file mode 100644 index 000000000000..50848d79b38b --- /dev/null +++ b/tools/verification/rv/src/in_kernel.c @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * in kernel monitor support: allows rv to control in-kernel monitors. + * + * Copyright (C) 2022 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> + */ +#include <getopt.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include <trace.h> +#include <utils.h> +#include <rv.h> + +static int config_has_id; +static int config_my_pid; +static int config_trace; + +static char *config_initial_reactor; +static char *config_reactor; + +/* + * __ikm_read_enable - reads monitor's enable status + * + * __does not log errors. + * + * Returns the current status, or -1 if the monitor does not exist, + * __hence not logging errors. + */ +static int __ikm_read_enable(char *monitor_name) +{ + char path[MAX_PATH]; + long long enabled; + int retval; + + snprintf(path, MAX_PATH, "rv/monitors/%s/enable", monitor_name); + + retval = tracefs_instance_file_read_number(NULL, path, &enabled); + if (retval < 0) + return -1; + + return enabled; +} + +/* + * ikm_read_enable - reads monitor's enable status + * + * Returns the current status, or -1 on error. + */ +static int ikm_read_enable(char *monitor_name) +{ + int enabled; + + enabled = __ikm_read_enable(monitor_name); + if (enabled < 0) { + err_msg("ikm: fail read enabled: %d\n", enabled); + return -1; + } + + debug_msg("ikm: read enabled: %d\n", enabled); + + return enabled; +} + +/* + * ikm_write_enable - write to the monitor's enable file + * + * Return the number of bytes written, -1 on error. + */ +static int ikm_write_enable(char *monitor_name, char *enable_disable) +{ + char path[MAX_PATH]; + int retval; + + debug_msg("ikm: writing enabled: %s\n", enable_disable); + + snprintf(path, MAX_PATH, "rv/monitors/%s/enable", monitor_name); + retval = tracefs_instance_file_write(NULL, path, enable_disable); + if (retval < strlen(enable_disable)) { + err_msg("ikm: writing enabled: %s\n", enable_disable); + return -1; + } + + return retval; +} + +/* + * ikm_enable - enable a monitor + * + * Returns -1 on failure. Success otherwise. + */ +static int ikm_enable(char *monitor_name) +{ + return ikm_write_enable(monitor_name, "1"); +} + +/* + * ikm_disable - disable a monitor + * + * Returns -1 on failure. Success otherwise. + */ +static int ikm_disable(char *monitor_name) +{ + return ikm_write_enable(monitor_name, "0"); +} + +/* + * ikm_read_desc - read monitors' description + * + * Return a dynamically allocated string with the monitor's + * description, NULL otherwise. + */ +static char *ikm_read_desc(char *monitor_name) +{ + char path[MAX_PATH]; + char *desc; + + snprintf(path, MAX_PATH, "rv/monitors/%s/desc", monitor_name); + desc = tracefs_instance_file_read(NULL, path, NULL); + if (!desc) { + err_msg("ikm: error reading monitor %s desc\n", monitor_name); + return NULL; + } + + *strstr(desc, "\n") = '\0'; + + return desc; +} + +/* + * ikm_fill_monitor_definition - fill monitor's definition + * + * Returns -1 on error, 0 otherwise. + */ +static int ikm_fill_monitor_definition(char *name, struct monitor *ikm) +{ + int enabled; + char *desc; + + enabled = ikm_read_enable(name); + if (enabled < 0) { + err_msg("ikm: monitor %s fail to read enable file, bug?\n", name); + return -1; + } + + desc = ikm_read_desc(name); + if (!desc) { + err_msg("ikm: monitor %s does not have desc file, bug?\n", name); + return -1; + } + + strncpy(ikm->name, name, MAX_DA_NAME_LEN); + ikm->enabled = enabled; + strncpy(ikm->desc, desc, MAX_DESCRIPTION); + + free(desc); + + return 0; +} + +/* + * ikm_write_reactor - switch the reactor to *reactor + * + * Return the number or characters written, -1 on error. + */ +static int ikm_write_reactor(char *monitor_name, char *reactor) +{ + char path[MAX_PATH]; + int retval; + + snprintf(path, MAX_PATH, "rv/monitors/%s/reactors", monitor_name); + retval = tracefs_instance_file_write(NULL, path, reactor); + debug_msg("ikm: write \"%s\" reactors: %d\n", reactor, retval); + + return retval; +} + +/* + * ikm_read_reactor - read the reactors file + * + * Returns a dynamically allocated string with monitor's + * available reactors, or NULL on error. + */ +static char *ikm_read_reactor(char *monitor_name) +{ + char path[MAX_PATH]; + char *reactors; + + snprintf(path, MAX_PATH, "rv/monitors/%s/reactors", monitor_name); + reactors = tracefs_instance_file_read(NULL, path, NULL); + if (!reactors) { + err_msg("ikm: fail reading monitor's %s reactors file\n", monitor_name); + return NULL; + } + + return reactors; +} + +/* + * ikm_get_current_reactor - get the current enabled reactor + * + * Reads the reactors file and find the currently enabled + * [reactor]. + * + * Returns a dynamically allocated memory with the current + * reactor. NULL otherwise. + */ +static char *ikm_get_current_reactor(char *monitor_name) +{ + char *reactors = ikm_read_reactor(monitor_name); + char *start; + char *end; + char *curr_reactor; + + if (!reactors) + return NULL; + + start = strstr(reactors, "["); + if (!start) + goto out_free; + + start++; + + end = strstr(start, "]"); + if (!end) + goto out_free; + + *end = '\0'; + + curr_reactor = calloc(strlen(start) + 1, sizeof(char)); + if (!curr_reactor) + goto out_free; + + strncpy(curr_reactor, start, strlen(start)); + debug_msg("ikm: read current reactor %s\n", curr_reactor); + +out_free: + free(reactors); + + return curr_reactor; +} + +static int ikm_has_id(char *monitor_name) +{ + char path[MAX_PATH]; + char *format; + int has_id; + + snprintf(path, MAX_PATH, "events/rv/event_%s/format", monitor_name); + format = tracefs_instance_file_read(NULL, path, NULL); + if (!format) { + err_msg("ikm: fail reading monitor's %s format event file\n", monitor_name); + return -1; + } + + /* print fmt: "%d: %s x %s -> %s %s", REC->id, ... */ + has_id = !!strstr(format, "REC->id"); + + debug_msg("ikm: monitor %s has id: %s\n", monitor_name, has_id ? "yes" : "no"); + + free(format); + + return has_id; +} + +/** + * ikm_list_monitors - list all available monitors + * + * Returns 0 on success, -1 otherwise. + */ +int ikm_list_monitors(void) +{ + char *available_monitors; + struct monitor ikm; + char *curr, *next; + int retval; + + available_monitors = tracefs_instance_file_read(NULL, "rv/available_monitors", NULL); + + if (!available_monitors) { + err_msg("ikm: available monitors is not available, is CONFIG_RV enabled?\n"); + return -1; + } + + curr = available_monitors; + do { + next = strstr(curr, "\n"); + *next = '\0'; + + retval = ikm_fill_monitor_definition(curr, &ikm); + if (retval) + err_msg("ikm: error reading %d in kernel monitor, skipping\n", curr); + + printf("%-24s %s %s\n", ikm.name, ikm.desc, ikm.enabled ? "[ON]" : "[OFF]"); + curr = ++next; + + } while (strlen(curr)); + + free(available_monitors); + + return 0; +} + +static void ikm_print_header(struct trace_seq *s) +{ + trace_seq_printf(s, "%16s-%-8s %5s %5s ", "<TASK>", "PID", "[CPU]", "TYPE"); + if (config_has_id) + trace_seq_printf(s, "%8s ", "ID"); + + trace_seq_printf(s, "%24s x %-24s -> %-24s %s\n", + "STATE", + "EVENT", + "NEXT_STATE", + "FINAL"); + + trace_seq_printf(s, "%16s %-8s %5s %5s ", " | ", " | ", " | ", " | "); + + if (config_has_id) + trace_seq_printf(s, "%8s ", " | "); + + trace_seq_printf(s, "%24s %-24s %-24s %s\n", + " | ", + " | ", + " | ", + "|"); + +} + +/* + * ikm_event_handler - callback to handle event events + * + * Called any time a rv:"monitor"_event events is generated. + * It parses and print event. + */ +static int +ikm_event_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *trace_event, void *context) +{ + /* if needed: struct trace_instance *inst = context; */ + char *state, *event, *next_state; + unsigned long long final_state; + unsigned long long pid; + unsigned long long id; + int cpu = record->cpu; + int val; + + if (config_has_id) + tep_get_field_val(s, trace_event, "id", record, &id, 1); + + tep_get_common_field_val(s, trace_event, "common_pid", record, &pid, 1); + + if (config_has_id && (config_my_pid == id)) + return 0; + else if (config_my_pid && (config_my_pid == pid)) + return 0; + + tep_print_event(trace_event->tep, s, record, "%16s-%-8d ", TEP_PRINT_COMM, TEP_PRINT_PID); + + trace_seq_printf(s, "[%.3d] event ", cpu); + + if (config_has_id) + trace_seq_printf(s, "%8llu ", id); + + state = tep_get_field_raw(s, trace_event, "state", record, &val, 0); + event = tep_get_field_raw(s, trace_event, "event", record, &val, 0); + next_state = tep_get_field_raw(s, trace_event, "next_state", record, &val, 0); + tep_get_field_val(s, trace_event, "final_state", record, &final_state, 1); + + trace_seq_printf(s, "%24s x %-24s -> %-24s %s\n", + state, + event, + next_state, + final_state ? "Y" : "N"); + + trace_seq_do_printf(s); + trace_seq_reset(s); + + return 0; +} + +/* + * ikm_error_handler - callback to handle error events + * + * Called any time a rv:"monitor"_errors events is generated. + * It parses and print event. + */ +static int +ikm_error_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *trace_event, void *context) +{ + unsigned long long pid, id; + int cpu = record->cpu; + char *state, *event; + int val; + + if (config_has_id) + tep_get_field_val(s, trace_event, "id", record, &id, 1); + + tep_get_common_field_val(s, trace_event, "common_pid", record, &pid, 1); + + if (config_has_id && config_my_pid == id) + return 0; + else if (config_my_pid == pid) + return 0; + + trace_seq_printf(s, "%8lld [%03d] error ", pid, cpu); + + if (config_has_id) + trace_seq_printf(s, "%8llu ", id); + + state = tep_get_field_raw(s, trace_event, "state", record, &val, 0); + event = tep_get_field_raw(s, trace_event, "event", record, &val, 0); + + trace_seq_printf(s, "%24s x %s\n", state, event); + + trace_seq_do_printf(s); + trace_seq_reset(s); + + return 0; +} + +/* + * ikm_setup_trace_instance - set up a tracing instance to collect data + * + * Create a trace instance, enable rv: events and enable the trace. + * + * Returns the trace_instance * with all set, NULL otherwise. + */ +static struct trace_instance *ikm_setup_trace_instance(char *monitor_name) +{ + char event[MAX_DA_NAME_LEN + 7]; /* max(error_,event_) + '0' = 7 */ + struct trace_instance *inst; + int retval; + + if (!config_trace) + return NULL; + + config_has_id = ikm_has_id(monitor_name); + if (config_has_id < 0) { + err_msg("ikm: failed to read monitor %s event format\n", monitor_name); + goto out_err; + } + + /* alloc data */ + inst = calloc(1, sizeof(*inst)); + if (!inst) { + err_msg("ikm: failed to allocate trace instance"); + goto out_err; + } + + retval = trace_instance_init(inst, monitor_name); + if (retval) + goto out_free; + + /* enable events */ + snprintf(event, sizeof(event), "event_%s", monitor_name); + retval = tracefs_event_enable(inst->inst, "rv", event); + if (retval) + goto out_inst; + + tep_register_event_handler(inst->tep, -1, "rv", event, + ikm_event_handler, NULL); + + snprintf(event, sizeof(event), "error_%s", monitor_name); + retval = tracefs_event_enable(inst->inst, "rv", event); + if (retval) + goto out_inst; + + tep_register_event_handler(inst->tep, -1, "rv", event, + ikm_error_handler, NULL); + + /* ready to enable */ + tracefs_trace_on(inst->inst); + + return inst; + +out_inst: + trace_instance_destroy(inst); +out_free: + free(inst); +out_err: + return NULL; +} + +/** + * ikm_destroy_trace_instance - destroy a previously created instance + */ +static void ikm_destroy_trace_instance(struct trace_instance *inst) +{ + if (!inst) + return; + + trace_instance_destroy(inst); + free(inst); +} + +/* + * ikm_usage_print_reactors - print all available reactors, one per line. + */ +static void ikm_usage_print_reactors(void) +{ + char *reactors = tracefs_instance_file_read(NULL, "rv/available_reactors", NULL); + char *start, *end; + + if (!reactors) + return; + + fprintf(stderr, " available reactors:"); + + start = reactors; + end = strstr(start, "\n"); + + while (end) { + *end = '\0'; + + fprintf(stderr, " %s", start); + + start = ++end; + end = strstr(start, "\n"); + }; + + fprintf(stderr, "\n"); +} +/* + * ikm_usage - print usage + */ +static void ikm_usage(int exit_val, char *monitor_name, const char *fmt, ...) +{ + + char message[1024]; + va_list ap; + int i; + + static const char *const usage[] = { + "", + " -h/--help: print this menu and the reactor list", + " -r/--reactor 'reactor': enables the 'reactor'", + " -s/--self: when tracing (-t), also trace rv command", + " -t/--trace: trace monitor's event", + " -v/--verbose: print debug messages", + "", + NULL, + }; + + va_start(ap, fmt); + vsnprintf(message, sizeof(message), fmt, ap); + va_end(ap); + + fprintf(stderr, " %s\n", message); + + fprintf(stderr, "\n usage: rv mon %s [-h] [-q] [-r reactor] [-s] [-v]", monitor_name); + + for (i = 0; usage[i]; i++) + fprintf(stderr, "%s\n", usage[i]); + + ikm_usage_print_reactors(); + exit(exit_val); +} + +/* + * parse_arguments - parse arguments and set config + */ +static int parse_arguments(char *monitor_name, int argc, char **argv) +{ + int c, retval; + + config_my_pid = getpid(); + + while (1) { + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"reactor", required_argument, 0, 'r'}, + {"self", no_argument, 0, 's'}, + {"trace", no_argument, 0, 't'}, + {"verbose", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long(argc, argv, "hr:stv", long_options, &option_index); + + /* detect the end of the options. */ + if (c == -1) + break; + + switch (c) { + case 'h': + ikm_usage(0, monitor_name, "help:"); + break; + case 'r': + config_reactor = optarg; + break; + case 's': + config_my_pid = 0; + break; + case 't': + config_trace = 1; + break; + case 'v': + config_debug = 1; + break; + } + } + + if (config_reactor) { + config_initial_reactor = ikm_get_current_reactor(monitor_name); + if (!config_initial_reactor) + ikm_usage(1, monitor_name, + "ikm: failed to read current reactor, are reactors enabled?"); + + retval = ikm_write_reactor(monitor_name, config_reactor); + if (retval <= 0) + ikm_usage(1, monitor_name, + "ikm: failed to set %s reactor, is it available?", + config_reactor); + } + + debug_msg("ikm: my pid is %d\n", config_my_pid); + + return 0; +} + +/** + * ikm_run_monitor - apply configs and run the monitor + * + * Returns 1 if a monitor was found an executed, 0 if no + * monitors were found, or -1 on error. + */ +int ikm_run_monitor(char *monitor_name, int argc, char **argv) +{ + struct trace_instance *inst = NULL; + int retval; + + /* + * Check if monitor exists by seeing it is enabled. + */ + retval = __ikm_read_enable(monitor_name); + if (retval < 0) + return 0; + + if (retval) { + err_msg("ikm: monitor %s (in-kernel) is already enabled\n", monitor_name); + return -1; + } + + /* we should be good to go */ + retval = parse_arguments(monitor_name, argc, argv); + if (retval) + ikm_usage(1, monitor_name, "ikm: failed parsing arguments"); + + if (config_trace) { + inst = ikm_setup_trace_instance(monitor_name); + if (!inst) + return -1; + } + + retval = ikm_enable(monitor_name); + if (retval < 0) + goto out_free_instance; + + if (config_trace) + ikm_print_header(inst->seq); + + while (!should_stop()) { + if (config_trace) { + retval = tracefs_iterate_raw_events(inst->tep, + inst->inst, + NULL, + 0, + collect_registered_events, + inst); + if (retval) { + err_msg("ikm: error reading trace buffer\n"); + break; + } + } + + sleep(1); + } + + ikm_disable(monitor_name); + ikm_destroy_trace_instance(inst); + + if (config_reactor && config_initial_reactor) + ikm_write_reactor(monitor_name, config_initial_reactor); + + return 1; + +out_free_instance: + ikm_destroy_trace_instance(inst); + if (config_reactor && config_initial_reactor) + ikm_write_reactor(monitor_name, config_initial_reactor); + return -1; +} diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c new file mode 100644 index 000000000000..e601cd9c411e --- /dev/null +++ b/tools/verification/rv/src/rv.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rv tool, the interface for the Linux kernel RV subsystem and home of + * user-space controlled monitors. + * + * Copyright (C) 2022 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> + */ + +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> + +#include <trace.h> +#include <utils.h> +#include <in_kernel.h> + +static int stop_session; + +/* + * stop_rv - tell monitors to stop + */ +static void stop_rv(int sig) +{ + stop_session = 1; +} + +/** + * should_stop - check if the monitor should stop. + * + * Returns 1 if the monitor should stop, 0 otherwise. + */ +int should_stop(void) +{ + return stop_session; +} + +/* + * rv_list - list all available monitors + */ +static void rv_list(int argc, char **argv) +{ + static const char *const usage[] = { + "", + " usage: rv list [-h]", + "", + " list all available monitors", + "", + " -h/--help: print this menu", + NULL, + }; + int i; + + if (argc > 1) { + fprintf(stderr, "rv version %s\n", VERSION); + + /* more than 1 is always usage */ + for (i = 0; usage[i]; i++) + fprintf(stderr, "%s\n", usage[i]); + + /* but only -h is valid */ + if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) + exit(0); + else + exit(1); + } + + ikm_list_monitors(); + exit(0); +} + +/* + * rv_mon - try to run a monitor passed as argument + */ +static void rv_mon(int argc, char **argv) +{ + char *monitor_name; + int i, run; + + static const char *const usage[] = { + "", + " usage: rv mon [-h] monitor [monitor options]", + "", + " run a monitor", + "", + " -h/--help: print this menu", + "", + " monitor [monitor options]: the monitor, passing", + " the arguments to the [monitor options]", + NULL, + }; + + /* requires at least one argument */ + if (argc == 1) { + + fprintf(stderr, "rv version %s\n", VERSION); + + for (i = 0; usage[i]; i++) + fprintf(stderr, "%s\n", usage[i]); + exit(1); + } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { + + fprintf(stderr, "rv version %s\n", VERSION); + + for (i = 0; usage[i]; i++) + fprintf(stderr, "%s\n", usage[i]); + exit(0); + } + + monitor_name = argv[1]; + /* + * Call all possible monitor implementations, looking + * for the [monitor]. + */ + run += ikm_run_monitor(monitor_name, argc-1, &argv[1]); + + if (!run) + err_msg("rv: monitor %s does not exist\n", monitor_name); + exit(!run); +} + +static void usage(int exit_val, const char *fmt, ...) +{ + char message[1024]; + va_list ap; + int i; + + static const char *const usage[] = { + "", + " usage: rv command [-h] [command_options]", + "", + " -h/--help: print this menu", + "", + " command: run one of the following command:", + " list: list all available monitors", + " mon: run a monitor", + "", + " [command options]: each command has its own set of options", + " run rv command -h for further information", + NULL, + }; + + va_start(ap, fmt); + vsnprintf(message, sizeof(message), fmt, ap); + va_end(ap); + + fprintf(stderr, "rv version %s: %s\n", VERSION, message); + + for (i = 0; usage[i]; i++) + fprintf(stderr, "%s\n", usage[i]); + + exit(exit_val); +} + +/* + * main - select which main sending the command + * + * main itself redirects the arguments to the sub-commands + * to handle the options. + * + * subcommands should exit. + */ +int main(int argc, char **argv) +{ + if (geteuid()) + usage(1, "%s needs root permission", argv[0]); + + if (argc <= 1) + usage(1, "%s requires a command", argv[0]); + + if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) + usage(0, "help"); + + if (!strcmp(argv[1], "list")) + rv_list(--argc, &argv[1]); + + if (!strcmp(argv[1], "mon")) { + /* + * monitor's main should monitor should_stop() function. + * and exit. + */ + signal(SIGINT, stop_rv); + + rv_mon(argc - 1, &argv[1]); + } + + /* invalid sub-command */ + usage(1, "%s does not know the %s command, old version?", argv[0], argv[1]); +} diff --git a/tools/verification/rv/src/trace.c b/tools/verification/rv/src/trace.c new file mode 100644 index 000000000000..2c7deed47f8d --- /dev/null +++ b/tools/verification/rv/src/trace.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * trace helpers. + * + * Copyright (C) 2022 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> + */ + +#include <sys/sendfile.h> +#include <tracefs.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + +#include <rv.h> +#include <trace.h> +#include <utils.h> + +/* + * create_instance - create a trace instance with *instance_name + */ +static struct tracefs_instance *create_instance(char *instance_name) +{ + return tracefs_instance_create(instance_name); +} + +/* + * destroy_instance - remove a trace instance and free the data + */ +static void destroy_instance(struct tracefs_instance *inst) +{ + tracefs_instance_destroy(inst); + tracefs_instance_free(inst); +} + +/** + * collect_registered_events - call the existing callback function for the event + * + * If an event has a registered callback function, call it. + * Otherwise, ignore the event. + * + * Returns 0 if the event was collected, 1 if the tool should stop collecting trace. + */ +int +collect_registered_events(struct tep_event *event, struct tep_record *record, + int cpu, void *context) +{ + struct trace_instance *trace = context; + struct trace_seq *s = trace->seq; + + if (should_stop()) + return 1; + + if (!event->handler) + return 0; + + event->handler(s, record, event, context); + + return 0; +} + +/** + * trace_instance_destroy - destroy and free a rv trace instance + */ +void trace_instance_destroy(struct trace_instance *trace) +{ + if (trace->inst) { + destroy_instance(trace->inst); + trace->inst = NULL; + } + + if (trace->seq) { + free(trace->seq); + trace->seq = NULL; + } + + if (trace->tep) { + tep_free(trace->tep); + trace->tep = NULL; + } +} + +/** + * trace_instance_init - create an trace instance + * + * It is more than the tracefs instance, as it contains other + * things required for the tracing, such as the local events and + * a seq file. + * + * Note that the trace instance is returned disabled. This allows + * the tool to apply some other configs, like setting priority + * to the kernel threads, before starting generating trace entries. + * + * Returns 0 on success, non-zero otherwise. + */ +int trace_instance_init(struct trace_instance *trace, char *name) +{ + trace->seq = calloc(1, sizeof(*trace->seq)); + if (!trace->seq) + goto out_err; + + trace_seq_init(trace->seq); + + trace->inst = create_instance(name); + if (!trace->inst) + goto out_err; + + trace->tep = tracefs_local_events(NULL); + if (!trace->tep) + goto out_err; + + /* + * Let the main enable the record after setting some other + * things such as the priority of the tracer's threads. + */ + tracefs_trace_off(trace->inst); + + return 0; + +out_err: + trace_instance_destroy(trace); + return 1; +} + +/** + * trace_instance_start - start tracing a given rv instance + * + * Returns 0 on success, -1 otherwise. + */ +int trace_instance_start(struct trace_instance *trace) +{ + return tracefs_trace_on(trace->inst); +} diff --git a/tools/verification/rv/src/utils.c b/tools/verification/rv/src/utils.c new file mode 100644 index 000000000000..5677b439dc2f --- /dev/null +++ b/tools/verification/rv/src/utils.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * util functions. + * + * Copyright (C) 2022 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> + */ + +#include <stdarg.h> +#include <stdio.h> +#include <utils.h> + +int config_debug; + +#define MAX_MSG_LENGTH 1024 + +/** + * err_msg - print an error message to the stderr + */ +void err_msg(const char *fmt, ...) +{ + char message[MAX_MSG_LENGTH]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(message, sizeof(message), fmt, ap); + va_end(ap); + + fprintf(stderr, "%s", message); +} + +/** + * debug_msg - print a debug message to stderr if debug is set + */ +void debug_msg(const char *fmt, ...) +{ + char message[MAX_MSG_LENGTH]; + va_list ap; + + if (!config_debug) + return; + + va_start(ap, fmt); + vsnprintf(message, sizeof(message), fmt, ap); + va_end(ap); + + fprintf(stderr, "%s", message); +} |