verification/rvgen: Add support for linear temporal logic

Add support for generating RV monitors from linear temporal logic, similar
to the generation of deterministic automaton monitors.

Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Gabriele Monaco <gmonaco@redhat.com>
Link: https://lore.kernel.org/f3c63b363ff9c5af3302ba2b5d92a26a98700eaf.1751634289.git.namcao@linutronix.de
Signed-off-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
This commit is contained in:
Nam Cao
2025-07-24 10:42:47 -04:00
committed by Steven Rostedt (Google)
parent cce86e03a2
commit 97ffa4ce6a
7 changed files with 915 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
__pycache__/
parser.out
parsetab.py
+2
View File
@@ -21,5 +21,7 @@ install:
$(INSTALL) rvgen/dot2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2k.py
$(INSTALL) rvgen/container.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/container.py
$(INSTALL) rvgen/generator.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/generator.py
$(INSTALL) rvgen/ltl2ba.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/ltl2ba.py
$(INSTALL) rvgen/ltl2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/ltl2k.py
$(INSTALL) __main__.py -D -m 755 $(DESTDIR)$(bindir)/rvgen
cp -rp rvgen/templates $(DESTDIR)$(PYLIB)/rvgen/
+2 -1
View File
@@ -12,6 +12,7 @@ if __name__ == '__main__':
from rvgen.dot2k import dot2k
from rvgen.generator import Monitor
from rvgen.container import Container
from rvgen.ltl2k import ltl2k
import argparse
import sys
@@ -44,7 +45,7 @@ if __name__ == '__main__':
if params.monitor_class == "da":
monitor = dot2k(params.spec, params.monitor_type, vars(params))
elif params.monitor_class == "ltl":
raise NotImplementedError
monitor = ltl2k(params.spec, params.monitor_type, vars(params))
else:
print("Unknown monitor class:", params.monitor_class)
sys.exit(1)
File diff suppressed because it is too large Load Diff
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
from pathlib import Path
from . import generator
from . import ltl2ba
COLUMN_LIMIT = 100
def line_len(line: str) -> int:
tabs = line.count('\t')
return tabs * 7 + len(line)
def break_long_line(line: str, indent='') -> list[str]:
result = []
while line_len(line) > COLUMN_LIMIT:
i = line[:COLUMN_LIMIT - line_len(line)].rfind(' ')
result.append(line[:i])
line = indent + line[i + 1:]
if line:
result.append(line)
return result
def build_condition_string(node: ltl2ba.GraphNode):
if not node.labels:
return "(true)"
result = "("
first = True
for label in sorted(node.labels):
if not first:
result += " && "
result += label
first = False
result += ")"
return result
def abbreviate_atoms(atoms: list[str]) -> list[str]:
def shorten(s: str) -> str:
skip = ["is", "by", "or", "and"]
return '_'.join([x[:2] for x in s.lower().split('_') if x not in skip])
abbrs = []
for atom in atoms:
for i in range(len(atom), -1, -1):
if sum(a.startswith(atom[:i]) for a in atoms) > 1:
break
share = atom[:i]
unique = atom[i:]
abbrs.append((shorten(share) + shorten(unique)))
return abbrs
class ltl2k(generator.Monitor):
template_dir = "ltl2k"
def __init__(self, file_path, MonitorType, extra_params={}):
if MonitorType != "per_task":
raise NotImplementedError("Only per_task monitor is supported for LTL")
super().__init__(extra_params)
with open(file_path) as f:
self.atoms, self.ba, self.ltl = ltl2ba.create_graph(f.read())
self.atoms_abbr = abbreviate_atoms(self.atoms)
self.name = extra_params.get("model_name")
if not self.name:
self.name = Path(file_path).stem
def _fill_states(self) -> str:
buf = [
"enum ltl_buchi_state {",
]
for node in self.ba:
buf.append("\tS%i," % node.id)
buf.append("\tRV_NUM_BA_STATES")
buf.append("};")
buf.append("static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);")
return buf
def _fill_atoms(self):
buf = ["enum ltl_atom {"]
for a in sorted(self.atoms):
buf.append("\tLTL_%s," % a)
buf.append("\tLTL_NUM_ATOM")
buf.append("};")
buf.append("static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);")
return buf
def _fill_atoms_to_string(self):
buf = [
"static const char *ltl_atom_str(enum ltl_atom atom)",
"{",
"\tstatic const char *const names[] = {"
]
for name in self.atoms_abbr:
buf.append("\t\t\"%s\"," % name)
buf.extend([
"\t};",
"",
"\treturn names[atom];",
"}"
])
return buf
def _fill_atom_values(self):
buf = []
for node in self.ltl:
if node.op.is_temporal():
continue
if isinstance(node.op, ltl2ba.Variable):
buf.append("\tbool %s = test_bit(LTL_%s, mon->atoms);" % (node, node.op.name))
elif isinstance(node.op, ltl2ba.AndOp):
buf.append("\tbool %s = %s && %s;" % (node, node.op.left, node.op.right))
elif isinstance(node.op, ltl2ba.OrOp):
buf.append("\tbool %s = %s || %s;" % (node, node.op.left, node.op.right))
elif isinstance(node.op, ltl2ba.NotOp):
buf.append("\tbool %s = !%s;" % (node, node.op.child))
buf.reverse()
buf2 = []
for line in buf:
buf2.extend(break_long_line(line, "\t "))
return buf2
def _fill_transitions(self):
buf = [
"static void",
"ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)",
"{"
]
buf.extend(self._fill_atom_values())
buf.extend([
"",
"\tswitch (state) {"
])
for node in self.ba:
buf.append("\tcase S%i:" % node.id)
for o in sorted(node.outgoing):
line = "\t\tif "
indent = "\t\t "
line += build_condition_string(o)
lines = break_long_line(line, indent)
buf.extend(lines)
buf.append("\t\t\t__set_bit(S%i, next);" % o.id)
buf.append("\t\tbreak;")
buf.extend([
"\t}",
"}"
])
return buf
def _fill_start(self):
buf = [
"static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)",
"{"
]
buf.extend(self._fill_atom_values())
buf.append("")
for node in self.ba:
if not node.init:
continue
line = "\tif "
indent = "\t "
line += build_condition_string(node)
lines = break_long_line(line, indent)
buf.extend(lines)
buf.append("\t\t__set_bit(S%i, mon->states);" % node.id)
buf.append("}")
return buf
def fill_tracepoint_handlers_skel(self):
buff = []
buff.append("static void handle_example_event(void *data, /* XXX: fill header */)")
buff.append("{")
buff.append("\tltl_atom_update(task, LTL_%s, true/false);" % self.atoms[0])
buff.append("}")
buff.append("")
return '\n'.join(buff)
def fill_tracepoint_attach_probe(self):
return "\trv_attach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_example_event);" \
% self.name
def fill_tracepoint_detach_helper(self):
return "\trv_detach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_sample_event);" \
% self.name
def fill_atoms_init(self):
buff = []
for a in self.atoms:
buff.append("\tltl_atom_set(mon, LTL_%s, true/false);" % a)
return '\n'.join(buff)
def fill_model_h(self):
buf = [
"/* SPDX-License-Identifier: GPL-2.0 */",
"",
"/*",
" * C implementation of Buchi automaton, automatically generated by",
" * tools/verification/rvgen from the linear temporal logic specification.",
" * For further information, see kernel documentation:",
" * Documentation/trace/rv/linear_temporal_logic.rst",
" */",
"",
"#include <linux/rv.h>",
"",
"#define MONITOR_NAME " + self.name,
""
]
buf.extend(self._fill_atoms())
buf.append('')
buf.extend(self._fill_atoms_to_string())
buf.append('')
buf.extend(self._fill_states())
buf.append('')
buf.extend(self._fill_start())
buf.append('')
buf.extend(self._fill_transitions())
buf.append('')
return '\n'.join(buf)
def fill_monitor_class_type(self):
return "LTL_MON_EVENTS_ID"
def fill_monitor_class(self):
return "ltl_monitor_id"
def fill_main_c(self):
main_c = super().fill_main_c()
main_c = main_c.replace("%%ATOMS_INIT%%", self.fill_atoms_init())
return main_c
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "%%MODEL_NAME%%"
/*
* XXX: include required tracepoint headers, e.g.,
* #include <trace/events/sched.h>
*/
#include <rv_trace.h>
%%INCLUDE_PARENT%%
/*
* This is the self-generated part of the monitor. Generally, there is no need
* to touch this section.
*/
#include "%%MODEL_NAME%%.h"
#include <rv/ltl_monitor.h>
static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
{
/*
* This is called everytime the Buchi automaton is triggered.
*
* This function could be used to fetch the atomic propositions which
* are expensive to trace. It is possible only if the atomic proposition
* does not need to be updated at precise time.
*
* It is recommended to use tracepoints and ltl_atom_update() instead.
*/
}
static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
{
/*
* This should initialize as many atomic propositions as possible.
*
* @task_creation indicates whether the task is being created. This is
* false if the task is already running before the monitor is enabled.
*/
%%ATOMS_INIT%%
}
/*
* This is the instrumentation part of the monitor.
*
* This is the section where manual work is required. Here the kernel events
* are translated into model's event.
*/
%%TRACEPOINT_HANDLERS_SKEL%%
static int enable_%%MODEL_NAME%%(void)
{
int retval;
retval = ltl_monitor_init();
if (retval)
return retval;
%%TRACEPOINT_ATTACH%%
return 0;
}
static void disable_%%MODEL_NAME%%(void)
{
%%TRACEPOINT_DETACH%%
ltl_monitor_destroy();
}
/*
* This is the monitor register section.
*/
static struct rv_monitor rv_%%MODEL_NAME%% = {
.name = "%%MODEL_NAME%%",
.description = "%%DESCRIPTION%%",
.enable = enable_%%MODEL_NAME%%,
.disable = disable_%%MODEL_NAME%%,
};
static int __init register_%%MODEL_NAME%%(void)
{
return rv_register_monitor(&rv_%%MODEL_NAME%%, %%PARENT%%);
}
static void __exit unregister_%%MODEL_NAME%%(void)
{
rv_unregister_monitor(&rv_%%MODEL_NAME%%);
}
module_init(register_%%MODEL_NAME%%);
module_exit(unregister_%%MODEL_NAME%%);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(/* TODO */);
MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_%%MODEL_NAME_UP%%
DEFINE_EVENT(event_%%MONITOR_CLASS%%, event_%%MODEL_NAME%%,
TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
TP_ARGS(task, states, atoms, next));
DEFINE_EVENT(error_%%MONITOR_CLASS%%, error_%%MODEL_NAME%%,
TP_PROTO(struct task_struct *task),
TP_ARGS(task));
#endif /* CONFIG_RV_MON_%%MODEL_NAME_UP%% */