xref: /linux/tools/verification/rvgen/rvgen/generator.py (revision 55ee4b931a7ffedc886175d265dd6e6d08fd4151)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0-only
3#
4# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
5#
6# Abstract class for generating kernel runtime verification monitors from specification file
7
8import platform
9from pathlib import Path
10
11
12class RVGenerator:
13    rv_dir = "kernel/trace/rv"
14
15    def __init__(self, extra_params={}):
16        self.name = extra_params.get("model_name")
17        self.parent = extra_params.get("parent")
18        self.abs_template_dir = \
19            Path(__file__).resolve().parent / "templates" / self.template_dir
20        self.main_c = self._read_template_file("main.c")
21        self.kconfig = self._read_template_file("Kconfig")
22        self.description = extra_params.get("description", self.name) or "auto-generated"
23        self.auto_patch = extra_params.get("auto_patch")
24        if self.auto_patch:
25            self._fill_rv_kernel_dir()
26
27    def _fill_rv_kernel_dir(self):
28        # find the kernel tree root relative to this file's location
29        resolved_path = Path(__file__).resolve()
30        if len(resolved_path.parents) > 4:
31            kernel_root = resolved_path.parents[4]
32            kernel_path = kernel_root / self.rv_dir
33
34            if kernel_path.exists():
35                self.rv_dir = str(kernel_path)
36                return
37
38        # best effort if rvgen is installed and we are at the root of a kernel tree
39        if Path(self.rv_dir).exists():
40            return
41
42        if platform.system() != "Linux":
43            raise OSError("I can only run on Linux.")
44
45        kernel_path = Path(f"/lib/modules/{platform.release()}/build") / self.rv_dir
46
47        # if the current kernel is from a distro this may not be a full kernel tree
48        # verify that one of the files we are going to modify is available
49        if (kernel_path / "rv_trace.h").exists():
50            self.rv_dir = str(kernel_path)
51            return
52
53        raise FileNotFoundError("Could not find the rv directory, do you have the kernel source installed?")
54
55    def _read_file(self, path):
56        with open(path, 'r') as fd:
57            content = fd.read()
58        return content
59
60    def _read_template_file(self, file):
61        try:
62            path = self.abs_template_dir / file
63            return self._read_file(path)
64        except OSError:
65            # Specific template file not found. Try the generic template file in the template/
66            # directory, which is one level up
67            path = self.abs_template_dir.parent / file
68            return self._read_file(path)
69
70    def fill_parent(self):
71        return f"&rv_{self.parent}" if self.parent else "NULL"
72
73    def fill_include_parent(self):
74        if self.parent:
75            return f"#include <monitors/{self.parent}/{self.parent}.h>\n"
76        return ""
77
78    def fill_tracepoint_handlers_skel(self):
79        return "NotImplemented"
80
81    def fill_tracepoint_attach_probe(self):
82        return "NotImplemented"
83
84    def fill_tracepoint_detach_helper(self):
85        return "NotImplemented"
86
87    def fill_main_c(self):
88        main_c = self.main_c
89        tracepoint_handlers = self.fill_tracepoint_handlers_skel()
90        tracepoint_attach = self.fill_tracepoint_attach_probe()
91        tracepoint_detach = self.fill_tracepoint_detach_helper()
92        parent = self.fill_parent()
93        parent_include = self.fill_include_parent()
94
95        main_c = main_c.replace("%%MODEL_NAME%%", self.name)
96        main_c = main_c.replace("%%TRACEPOINT_HANDLERS_SKEL%%", tracepoint_handlers)
97        main_c = main_c.replace("%%TRACEPOINT_ATTACH%%", tracepoint_attach)
98        main_c = main_c.replace("%%TRACEPOINT_DETACH%%", tracepoint_detach)
99        main_c = main_c.replace("%%DESCRIPTION%%", self.description)
100        main_c = main_c.replace("%%PARENT%%", parent)
101        main_c = main_c.replace("%%INCLUDE_PARENT%%", parent_include)
102
103        return main_c
104
105    def fill_model_h(self):
106        return "NotImplemented"
107
108    def fill_monitor_class_type(self):
109        return "NotImplemented"
110
111    def fill_monitor_class(self):
112        return "NotImplemented"
113
114    def fill_tracepoint_args_skel(self, tp_type):
115        return "NotImplemented"
116
117    def fill_monitor_deps(self):
118        buff = []
119        buff.append("	# XXX: add dependencies if there")
120        if self.parent:
121            buff.append(f"	depends on RV_MON_{self.parent.upper()}")
122            buff.append("	default y")
123        return '\n'.join(buff)
124
125    def fill_kconfig(self):
126        kconfig = self.kconfig
127        monitor_class_type = self.fill_monitor_class_type()
128        monitor_deps = self.fill_monitor_deps()
129        kconfig = kconfig.replace("%%MODEL_NAME%%", self.name)
130        kconfig = kconfig.replace("%%MODEL_NAME_UP%%", self.name.upper())
131        kconfig = kconfig.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
132        kconfig = kconfig.replace("%%DESCRIPTION%%", self.description)
133        kconfig = kconfig.replace("%%MONITOR_DEPS%%", monitor_deps)
134        return kconfig
135
136    def _patch_file(self, file, marker, line):
137        assert self.auto_patch
138        file_to_patch = Path(self.rv_dir) / file
139        content = self._read_file(file_to_patch)
140        content = content.replace(marker, line + "\n" + marker)
141        self.__write_file(file_to_patch, content)
142
143    def fill_tracepoint_tooltip(self):
144        monitor_class_type = self.fill_monitor_class_type()
145        if self.auto_patch:
146            self._patch_file("rv_trace.h",
147                            f"// Add new monitors based on CONFIG_{monitor_class_type} here",
148                            f"#include <monitors/{self.name}/{self.name}_trace.h>")
149            return f"  - Patching {self.rv_dir}/rv_trace.h, double check the result"
150
151        return f"""  - Edit {self.rv_dir}/rv_trace.h:
152Add this line where other tracepoints are included and {monitor_class_type} is defined:
153#include <monitors/{self.name}/{self.name}_trace.h>
154"""
155
156    def _kconfig_marker(self, container=None) -> str:
157        return f"# Add new {container + ' ' if container else ''}monitors here"
158
159    def fill_kconfig_tooltip(self):
160        if self.auto_patch:
161            # monitors with a container should stay together in the Kconfig
162            self._patch_file("Kconfig",
163                             self._kconfig_marker(self.parent),
164                            f"source \"kernel/trace/rv/monitors/{self.name}/Kconfig\"")
165            return f"  - Patching {self.rv_dir}/Kconfig, double check the result"
166
167        return f"""  - Edit {self.rv_dir}/Kconfig:
168Add this line where other monitors are included:
169source \"kernel/trace/rv/monitors/{self.name}/Kconfig\"
170"""
171
172    def fill_makefile_tooltip(self):
173        name = self.name
174        name_up = name.upper()
175        if self.auto_patch:
176            self._patch_file("Makefile",
177                            "# Add new monitors here",
178                            f"obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o")
179            return f"  - Patching {self.rv_dir}/Makefile, double check the result"
180
181        return f"""  - Edit {self.rv_dir}/Makefile:
182Add this line where other monitors are included:
183obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
184"""
185
186    def fill_monitor_tooltip(self):
187        if self.auto_patch:
188            return f"  - Monitor created in {self.rv_dir}/monitors/{self.name}"
189        return f"  - Move {self.name}/ to the kernel's monitor directory ({self.rv_dir}/monitors)"
190
191    def __create_directory(self):
192        path = Path(self.name)
193        if self.auto_patch:
194            path = Path(self.rv_dir) / "monitors" / path
195        path.mkdir(exist_ok=True)
196
197    def __write_file(self, file_name, content):
198        with open(file_name, 'w') as file:
199            file.write(content)
200
201    def _create_file(self, file_name, content):
202        path = Path(self.name) / file_name
203        if self.auto_patch:
204            path = Path(self.rv_dir) / "monitors" / self.name / file_name
205        self.__write_file(path, content)
206
207    def print_files(self):
208        main_c = self.fill_main_c()
209
210        self.__create_directory()
211
212        path = f"{self.name}.c"
213        self._create_file(path, main_c)
214
215        model_h = self.fill_model_h()
216        path = f"{self.name}.h"
217        self._create_file(path, model_h)
218
219        kconfig = self.fill_kconfig()
220        self._create_file("Kconfig", kconfig)
221
222
223class Monitor(RVGenerator):
224    monitor_types = {"global": 1, "per_cpu": 2, "per_task": 3, "per_obj": 4}
225
226    def __init__(self, extra_params={}):
227        super().__init__(extra_params)
228        self.trace_h = self._read_template_file("trace.h")
229
230    def fill_trace_h(self):
231        trace_h = self.trace_h
232        monitor_class = self.fill_monitor_class()
233        monitor_class_type = self.fill_monitor_class_type()
234        tracepoint_args_skel_event = self.fill_tracepoint_args_skel("event")
235        tracepoint_args_skel_error = self.fill_tracepoint_args_skel("error")
236        tracepoint_args_skel_error_env = self.fill_tracepoint_args_skel("error_env")
237        trace_h = trace_h.replace("%%MODEL_NAME%%", self.name)
238        trace_h = trace_h.replace("%%MODEL_NAME_UP%%", self.name.upper())
239        trace_h = trace_h.replace("%%MONITOR_CLASS%%", monitor_class)
240        trace_h = trace_h.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
241        trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_EVENT%%", tracepoint_args_skel_event)
242        trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR%%", tracepoint_args_skel_error)
243        trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR_ENV%%", tracepoint_args_skel_error_env)
244        return trace_h
245
246    def print_files(self):
247        super().print_files()
248        trace_h = self.fill_trace_h()
249        path = f"{self.name}_trace.h"
250        self._create_file(path, trace_h)
251