Merge tag 'trace-rv-6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace

Pull runtime verification updates from Steven Rostedt:

 - Added Linear temporal logic monitors for RT application

   Real-time applications may have design flaws causing them to have
   unexpected latency. For example, the applications may raise page
   faults, or may be blocked trying to take a mutex without priority
   inheritance.

   However, while attempting to implement DA monitors for these
   real-time rules, deterministic automaton is found to be inappropriate
   as the specification language. The automaton is complicated, hard to
   understand, and error-prone.

   For these cases, linear temporal logic is found to be more suitable.
   The LTL is more concise and intuitive.

 - Make printk_deferred() public

   The new monitors needed access to printk_deferred(). Make them
   visible for the entire kernel.

 - Add a vpanic() to allow for va_list to be passed to panic.

 - Add rtapp container monitor.

   A collection of monitors that check for common problems with
   real-time applications that cause unexpected latency.

 - Add page fault tracepoints to risc-v

   These tracepoints are necessary to for the RV monitor to run on
   risc-v.

 - Fix the behaviour of the rv tool with -s and idle tasks.

 - Allow the rv tool to gracefully terminate with SIGTERM

 - Adjusts dot2c not to create lines over 100 columns

 - Properly order nested monitors in the RV Kconfig file

 - Return the registration error in all DA monitor instead of 0

 - Update and add new sched collection monitors

   Replace tss and sncid monitors with more complete sts:

   Not only prove that switches occur in scheduling context and scheduling
   needs interrupt disabled but also that each call to the scheduler
   disables interrupts to (optionally) switch.

   New monitor: nrp
     Preemption requires need resched which is cleared by any switch
     (includes a non optimal workaround for /nested/ preemptions)

   New monitor: sssw
     suspension requires setting the task to sleepable and, after the
     switch occurs, the task requires a wakeup to come back to runnable

   New monitor: opid
      waking and need-resched operations occur with interrupts and
      preemption disabled or in IRQ without explicitly disabling
      preemption"

* tag 'trace-rv-6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (48 commits)
  rv: Add opid per-cpu monitor
  rv: Add nrp and sssw per-task monitors
  rv: Replace tss and sncid monitors with more complete sts
  sched: Adapt sched tracepoints for RV task model
  rv: Retry when da monitor detects race conditions
  rv: Adjust monitor dependencies
  rv: Use strings in da monitors tracepoints
  rv: Remove trailing whitespace from tracepoint string
  rv: Add da_handle_start_run_event_ to per-task monitors
  rv: Fix wrong type cast in reactors_show() and monitor_reactor_show()
  rv: Fix wrong type cast in monitors_show()
  rv: Remove struct rv_monitor::reacting
  rv: Remove rv_reactor's reference counter
  rv: Merge struct rv_reactor_def into struct rv_reactor
  rv: Merge struct rv_monitor_def into struct rv_monitor
  rv: Remove unused field in struct rv_monitor_def
  rv: Return init error when registering monitors
  verification/rvgen: Organise Kconfig entries for nested monitors
  tools/dot2c: Fix generated files going over 100 column limit
  tools/rv: Stop gracefully also on SIGTERM
  ...
This commit is contained in:
Linus Torvalds
2025-07-30 16:23:12 -07:00
101 changed files with 4860 additions and 1265 deletions
@@ -1,147 +0,0 @@
Deterministic Automata Monitor Synthesis
========================================
The starting point for the application of runtime verification (RV) techniques
is the *specification* or *modeling* of the desired (or undesired) behavior
of the system under scrutiny.
The formal representation needs to be then *synthesized* into a *monitor*
that can then be used in the analysis of the trace of the system. The
*monitor* connects to the system via an *instrumentation* that converts
the events from the *system* to the events of the *specification*.
In Linux terms, the runtime verification monitors are encapsulated inside
the *RV monitor* abstraction. The RV monitor includes a set of instances
of the monitor (per-cpu monitor, per-task monitor, and so on), the helper
functions that glue the monitor to the system reference model, and the
trace output as a reaction to event parsing and exceptions, as depicted
below::
Linux +----- RV Monitor ----------------------------------+ Formal
Realm | | Realm
+-------------------+ +----------------+ +-----------------+
| Linux kernel | | Monitor | | Reference |
| Tracing | -> | Instance(s) | <- | Model |
| (instrumentation) | | (verification) | | (specification) |
+-------------------+ +----------------+ +-----------------+
| | |
| V |
| +----------+ |
| | Reaction | |
| +--+--+--+-+ |
| | | | |
| | | +-> trace output ? |
+------------------------|--|----------------------+
| +----> panic ?
+-------> <user-specified>
DA monitor synthesis
--------------------
The synthesis of automata-based models into the Linux *RV monitor* abstraction
is automated by the dot2k tool and the rv/da_monitor.h header file that
contains a set of macros that automatically generate the monitor's code.
dot2k
-----
The dot2k utility leverages dot2c by converting an automaton model in
the DOT format into the C representation [1] and creating the skeleton of
a kernel monitor in C.
For example, it is possible to transform the wip.dot model present in
[1] into a per-cpu monitor with the following command::
$ dot2k -d wip.dot -t per_cpu
This will create a directory named wip/ with the following files:
- wip.h: the wip model in C
- wip.c: the RV monitor
The wip.c file contains the monitor declaration and the starting point for
the system instrumentation.
Monitor macros
--------------
The rv/da_monitor.h enables automatic code generation for the *Monitor
Instance(s)* using C macros.
The benefits of the usage of macro for monitor synthesis are 3-fold as it:
- Reduces the code duplication;
- Facilitates the bug fix/improvement;
- Avoids the case of developers changing the core of the monitor code
to manipulate the model in a (let's say) non-standard way.
This initial implementation presents three different types of monitor instances:
- ``#define DECLARE_DA_MON_GLOBAL(name, type)``
- ``#define DECLARE_DA_MON_PER_CPU(name, type)``
- ``#define DECLARE_DA_MON_PER_TASK(name, type)``
The first declares the functions for a global deterministic automata monitor,
the second for monitors with per-cpu instances, and the third with per-task
instances.
In all cases, the 'name' argument is a string that identifies the monitor, and
the 'type' argument is the data type used by dot2k on the representation of
the model in C.
For example, the wip model with two states and three events can be
stored in an 'unsigned char' type. Considering that the preemption control
is a per-cpu behavior, the monitor declaration in the 'wip.c' file is::
DECLARE_DA_MON_PER_CPU(wip, unsigned char);
The monitor is executed by sending events to be processed via the functions
presented below::
da_handle_event_$(MONITOR_NAME)($(event from event enum));
da_handle_start_event_$(MONITOR_NAME)($(event from event enum));
da_handle_start_run_event_$(MONITOR_NAME)($(event from event enum));
The function ``da_handle_event_$(MONITOR_NAME)()`` is the regular case where
the event will be processed if the monitor is processing events.
When a monitor is enabled, it is placed in the initial state of the automata.
However, the monitor does not know if the system is in the *initial state*.
The ``da_handle_start_event_$(MONITOR_NAME)()`` function is used to notify the
monitor that the system is returning to the initial state, so the monitor can
start monitoring the next event.
The ``da_handle_start_run_event_$(MONITOR_NAME)()`` function is used to notify
the monitor that the system is known to be in the initial state, so the
monitor can start monitoring and monitor the current event.
Using the wip model as example, the events "preempt_disable" and
"sched_waking" should be sent to monitor, respectively, via [2]::
da_handle_event_wip(preempt_disable_wip);
da_handle_event_wip(sched_waking_wip);
While the event "preempt_enabled" will use::
da_handle_start_event_wip(preempt_enable_wip);
To notify the monitor that the system will be returning to the initial state,
so the system and the monitor should be in sync.
Final remarks
-------------
With the monitor synthesis in place using the rv/da_monitor.h and
dot2k, the developer's work should be limited to the instrumentation
of the system, increasing the confidence in the overall approach.
[1] For details about deterministic automata format and the translation
from one representation to another, see::
Documentation/trace/rv/deterministic_automata.rst
[2] dot2k appends the monitor's name suffix to the events enums to
avoid conflicting variables when exporting the global vmlinux.h
use by BPF programs.
+3 -1
View File
@@ -8,8 +8,10 @@ Runtime Verification
runtime-verification.rst
deterministic_automata.rst
da_monitor_synthesis.rst
linear_temporal_logic.rst
monitor_synthesis.rst
da_monitor_instrumentation.rst
monitor_wip.rst
monitor_wwnr.rst
monitor_sched.rst
monitor_rtapp.rst
@@ -0,0 +1,134 @@
Linear temporal logic
=====================
Introduction
------------
Runtime verification monitor is a verification technique which checks that the
kernel follows a specification. It does so by using tracepoints to monitor the
kernel's execution trace, and verifying that the execution trace sastifies the
specification.
Initially, the specification can only be written in the form of deterministic
automaton (DA). However, while attempting to implement DA monitors for some
complex specifications, deterministic automaton is found to be inappropriate as
the specification language. The automaton is complicated, hard to understand,
and error-prone.
Thus, RV monitors based on linear temporal logic (LTL) are introduced. This type
of monitor uses LTL as specification instead of DA. For some cases, writing the
specification as LTL is more concise and intuitive.
Many materials explain LTL in details. One book is::
Christel Baier and Joost-Pieter Katoen: Principles of Model Checking, The MIT
Press, 2008.
Grammar
-------
Unlike some existing syntax, kernel's implementation of LTL is more verbose.
This is motivated by considering that the people who read the LTL specifications
may not be well-versed in LTL.
Grammar:
ltl ::= opd | ( ltl ) | ltl binop ltl | unop ltl
Operands (opd):
true, false, user-defined names consisting of upper-case characters, digits,
and underscore.
Unary Operators (unop):
always
eventually
next
not
Binary Operators (binop):
until
and
or
imply
equivalent
This grammar is ambiguous: operator precedence is not defined. Parentheses must
be used.
Example linear temporal logic
-----------------------------
.. code-block::
RAIN imply (GO_OUTSIDE imply HAVE_UMBRELLA)
means: if it is raining, going outside means having an umbrella.
.. code-block::
RAIN imply (WET until not RAIN)
means: if it is raining, it is going to be wet until the rain stops.
.. code-block::
RAIN imply eventually not RAIN
means: if it is raining, rain will eventually stop.
The above examples are referring to the current time instance only. For kernel
verification, the `always` operator is usually desirable, to specify that
something is always true at the present and for all future. For example::
always (RAIN imply eventually not RAIN)
means: *all* rain eventually stops.
In the above examples, `RAIN`, `GO_OUTSIDE`, `HAVE_UMBRELLA` and `WET` are the
"atomic propositions".
Monitor synthesis
-----------------
To synthesize an LTL into a kernel monitor, the `rvgen` tool can be used:
`tools/verification/rvgen`. The specification needs to be provided as a file,
and it must have a "RULE = LTL" assignment. For example::
RULE = always (ACQUIRE imply ((not KILLED and not CRASHED) until RELEASE))
which says: if `ACQUIRE`, then `RELEASE` must happen before `KILLED` or
`CRASHED`.
The LTL can be broken down using sub-expressions. The above is equivalent to:
.. code-block::
RULE = always (ACQUIRE imply (ALIVE until RELEASE))
ALIVE = not KILLED and not CRASHED
From this specification, `rvgen` generates the C implementation of a Buchi
automaton - a non-deterministic state machine which checks the satisfiability of
the LTL. See Documentation/trace/rv/monitor_synthesis.rst for details on using
`rvgen`.
References
----------
One book covering model checking and linear temporal logic is::
Christel Baier and Joost-Pieter Katoen: Principles of Model Checking, The MIT
Press, 2008.
For an example of using linear temporal logic in software testing, see::
Ruijie Meng, Zhen Dong, Jialin Li, Ivan Beschastnikh, and Abhik Roychoudhury.
2022. Linear-time temporal logic guided greybox fuzzing. In Proceedings of the
44th International Conference on Software Engineering (ICSE '22). Association
for Computing Machinery, New York, NY, USA, 13431355.
https://doi.org/10.1145/3510003.3510082
The kernel's LTL monitor implementation is based on::
Gerth, R., Peled, D., Vardi, M.Y., Wolper, P. (1996). Simple On-the-fly
Automatic Verification of Linear Temporal Logic. In: Dembiński, P., Średniawa,
M. (eds) Protocol Specification, Testing and Verification XV. PSTV 1995. IFIP
Advances in Information and Communication Technology. Springer, Boston, MA.
https://doi.org/10.1007/978-0-387-34892-6_1
+133
View File
@@ -0,0 +1,133 @@
Real-time application monitors
==============================
- Name: rtapp
- Type: container for multiple monitors
- Author: Nam Cao <namcao@linutronix.de>
Description
-----------
Real-time applications may have design flaws such that they experience
unexpected latency and fail to meet their time requirements. Often, these flaws
follow a few patterns:
- Page faults: A real-time thread may access memory that does not have a
mapped physical backing or must first be copied (such as for copy-on-write).
Thus a page fault is raised and the kernel must first perform the expensive
action. This causes significant delays to the real-time thread
- Priority inversion: A real-time thread blocks waiting for a lower-priority
thread. This causes the real-time thread to effectively take on the
scheduling priority of the lower-priority thread. For example, the real-time
thread needs to access a shared resource that is protected by a
non-pi-mutex, but the mutex is currently owned by a non-real-time thread.
The `rtapp` monitor detects these patterns. It aids developers to identify
reasons for unexpected latency with real-time applications. It is a container of
multiple sub-monitors described in the following sections.
Monitor pagefault
+++++++++++++++++
The `pagefault` monitor reports real-time tasks raising page faults. Its
specification is::
RULE = always (RT imply not PAGEFAULT)
To fix warnings reported by this monitor, `mlockall()` or `mlock()` can be used
to ensure physical backing for memory.
This monitor may have false negatives because the pages used by the real-time
threads may just happen to be directly available during testing. To minimize
this, the system can be put under memory pressure (e.g. invoking the OOM killer
using a program that does `ptr = malloc(SIZE_OF_RAM); memset(ptr, 0,
SIZE_OF_RAM);`) so that the kernel executes aggressive strategies to recycle as
much physical memory as possible.
Monitor sleep
+++++++++++++
The `sleep` monitor reports real-time threads sleeping in a manner that may
cause undesirable latency. Real-time applications should only put a real-time
thread to sleep for one of the following reasons:
- Cyclic work: real-time thread sleeps waiting for the next cycle. For this
case, only the `clock_nanosleep` syscall should be used with `TIMER_ABSTIME`
(to avoid time drift) and `CLOCK_MONOTONIC` (to avoid the clock being
changed). No other method is safe for real-time. For example, threads
waiting for timerfd can be woken by softirq which provides no real-time
guarantee.
- Real-time thread waiting for something to happen (e.g. another thread
releasing shared resources, or a completion signal from another thread). In
this case, only futexes (FUTEX_LOCK_PI, FUTEX_LOCK_PI2 or one of
FUTEX_WAIT_*) should be used. Applications usually do not use futexes
directly, but use PI mutexes and PI condition variables which are built on
top of futexes. Be aware that the C library might not implement conditional
variables as safe for real-time. As an alternative, the librtpi library
exists to provide a conditional variable implementation that is correct for
real-time applications in Linux.
Beside the reason for sleeping, the eventual waker should also be
real-time-safe. Namely, one of:
- An equal-or-higher-priority thread
- Hard interrupt handler
- Non-maskable interrupt handler
This monitor's warning usually means one of the following:
- Real-time thread is blocked by a non-real-time thread (e.g. due to
contention on a mutex without priority inheritance). This is priority
inversion.
- Time-critical work waits for something which is not safe for real-time (e.g.
timerfd).
- The work executed by the real-time thread does not need to run at real-time
priority at all. This is not a problem for the real-time thread itself, but
it is potentially taking the CPU away from other important real-time work.
Application developers may purposely choose to have their real-time application
sleep in a way that is not safe for real-time. It is debatable whether that is a
problem. Application developers must analyze the warnings to make a proper
assessment.
The monitor's specification is::
RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST))
RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD)
and ((not WAKE) until RT_FRIENDLY_WAKE)
RT_VALID_SLEEP_REASON = FUTEX_WAIT
or RT_FRIENDLY_NANOSLEEP
RT_FRIENDLY_NANOSLEEP = CLOCK_NANOSLEEP
and NANOSLEEP_TIMER_ABSTIME
and NANOSLEEP_CLOCK_MONOTONIC
RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO
or WOKEN_BY_HARDIRQ
or WOKEN_BY_NMI
or KTHREAD_SHOULD_STOP
ALLOWLIST = BLOCK_ON_RT_MUTEX
or FUTEX_LOCK_PI
or TASK_IS_RCU
or TASK_IS_MIGRATION
Beside the scenarios described above, this specification also handle some
special cases:
- `KERNEL_THREAD`: kernel tasks do not have any pattern that can be recognized
as valid real-time sleeping reasons. Therefore sleeping reason is not
checked for kernel tasks.
- `KTHREAD_SHOULD_STOP`: a non-real-time thread may stop a real-time kernel
thread by waking it and waiting for it to exit (`kthread_stop()`). This
wakeup is safe for real-time.
- `ALLOWLIST`: to handle known false positives with the kernel.
- `BLOCK_ON_RT_MUTEX` is included in the allowlist due to its implementation.
In the release path of rt_mutex, a boosted task is de-boosted before waking
the rt_mutex's waiter. Consequently, the monitor may see a real-time-unsafe
wakeup (e.g. non-real-time task waking real-time task). This is actually
real-time-safe because preemption is disabled for the duration.
- `FUTEX_LOCK_PI` is included in the allowlist for the same reason as
`BLOCK_ON_RT_MUTEX`.
+269 -38
View File
@@ -40,26 +40,6 @@ defined in by Daniel Bristot in [1].
Currently we included the following:
Monitor tss
~~~~~~~~~~~
The task switch while scheduling (tss) monitor ensures a task switch happens
only in scheduling context, that is inside a call to `__schedule`::
|
|
v
+-----------------+
| thread | <+
+-----------------+ |
| |
| schedule_entry | schedule_exit
v |
sched_switch |
+--------------- |
| sched |
+--------------> -+
Monitor sco
~~~~~~~~~~~
@@ -144,26 +124,277 @@ does not enable preemption::
|
scheduling_contex -+
Monitor sncid
~~~~~~~~~~~~~
Monitor sts
~~~~~~~~~~~
The schedule not called with interrupt disabled (sncid) monitor ensures
schedule is not called with interrupt disabled::
The schedule implies task switch (sts) monitor ensures a task switch happens
only in scheduling context and up to once, as well as scheduling occurs with
interrupts enabled but no task switch can happen before interrupts are
disabled. When the next task picked for execution is the same as the previously
running one, no real task switch occurs but interrupts are disabled nonetheless::
|
|
v
schedule_entry +--------------+
schedule_exit | |
+----------------- | can_sched |
| | |
+----------------> | | <+
+--------------+ |
| |
| irq_disable | irq_enable
v |
|
cant_sched -+
irq_entry |
+----+ |
v | v
+------------+ irq_enable #===================# irq_disable
| | ------------> H H irq_entry
| cant_sched | <------------ H H irq_enable
| | irq_disable H can_sched H --------------+
+------------+ H H |
H H |
+---------------> H H <-------------+
| #===================#
| |
schedule_exit | schedule_entry
| v
| +-------------------+ irq_enable
| | scheduling | <---------------+
| +-------------------+ |
| | |
| | irq_disable +--------+ irq_entry
| v | | --------+
| +-------------------+ irq_entry | in_irq | |
| | | -----------> | | <-------+
| | disable_to_switch | +--------+
| | | --+
| +-------------------+ |
| | |
| | sched_switch |
| v |
| +-------------------+ |
| | switching | | irq_enable
| +-------------------+ |
| | |
| | irq_enable |
| v |
| +-------------------+ |
+-- | enable_to_exit | <-+
+-------------------+
^ | irq_disable
| | irq_entry
+---------------+ irq_enable
Monitor nrp
-----------
The need resched preempts (nrp) monitor ensures preemption requires
``need_resched``. Only kernel preemption is considered, since preemption
while returning to userspace, for this monitor, is indistinguishable from
``sched_switch_yield`` (described in the sssw monitor).
A kernel preemption is whenever ``__schedule`` is called with the preemption
flag set to true (e.g. from preempt_enable or exiting from interrupts). This
type of preemption occurs after the need for ``rescheduling`` has been set.
This is not valid for the *lazy* variant of the flag, which causes only
userspace preemption.
A ``schedule_entry_preempt`` may involve a task switch or not, in the latter
case, a task goes through the scheduler from a preemption context but it is
picked as the next task to run. Since the scheduler runs, this clears the need
to reschedule. The ``any_thread_running`` state does not imply the monitored
task is not running as this monitor does not track the outcome of scheduling.
In theory, a preemption can only occur after the ``need_resched`` flag is set. In
practice, however, it is possible to see a preemption where the flag is not
set. This can happen in one specific condition::
need_resched
preempt_schedule()
preempt_schedule_irq()
__schedule()
!need_resched
__schedule()
In the situation above, standard preemption starts (e.g. from preempt_enable
when the flag is set), an interrupt occurs before scheduling and, on its exit
path, it schedules, which clears the ``need_resched`` flag.
When the preempted task runs again, the standard preemption started earlier
resumes, although the flag is no longer set. The monitor considers this a
``nested_preemption``, this allows another preemption without re-setting the
flag. This condition relaxes the monitor constraints and may catch false
negatives (i.e. no real ``nested_preemptions``) but makes the monitor more
robust and able to validate other scenarios.
For simplicity, the monitor starts in ``preempt_irq``, although no interrupt
occurred, as the situation above is hard to pinpoint::
schedule_entry
irq_entry #===========================================#
+-------------------------- H H
| H H
+-------------------------> H any_thread_running H
H H
+-------------------------> H H
| #===========================================#
| schedule_entry | ^
| schedule_entry_preempt | sched_need_resched | schedule_entry
| | schedule_entry_preempt
| v |
| +----------------------+ |
| +--- | | |
| sched_need_resched | | rescheduling | -+
| +--> | |
| +----------------------+
| | irq_entry
| v
| +----------------------+
| | | ---+
| ---> | | | sched_need_resched
| | preempt_irq | | irq_entry
| | | <--+
| | | <--+
| +----------------------+ |
| | schedule_entry | sched_need_resched
| | schedule_entry_preempt |
| v |
| +-----------------------+ |
+-------------------------- | nested_preempt | --+
+-----------------------+
^ irq_entry |
+-------------------+
Due to how the ``need_resched`` flag on the preemption count works on arm64,
this monitor is unstable on that architecture, as it often records preemption
when the flag is not set, even in presence of the workaround above.
For the time being, the monitor is disabled by default on arm64.
Monitor sssw
------------
The set state sleep and wakeup (sssw) monitor ensures ``set_state`` to
sleepable leads to sleeping and sleeping tasks require wakeup. It includes the
following types of switch:
* ``switch_suspend``:
a task puts itself to sleep, this can happen only after explicitly setting
the task to ``sleepable``. After a task is suspended, it needs to be woken up
(``waking`` state) before being switched in again.
Setting the task's state to ``sleepable`` can be reverted before switching if it
is woken up or set to ``runnable``.
* ``switch_blocking``:
a special case of a ``switch_suspend`` where the task is waiting on a
sleeping RT lock (``PREEMPT_RT`` only), it is common to see wakeup and set
state events racing with each other and this leads the model to perceive this
type of switch when the task is not set to sleepable. This is a limitation of
the model in SMP system and workarounds may slow down the system.
* ``switch_preempt``:
a task switch as a result of kernel preemption (``schedule_entry_preempt`` in
the nrp model).
* ``switch_yield``:
a task explicitly calls the scheduler or is preempted while returning to
userspace. It can happen after a ``yield`` system call, from the idle task or
if the ``need_resched`` flag is set. By definition, a task cannot yield while
``sleepable`` as that would be a suspension. A special case of a yield occurs
when a task in ``TASK_INTERRUPTIBLE`` calls the scheduler while a signal is
pending. The task doesn't go through the usual blocking/waking and is set
back to runnable, the resulting switch (if there) looks like a yield to the
``signal_wakeup`` state and is followed by the signal delivery. From this
state, the monitor expects a signal even if it sees a wakeup event, although
not necessary, to rule out false negatives.
This monitor doesn't include a running state, ``sleepable`` and ``runnable``
are only referring to the task's desired state, which could be scheduled out
(e.g. due to preemption). However, it does include the event
``sched_switch_in`` to represent when a task is allowed to become running. This
can be triggered also by preemption, but cannot occur after the task got to
``sleeping`` before a ``wakeup`` occurs::
+--------------------------------------------------------------------------+
| |
| |
| switch_suspend | |
| switch_blocking | |
v v |
+----------+ #==========================# set_state_runnable |
| | H H wakeup |
| | H H switch_in |
| | H H switch_yield |
| sleeping | H H switch_preempt |
| | H H signal_deliver |
| | switch_ H H ------+ |
| | _blocking H runnable H | |
| | <----------- H H <-----+ |
+----------+ H H |
| wakeup H H |
+---------------------> H H |
H H |
+---------> H H |
| #==========================# |
| | ^ |
| | | set_state_runnable |
| | | wakeup |
| set_state_sleepable | +------------------------+
| v | |
| +--------------------------+ set_state_sleepable
| | | switch_in
| | | switch_preempt
signal_deliver | sleepable | signal_deliver
| | | ------+
| | | |
| | | <-----+
| +--------------------------+
| | ^
| switch_yield | set_state_sleepable
| v |
| +---------------+ |
+---------- | signal_wakeup | -+
+---------------+
^ | switch_in
| | switch_preempt
| | switch_yield
+-----------+ wakeup
Monitor opid
------------
The operations with preemption and irq disabled (opid) monitor ensures
operations like ``wakeup`` and ``need_resched`` occur with interrupts and
preemption disabled or during interrupt context, in such case preemption may
not be disabled explicitly.
``need_resched`` can be set by some RCU internals functions, in which case it
doesn't match a task wakeup and might occur with only interrupts disabled::
| sched_need_resched
| sched_waking
| irq_entry
| +--------------------+
v v |
+------------------------------------------------------+
+----------- | disabled | <+
| +------------------------------------------------------+ |
| | ^ |
| | preempt_disable sched_need_resched |
| preempt_enable | +--------------------+ |
| v | v | |
| +------------------------------------------------------+ |
| | irq_disabled | |
| +------------------------------------------------------+ |
| | | ^ |
| irq_entry irq_entry | | |
| sched_need_resched v | irq_disable |
| sched_waking +--------------+ | | |
| +----- | | irq_enable | |
| | | in_irq | | | |
| +----> | | | | |
| +--------------+ | | irq_disable
| | | | |
| irq_enable | irq_enable | | |
| v v | |
| #======================================================# |
| H enabled H |
| #======================================================# |
| | ^ ^ preempt_enable | |
| preempt_disable preempt_enable +--------------------+ |
| v | |
| +------------------+ | |
+----------> | preempt_disabled | -+ |
+------------------+ |
| |
+-------------------------------------------------------+
This monitor is designed to work on ``PREEMPT_RT`` kernels, the special case of
events occurring in interrupt context is a shortcut to identify valid scenarios
where the preemption tracepoints might not be visible, during interrupts
preemption is always disabled. On non- ``PREEMPT_RT`` kernels, the interrupts
might invoke a softirq to set ``need_resched`` and wake up a task. This is
another special case that is currently not supported by the monitor.
References
----------
@@ -0,0 +1,271 @@
Runtime Verification Monitor Synthesis
======================================
The starting point for the application of runtime verification (RV) techniques
is the *specification* or *modeling* of the desired (or undesired) behavior
of the system under scrutiny.
The formal representation needs to be then *synthesized* into a *monitor*
that can then be used in the analysis of the trace of the system. The
*monitor* connects to the system via an *instrumentation* that converts
the events from the *system* to the events of the *specification*.
In Linux terms, the runtime verification monitors are encapsulated inside
the *RV monitor* abstraction. The RV monitor includes a set of instances
of the monitor (per-cpu monitor, per-task monitor, and so on), the helper
functions that glue the monitor to the system reference model, and the
trace output as a reaction to event parsing and exceptions, as depicted
below::
Linux +----- RV Monitor ----------------------------------+ Formal
Realm | | Realm
+-------------------+ +----------------+ +-----------------+
| Linux kernel | | Monitor | | Reference |
| Tracing | -> | Instance(s) | <- | Model |
| (instrumentation) | | (verification) | | (specification) |
+-------------------+ +----------------+ +-----------------+
| | |
| V |
| +----------+ |
| | Reaction | |
| +--+--+--+-+ |
| | | | |
| | | +-> trace output ? |
+------------------------|--|----------------------+
| +----> panic ?
+-------> <user-specified>
RV monitor synthesis
--------------------
The synthesis of a specification into the Linux *RV monitor* abstraction is
automated by the rvgen tool and the header file containing common code for
creating monitors. The header files are:
* rv/da_monitor.h for deterministic automaton monitor.
* rv/ltl_monitor.h for linear temporal logic monitor.
rvgen
-----
The rvgen utility converts a specification into the C presentation and creating
the skeleton of a kernel monitor in C.
For example, it is possible to transform the wip.dot model present in
[1] into a per-cpu monitor with the following command::
$ rvgen monitor -c da -s wip.dot -t per_cpu
This will create a directory named wip/ with the following files:
- wip.h: the wip model in C
- wip.c: the RV monitor
The wip.c file contains the monitor declaration and the starting point for
the system instrumentation.
Similarly, a linear temporal logic monitor can be generated with the following
command::
$ rvgen monitor -c ltl -s pagefault.ltl -t per_task
This generates pagefault/ directory with:
- pagefault.h: The Buchi automaton (the non-deterministic state machine to
verify the specification)
- pagefault.c: The skeleton for the RV monitor
Monitor header files
--------------------
The header files:
- `rv/da_monitor.h` for deterministic automaton monitor
- `rv/ltl_monitor` for linear temporal logic monitor
include common macros and static functions for implementing *Monitor
Instance(s)*.
The benefits of having all common functionalities in a single header file are
3-fold:
- Reduce the code duplication;
- Facilitate the bug fix/improvement;
- Avoid the case of developers changing the core of the monitor code to
manipulate the model in a (let's say) non-standard way.
rv/da_monitor.h
+++++++++++++++
This initial implementation presents three different types of monitor instances:
- ``#define DECLARE_DA_MON_GLOBAL(name, type)``
- ``#define DECLARE_DA_MON_PER_CPU(name, type)``
- ``#define DECLARE_DA_MON_PER_TASK(name, type)``
The first declares the functions for a global deterministic automata monitor,
the second for monitors with per-cpu instances, and the third with per-task
instances.
In all cases, the 'name' argument is a string that identifies the monitor, and
the 'type' argument is the data type used by rvgen on the representation of
the model in C.
For example, the wip model with two states and three events can be
stored in an 'unsigned char' type. Considering that the preemption control
is a per-cpu behavior, the monitor declaration in the 'wip.c' file is::
DECLARE_DA_MON_PER_CPU(wip, unsigned char);
The monitor is executed by sending events to be processed via the functions
presented below::
da_handle_event_$(MONITOR_NAME)($(event from event enum));
da_handle_start_event_$(MONITOR_NAME)($(event from event enum));
da_handle_start_run_event_$(MONITOR_NAME)($(event from event enum));
The function ``da_handle_event_$(MONITOR_NAME)()`` is the regular case where
the event will be processed if the monitor is processing events.
When a monitor is enabled, it is placed in the initial state of the automata.
However, the monitor does not know if the system is in the *initial state*.
The ``da_handle_start_event_$(MONITOR_NAME)()`` function is used to notify the
monitor that the system is returning to the initial state, so the monitor can
start monitoring the next event.
The ``da_handle_start_run_event_$(MONITOR_NAME)()`` function is used to notify
the monitor that the system is known to be in the initial state, so the
monitor can start monitoring and monitor the current event.
Using the wip model as example, the events "preempt_disable" and
"sched_waking" should be sent to monitor, respectively, via [2]::
da_handle_event_wip(preempt_disable_wip);
da_handle_event_wip(sched_waking_wip);
While the event "preempt_enabled" will use::
da_handle_start_event_wip(preempt_enable_wip);
To notify the monitor that the system will be returning to the initial state,
so the system and the monitor should be in sync.
rv/ltl_monitor.h
++++++++++++++++
This file must be combined with the $(MODEL_NAME).h file (generated by `rvgen`)
to be complete. For example, for the `pagefault` monitor, the `pagefault.c`
source file must include::
#include "pagefault.h"
#include <rv/ltl_monitor.h>
(the skeleton monitor file generated by `rvgen` already does this).
`$(MODEL_NAME).h` (`pagefault.h` in the above example) includes the
implementation of the Buchi automaton - a non-deterministic state machine that
verifies the LTL specification. While `rv/ltl_monitor.h` includes the common
helper functions to interact with the Buchi automaton and to implement an RV
monitor. An important definition in `$(MODEL_NAME).h` is::
enum ltl_atom {
LTL_$(FIRST_ATOMIC_PROPOSITION),
LTL_$(SECOND_ATOMIC_PROPOSITION),
...
LTL_NUM_ATOM
};
which is the list of atomic propositions present in the LTL specification
(prefixed with "LTL\_" to avoid name collision). This `enum` is passed to the
functions interacting with the Buchi automaton.
While generating code, `rvgen` cannot understand the meaning of the atomic
propositions. Thus, that task is left for manual work. The recommended pratice
is adding tracepoints to places where the atomic propositions change; and in the
tracepoints' handlers: the Buchi automaton is executed using::
void ltl_atom_update(struct task_struct *task, enum ltl_atom atom, bool value)
which tells the Buchi automaton that the atomic proposition `atom` is now
`value`. The Buchi automaton checks whether the LTL specification is still
satisfied, and invokes the monitor's error tracepoint and the reactor if
violation is detected.
Tracepoints and `ltl_atom_update()` should be used whenever possible. However,
it is sometimes not the most convenient. For some atomic propositions which are
changed in multiple places in the kernel, it is cumbersome to trace all those
places. Furthermore, it may not be important that the atomic propositions are
updated at precise times. For example, considering the following linear temporal
logic::
RULE = always (RT imply not PAGEFAULT)
This LTL states that a real-time task does not raise page faults. For this
specification, it is not important when `RT` changes, as long as it has the
correct value when `PAGEFAULT` is true. Motivated by this case, another
function is introduced::
void ltl_atom_fetch(struct task_struct *task, struct ltl_monitor *mon)
This function is called whenever the Buchi automaton is triggered. Therefore, it
can be manually implemented to "fetch" `RT`::
void ltl_atom_fetch(struct task_struct *task, struct ltl_monitor *mon)
{
ltl_atom_set(mon, LTL_RT, rt_task(task));
}
Effectively, whenever `PAGEFAULT` is updated with a call to `ltl_atom_update()`,
`RT` is also fetched. Thus, the LTL specification can be verified without
tracing `RT` everywhere.
For atomic propositions which act like events, they usually need to be set (or
cleared) and then immediately cleared (or set). A convenient function is
provided::
void ltl_atom_pulse(struct task_struct *task, enum ltl_atom atom, bool value)
which is equivalent to::
ltl_atom_update(task, atom, value);
ltl_atom_update(task, atom, !value);
To initialize the atomic propositions, the following function must be
implemented::
ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
This function is called for all running tasks when the monitor is enabled. It is
also called for new tasks created after the enabling the monitor. It should
initialize as many atomic propositions as possible, for example::
void ltl_atom_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
{
ltl_atom_set(mon, LTL_RT, rt_task(task));
if (task_creation)
ltl_atom_set(mon, LTL_PAGEFAULT, false);
}
Atomic propositions not initialized by `ltl_atom_init()` will stay in the
unknown state until relevant tracepoints are hit, which can take some time. As
monitoring for a task cannot be done until all atomic propositions is known for
the task, the monitor may need some time to start validating tasks which have
been running before the monitor is enabled. Therefore, it is recommended to
start the tasks of interest after enabling the monitor.
Final remarks
-------------
With the monitor synthesis in place using the header files and
rvgen, the developer's work should be limited to the instrumentation
of the system, increasing the confidence in the overall approach.
[1] For details about deterministic automata format and the translation
from one representation to another, see::
Documentation/trace/rv/deterministic_automata.rst
[2] rvgen appends the monitor's name suffix to the events enums to
avoid conflicting variables when exporting the global vmlinux.h
use by BPF programs.
+8
View File
@@ -20,6 +20,9 @@
#include <asm/ptrace.h>
#include <asm/tlbflush.h>
#define CREATE_TRACE_POINTS
#include <trace/events/exceptions.h>
#include "../kernel/head.h"
static void show_pte(unsigned long addr)
@@ -291,6 +294,11 @@ void handle_page_fault(struct pt_regs *regs)
if (kprobe_page_fault(regs, cause))
return;
if (user_mode(regs))
trace_page_fault_user(addr, regs, cause);
else
trace_page_fault_kernel(addr, regs, cause);
/*
* Fault-in kernel-space virtual memory on-demand.
* The 'reference' page table is init_mm.pgd.
+3
View File
@@ -3,6 +3,7 @@
#define _LINUX_PANIC_H
#include <linux/compiler_attributes.h>
#include <linux/stdarg.h>
#include <linux/types.h>
struct pt_regs;
@@ -10,6 +11,8 @@ struct pt_regs;
extern long (*panic_blink)(int state);
__printf(1, 2)
void panic(const char *fmt, ...) __noreturn __cold;
__printf(1, 0)
void vpanic(const char *fmt, va_list args) __noreturn __cold;
void nmi_panic(struct pt_regs *regs, const char *msg);
void check_panic_on_warn(const char *origin);
extern void oops_enter(void);
+7
View File
@@ -154,6 +154,8 @@ int vprintk_emit(int facility, int level,
asmlinkage __printf(1, 0)
int vprintk(const char *fmt, va_list args);
__printf(1, 0)
int vprintk_deferred(const char *fmt, va_list args);
asmlinkage __printf(1, 2) __cold
int _printk(const char *fmt, ...);
@@ -214,6 +216,11 @@ int vprintk(const char *s, va_list args)
{
return 0;
}
static inline __printf(1, 0)
int vprintk_deferred(const char *fmt, va_list args)
{
return 0;
}
static inline __printf(1, 2) __cold
int _printk(const char *s, ...)
{
+75 -13
View File
@@ -7,9 +7,17 @@
#ifndef _LINUX_RV_H
#define _LINUX_RV_H
#define MAX_DA_NAME_LEN 32
#include <linux/types.h>
#include <linux/list.h>
#define MAX_DA_NAME_LEN 32
#define MAX_DA_RETRY_RACING_EVENTS 3
#ifdef CONFIG_RV
#include <linux/bitops.h>
#include <linux/types.h>
#include <linux/array_size.h>
/*
* Deterministic automaton per-object variables.
*/
@@ -18,27 +26,72 @@ struct da_monitor {
unsigned int curr_state;
};
/*
* Per-task RV monitors count. Nowadays fixed in RV_PER_TASK_MONITORS.
* If we find justification for more monitors, we can think about
* adding more or developing a dynamic method. So far, none of
* these are justified.
*/
#define RV_PER_TASK_MONITORS 1
#define RV_PER_TASK_MONITOR_INIT (RV_PER_TASK_MONITORS)
#ifdef CONFIG_RV_LTL_MONITOR
/*
* Futher monitor types are expected, so make this a union.
* In the future, if the number of atomic propositions or the size of Buchi
* automaton is larger, we can switch to dynamic allocation. For now, the code
* is simpler this way.
*/
#define RV_MAX_LTL_ATOM 32
#define RV_MAX_BA_STATES 32
/**
* struct ltl_monitor - A linear temporal logic runtime verification monitor
* @states: States in the Buchi automaton. As Buchi automaton is a
* non-deterministic state machine, the monitor can be in multiple
* states simultaneously. This is a bitmask of all possible states.
* If this is zero, that means either:
* - The monitor has not started yet (e.g. because not all
* atomic propositions are known).
* - There is no possible state to be in. In other words, a
* violation of the LTL property is detected.
* @atoms: The values of atomic propositions.
* @unknown_atoms: Atomic propositions which are still unknown.
*/
struct ltl_monitor {
DECLARE_BITMAP(states, RV_MAX_BA_STATES);
DECLARE_BITMAP(atoms, RV_MAX_LTL_ATOM);
DECLARE_BITMAP(unknown_atoms, RV_MAX_LTL_ATOM);
};
static inline bool rv_ltl_valid_state(struct ltl_monitor *mon)
{
for (int i = 0; i < ARRAY_SIZE(mon->states); ++i) {
if (mon->states[i])
return true;
}
return false;
}
static inline bool rv_ltl_all_atoms_known(struct ltl_monitor *mon)
{
for (int i = 0; i < ARRAY_SIZE(mon->unknown_atoms); ++i) {
if (mon->unknown_atoms[i])
return false;
}
return true;
}
#else
struct ltl_monitor {};
#endif /* CONFIG_RV_LTL_MONITOR */
#define RV_PER_TASK_MONITOR_INIT (CONFIG_RV_PER_TASK_MONITORS)
union rv_task_monitor {
struct da_monitor da_mon;
struct da_monitor da_mon;
struct ltl_monitor ltl_mon;
};
#ifdef CONFIG_RV_REACTORS
struct rv_reactor {
const char *name;
const char *description;
void (*react)(char *msg);
__printf(1, 2) void (*react)(const char *msg, ...);
struct list_head list;
};
#endif
@@ -50,8 +103,12 @@ struct rv_monitor {
void (*disable)(void);
void (*reset)(void);
#ifdef CONFIG_RV_REACTORS
void (*react)(char *msg);
struct rv_reactor *reactor;
__printf(1, 2) void (*react)(const char *msg, ...);
#endif
struct list_head list;
struct rv_monitor *parent;
struct dentry *root_d;
};
bool rv_monitoring_on(void);
@@ -64,6 +121,11 @@ void rv_put_task_monitor_slot(int slot);
bool rv_reacting_on(void);
int rv_unregister_reactor(struct rv_reactor *reactor);
int rv_register_reactor(struct rv_reactor *reactor);
#else
static inline bool rv_reacting_on(void)
{
return false;
}
#endif /* CONFIG_RV_REACTORS */
#endif /* CONFIG_RV */
+9 -6
View File
@@ -340,9 +340,11 @@ extern void io_schedule_finish(int token);
extern long io_schedule_timeout(long timeout);
extern void io_schedule(void);
/* wrapper function to trace from this header file */
/* wrapper functions to trace from this header file */
DECLARE_TRACEPOINT(sched_set_state_tp);
extern void __trace_set_current_state(int state_value);
DECLARE_TRACEPOINT(sched_set_need_resched_tp);
extern void __trace_set_need_resched(struct task_struct *curr, int tif);
/**
* struct prev_cputime - snapshot of system and user cputime
@@ -1634,12 +1636,10 @@ struct task_struct {
#ifdef CONFIG_RV
/*
* Per-task RV monitor. Nowadays fixed in RV_PER_TASK_MONITORS.
* If we find justification for more monitors, we can think
* about adding more or developing a dynamic method. So far,
* none of these are justified.
* Per-task RV monitor, fixed in CONFIG_RV_PER_TASK_MONITORS.
* If memory becomes a concern, we can think about a dynamic method.
*/
union rv_task_monitor rv[RV_PER_TASK_MONITORS];
union rv_task_monitor rv[CONFIG_RV_PER_TASK_MONITORS];
#endif
#ifdef CONFIG_USER_EVENTS
@@ -2030,6 +2030,9 @@ static inline int test_tsk_thread_flag(struct task_struct *tsk, int flag)
static inline void set_tsk_need_resched(struct task_struct *tsk)
{
if (tracepoint_enabled(sched_set_need_resched_tp) &&
!test_tsk_thread_flag(tsk, TIF_NEED_RESCHED))
__trace_set_need_resched(tsk, TIF_NEED_RESCHED);
set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
}
+84 -84
View File
@@ -19,45 +19,22 @@
#ifdef CONFIG_RV_REACTORS
#define DECLARE_RV_REACTING_HELPERS(name, type) \
static char REACT_MSG_##name[1024]; \
\
static inline char *format_react_msg_##name(type curr_state, type event) \
static void cond_react_##name(type curr_state, type event) \
{ \
snprintf(REACT_MSG_##name, 1024, \
"rv: monitor %s does not allow event %s on state %s\n", \
#name, \
model_get_event_name_##name(event), \
model_get_state_name_##name(curr_state)); \
return REACT_MSG_##name; \
} \
\
static void cond_react_##name(char *msg) \
{ \
if (rv_##name.react) \
rv_##name.react(msg); \
} \
\
static bool rv_reacting_on_##name(void) \
{ \
return rv_reacting_on(); \
if (!rv_reacting_on() || !rv_##name.react) \
return; \
rv_##name.react("rv: monitor %s does not allow event %s on state %s\n", \
#name, \
model_get_event_name_##name(event), \
model_get_state_name_##name(curr_state)); \
}
#else /* CONFIG_RV_REACTOR */
#define DECLARE_RV_REACTING_HELPERS(name, type) \
static inline char *format_react_msg_##name(type curr_state, type event) \
{ \
return NULL; \
} \
\
static void cond_react_##name(char *msg) \
static void cond_react_##name(type curr_state, type event) \
{ \
return; \
} \
\
static bool rv_reacting_on_##name(void) \
{ \
return 0; \
}
#endif
@@ -77,23 +54,6 @@ static inline void da_monitor_reset_##name(struct da_monitor *da_mon) \
da_mon->curr_state = model_get_initial_state_##name(); \
} \
\
/* \
* da_monitor_curr_state_##name - return the current state \
*/ \
static inline type da_monitor_curr_state_##name(struct da_monitor *da_mon) \
{ \
return da_mon->curr_state; \
} \
\
/* \
* da_monitor_set_state_##name - set the new current state \
*/ \
static inline void \
da_monitor_set_state_##name(struct da_monitor *da_mon, enum states_##name state) \
{ \
da_mon->curr_state = state; \
} \
\
/* \
* da_monitor_start_##name - start monitoring \
* \
@@ -150,65 +110,81 @@ static inline bool da_monitor_handling_event_##name(struct da_monitor *da_mon)
* Event handler for implicit monitors. Implicit monitor is the one which the
* handler does not need to specify which da_monitor to manipulate. Examples
* of implicit monitor are the per_cpu or the global ones.
*
* Retry in case there is a race between getting and setting the next state,
* warn and reset the monitor if it runs out of retries. The monitor should be
* able to handle various orders.
*/
#define DECLARE_DA_MON_MODEL_HANDLER_IMPLICIT(name, type) \
\
static inline bool \
da_event_##name(struct da_monitor *da_mon, enum events_##name event) \
{ \
type curr_state = da_monitor_curr_state_##name(da_mon); \
type next_state = model_get_next_state_##name(curr_state, event); \
enum states_##name curr_state, next_state; \
\
if (next_state != INVALID_STATE) { \
da_monitor_set_state_##name(da_mon, next_state); \
\
trace_event_##name(model_get_state_name_##name(curr_state), \
model_get_event_name_##name(event), \
model_get_state_name_##name(next_state), \
model_is_final_state_##name(next_state)); \
\
return true; \
curr_state = READ_ONCE(da_mon->curr_state); \
for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) { \
next_state = model_get_next_state_##name(curr_state, event); \
if (next_state == INVALID_STATE) { \
cond_react_##name(curr_state, event); \
trace_error_##name(model_get_state_name_##name(curr_state), \
model_get_event_name_##name(event)); \
return false; \
} \
if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) { \
trace_event_##name(model_get_state_name_##name(curr_state), \
model_get_event_name_##name(event), \
model_get_state_name_##name(next_state), \
model_is_final_state_##name(next_state)); \
return true; \
} \
} \
\
if (rv_reacting_on_##name()) \
cond_react_##name(format_react_msg_##name(curr_state, event)); \
\
trace_error_##name(model_get_state_name_##name(curr_state), \
model_get_event_name_##name(event)); \
\
trace_rv_retries_error(#name, model_get_event_name_##name(event)); \
pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS) \
" retries reached for event %s, resetting monitor %s", \
model_get_event_name_##name(event), #name); \
return false; \
} \
/*
* Event handler for per_task monitors.
*
* Retry in case there is a race between getting and setting the next state,
* warn and reset the monitor if it runs out of retries. The monitor should be
* able to handle various orders.
*/
#define DECLARE_DA_MON_MODEL_HANDLER_PER_TASK(name, type) \
\
static inline bool da_event_##name(struct da_monitor *da_mon, struct task_struct *tsk, \
enum events_##name event) \
{ \
type curr_state = da_monitor_curr_state_##name(da_mon); \
type next_state = model_get_next_state_##name(curr_state, event); \
enum states_##name curr_state, next_state; \
\
if (next_state != INVALID_STATE) { \
da_monitor_set_state_##name(da_mon, next_state); \
\
trace_event_##name(tsk->pid, \
model_get_state_name_##name(curr_state), \
model_get_event_name_##name(event), \
model_get_state_name_##name(next_state), \
model_is_final_state_##name(next_state)); \
\
return true; \
curr_state = READ_ONCE(da_mon->curr_state); \
for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) { \
next_state = model_get_next_state_##name(curr_state, event); \
if (next_state == INVALID_STATE) { \
cond_react_##name(curr_state, event); \
trace_error_##name(tsk->pid, \
model_get_state_name_##name(curr_state), \
model_get_event_name_##name(event)); \
return false; \
} \
if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) { \
trace_event_##name(tsk->pid, \
model_get_state_name_##name(curr_state), \
model_get_event_name_##name(event), \
model_get_state_name_##name(next_state), \
model_is_final_state_##name(next_state)); \
return true; \
} \
} \
\
if (rv_reacting_on_##name()) \
cond_react_##name(format_react_msg_##name(curr_state, event)); \
\
trace_error_##name(tsk->pid, \
model_get_state_name_##name(curr_state), \
model_get_event_name_##name(event)); \
\
trace_rv_retries_error(#name, model_get_event_name_##name(event)); \
pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS) \
" retries reached for event %s, resetting monitor %s", \
model_get_event_name_##name(event), #name); \
return false; \
}
@@ -512,6 +488,30 @@ da_handle_start_event_##name(struct task_struct *tsk, enum events_##name event)
__da_handle_event_##name(da_mon, tsk, event); \
\
return 1; \
} \
\
/* \
* da_handle_start_run_event_##name - start monitoring and handle event \
* \
* This function is used to notify the monitor that the system is in the \
* initial state, so the monitor can start monitoring and handling event. \
*/ \
static inline bool \
da_handle_start_run_event_##name(struct task_struct *tsk, enum events_##name event) \
{ \
struct da_monitor *da_mon; \
\
if (!da_monitor_enabled_##name()) \
return 0; \
\
da_mon = da_get_monitor_##name(tsk); \
\
if (unlikely(!da_monitoring_##name(da_mon))) \
da_monitor_start_##name(da_mon); \
\
__da_handle_event_##name(da_mon, tsk, event); \
\
return 1; \
}
/*
+186
View File
@@ -0,0 +1,186 @@
/* SPDX-License-Identifier: GPL-2.0 */
/**
* This file must be combined with the $(MODEL_NAME).h file generated by
* tools/verification/rvgen.
*/
#include <linux/args.h>
#include <linux/rv.h>
#include <linux/stringify.h>
#include <linux/seq_buf.h>
#include <rv/instrumentation.h>
#include <trace/events/task.h>
#include <trace/events/sched.h>
#ifndef MONITOR_NAME
#error "Please include $(MODEL_NAME).h generated by rvgen"
#endif
#ifdef CONFIG_RV_REACTORS
#define RV_MONITOR_NAME CONCATENATE(rv_, MONITOR_NAME)
static struct rv_monitor RV_MONITOR_NAME;
static void rv_cond_react(struct task_struct *task)
{
if (!rv_reacting_on() || !RV_MONITOR_NAME.react)
return;
RV_MONITOR_NAME.react("rv: "__stringify(MONITOR_NAME)": %s[%d]: violation detected\n",
task->comm, task->pid);
}
#else
static void rv_cond_react(struct task_struct *task)
{
}
#endif
static int ltl_monitor_slot = RV_PER_TASK_MONITOR_INIT;
static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon);
static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation);
static struct ltl_monitor *ltl_get_monitor(struct task_struct *task)
{
return &task->rv[ltl_monitor_slot].ltl_mon;
}
static void ltl_task_init(struct task_struct *task, bool task_creation)
{
struct ltl_monitor *mon = ltl_get_monitor(task);
memset(&mon->states, 0, sizeof(mon->states));
for (int i = 0; i < LTL_NUM_ATOM; ++i)
__set_bit(i, mon->unknown_atoms);
ltl_atoms_init(task, mon, task_creation);
ltl_atoms_fetch(task, mon);
}
static void handle_task_newtask(void *data, struct task_struct *task, unsigned long flags)
{
ltl_task_init(task, true);
}
static int ltl_monitor_init(void)
{
struct task_struct *g, *p;
int ret, cpu;
ret = rv_get_task_monitor_slot();
if (ret < 0)
return ret;
ltl_monitor_slot = ret;
rv_attach_trace_probe(name, task_newtask, handle_task_newtask);
read_lock(&tasklist_lock);
for_each_process_thread(g, p)
ltl_task_init(p, false);
for_each_present_cpu(cpu)
ltl_task_init(idle_task(cpu), false);
read_unlock(&tasklist_lock);
return 0;
}
static void ltl_monitor_destroy(void)
{
rv_detach_trace_probe(name, task_newtask, handle_task_newtask);
rv_put_task_monitor_slot(ltl_monitor_slot);
ltl_monitor_slot = RV_PER_TASK_MONITOR_INIT;
}
static void ltl_illegal_state(struct task_struct *task, struct ltl_monitor *mon)
{
CONCATENATE(trace_error_, MONITOR_NAME)(task);
rv_cond_react(task);
}
static void ltl_attempt_start(struct task_struct *task, struct ltl_monitor *mon)
{
if (rv_ltl_all_atoms_known(mon))
ltl_start(task, mon);
}
static inline void ltl_atom_set(struct ltl_monitor *mon, enum ltl_atom atom, bool value)
{
__clear_bit(atom, mon->unknown_atoms);
if (value)
__set_bit(atom, mon->atoms);
else
__clear_bit(atom, mon->atoms);
}
static void
ltl_trace_event(struct task_struct *task, struct ltl_monitor *mon, unsigned long *next_state)
{
const char *format_str = "%s";
DECLARE_SEQ_BUF(atoms, 64);
char states[32], next[32];
int i;
if (!CONCATENATE(CONCATENATE(trace_event_, MONITOR_NAME), _enabled)())
return;
snprintf(states, sizeof(states), "%*pbl", RV_MAX_BA_STATES, mon->states);
snprintf(next, sizeof(next), "%*pbl", RV_MAX_BA_STATES, next_state);
for (i = 0; i < LTL_NUM_ATOM; ++i) {
if (test_bit(i, mon->atoms)) {
seq_buf_printf(&atoms, format_str, ltl_atom_str(i));
format_str = ",%s";
}
}
CONCATENATE(trace_event_, MONITOR_NAME)(task, states, atoms.buffer, next);
}
static void ltl_validate(struct task_struct *task, struct ltl_monitor *mon)
{
DECLARE_BITMAP(next_states, RV_MAX_BA_STATES) = {0};
if (!rv_ltl_valid_state(mon))
return;
for (unsigned int i = 0; i < RV_NUM_BA_STATES; ++i) {
if (test_bit(i, mon->states))
ltl_possible_next_states(mon, i, next_states);
}
ltl_trace_event(task, mon, next_states);
memcpy(mon->states, next_states, sizeof(next_states));
if (!rv_ltl_valid_state(mon))
ltl_illegal_state(task, mon);
}
static void ltl_atom_update(struct task_struct *task, enum ltl_atom atom, bool value)
{
struct ltl_monitor *mon = ltl_get_monitor(task);
ltl_atom_set(mon, atom, value);
ltl_atoms_fetch(task, mon);
if (!rv_ltl_valid_state(mon)) {
ltl_attempt_start(task, mon);
return;
}
ltl_validate(task, mon);
}
static void __maybe_unused ltl_atom_pulse(struct task_struct *task, enum ltl_atom atom, bool value)
{
struct ltl_monitor *mon = ltl_get_monitor(task);
ltl_atom_update(task, atom, value);
ltl_atom_set(mon, atom, !value);
ltl_validate(task, mon);
}
+8 -4
View File
@@ -882,18 +882,22 @@ DECLARE_TRACE(sched_compute_energy,
TP_ARGS(p, dst_cpu, energy, max_util, busy_time));
DECLARE_TRACE(sched_entry,
TP_PROTO(bool preempt, unsigned long ip),
TP_ARGS(preempt, ip));
TP_PROTO(bool preempt),
TP_ARGS(preempt));
DECLARE_TRACE(sched_exit,
TP_PROTO(bool is_switch, unsigned long ip),
TP_ARGS(is_switch, ip));
TP_PROTO(bool is_switch),
TP_ARGS(is_switch));
DECLARE_TRACE_CONDITION(sched_set_state,
TP_PROTO(struct task_struct *tsk, int state),
TP_ARGS(tsk, state),
TP_CONDITION(!!(tsk->__state) != !!state));
DECLARE_TRACE(sched_set_need_resched,
TP_PROTO(struct task_struct *tsk, int cpu, int tif),
TP_ARGS(tsk, cpu, tif));
#endif /* _TRACE_SCHED_H */
/* This part must be outside protection */
+1 -4
View File
@@ -1890,10 +1890,7 @@ static void copy_oom_score_adj(u64 clone_flags, struct task_struct *tsk)
#ifdef CONFIG_RV
static void rv_task_fork(struct task_struct *p)
{
int i;
for (i = 0; i < RV_PER_TASK_MONITORS; i++)
p->rv[i].da_mon.monitoring = false;
memset(&p->rv, 0, sizeof(p->rv));
}
#else
#define rv_task_fork(p) do {} while (0)
+13 -5
View File
@@ -367,15 +367,15 @@ static void panic_other_cpus_shutdown(bool crash_kexec)
}
/**
* panic - halt the system
* vpanic - halt the system
* @fmt: The text string to print
* @args: Arguments for the format string
*
* Display a message, then perform cleanups. This function never returns.
*/
void panic(const char *fmt, ...)
void vpanic(const char *fmt, va_list args)
{
static char buf[1024];
va_list args;
long i, i_next = 0, len;
int state = 0;
int old_cpu, this_cpu;
@@ -426,9 +426,7 @@ void panic(const char *fmt, ...)
console_verbose();
bust_spinlocks(1);
va_start(args, fmt);
len = vscnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
if (len && buf[len - 1] == '\n')
buf[len - 1] = '\0';
@@ -565,7 +563,17 @@ void panic(const char *fmt, ...)
mdelay(PANIC_TIMER_STEP);
}
}
EXPORT_SYMBOL(vpanic);
/* Identical to vpanic(), except it takes variadic arguments instead of va_list */
void panic(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vpanic(fmt, args);
va_end(args);
}
EXPORT_SYMBOL(panic);
#define TAINT_FLAG(taint, _c_true, _c_false, _module) \
-1
View File
@@ -72,7 +72,6 @@ int vprintk_store(int facility, int level,
const char *fmt, va_list args);
__printf(1, 0) int vprintk_default(const char *fmt, va_list args);
__printf(1, 0) int vprintk_deferred(const char *fmt, va_list args);
void __printk_safe_enter(void);
void __printk_safe_exit(void);
+10 -3
View File
@@ -1113,6 +1113,7 @@ static void __resched_curr(struct rq *rq, int tif)
cpu = cpu_of(rq);
trace_sched_set_need_resched_tp(curr, cpu, tif);
if (cpu == smp_processor_id()) {
set_ti_thread_flag(cti, tif);
if (tif == TIF_NEED_RESCHED)
@@ -1128,6 +1129,11 @@ static void __resched_curr(struct rq *rq, int tif)
}
}
void __trace_set_need_resched(struct task_struct *curr, int tif)
{
trace_sched_set_need_resched_tp(curr, smp_processor_id(), tif);
}
void resched_curr(struct rq *rq)
{
__resched_curr(rq, TIF_NEED_RESCHED);
@@ -5279,7 +5285,7 @@ asmlinkage __visible void schedule_tail(struct task_struct *prev)
* switched the context for the first time. It is returning from
* schedule for the first time in this path.
*/
trace_sched_exit_tp(true, CALLER_ADDR0);
trace_sched_exit_tp(true);
preempt_enable();
if (current->set_child_tid)
@@ -6822,7 +6828,8 @@ static void __sched notrace __schedule(int sched_mode)
struct rq *rq;
int cpu;
trace_sched_entry_tp(preempt, CALLER_ADDR0);
/* Trace preemptions consistently with task switches */
trace_sched_entry_tp(sched_mode == SM_PREEMPT);
cpu = smp_processor_id();
rq = cpu_rq(cpu);
@@ -6961,7 +6968,7 @@ keep_resched:
__balance_callbacks(rq);
raw_spin_rq_unlock_irq(rq);
}
trace_sched_exit_tp(is_switch, CALLER_ADDR0);
trace_sched_exit_tp(is_switch);
}
void __noreturn do_task_dead(void)
+37 -6
View File
@@ -1,19 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config DA_MON_EVENTS
config RV_MON_EVENTS
bool
config RV_MON_MAINTENANCE_EVENTS
bool
config DA_MON_EVENTS_IMPLICIT
select DA_MON_EVENTS
select RV_MON_EVENTS
select RV_MON_MAINTENANCE_EVENTS
bool
config DA_MON_EVENTS_ID
select DA_MON_EVENTS
select RV_MON_EVENTS
select RV_MON_MAINTENANCE_EVENTS
bool
config LTL_MON_EVENTS_ID
select RV_MON_EVENTS
bool
config RV_LTL_MONITOR
bool
menuconfig RV
bool "Runtime Verification"
depends on TRACING
select TRACING
help
Enable the kernel runtime verification infrastructure. RV is a
lightweight (yet rigorous) method that complements classical
@@ -25,15 +37,34 @@ menuconfig RV
For further information, see:
Documentation/trace/rv/runtime-verification.rst
config RV_PER_TASK_MONITORS
int "Maximum number of per-task monitor"
depends on RV
range 1 8
default 2
help
This option configures the maximum number of per-task RV monitors that can run
simultaneously.
source "kernel/trace/rv/monitors/wip/Kconfig"
source "kernel/trace/rv/monitors/wwnr/Kconfig"
source "kernel/trace/rv/monitors/sched/Kconfig"
source "kernel/trace/rv/monitors/tss/Kconfig"
source "kernel/trace/rv/monitors/sco/Kconfig"
source "kernel/trace/rv/monitors/snroc/Kconfig"
source "kernel/trace/rv/monitors/scpd/Kconfig"
source "kernel/trace/rv/monitors/snep/Kconfig"
source "kernel/trace/rv/monitors/sncid/Kconfig"
source "kernel/trace/rv/monitors/sts/Kconfig"
source "kernel/trace/rv/monitors/nrp/Kconfig"
source "kernel/trace/rv/monitors/sssw/Kconfig"
source "kernel/trace/rv/monitors/opid/Kconfig"
# Add new sched monitors here
source "kernel/trace/rv/monitors/rtapp/Kconfig"
source "kernel/trace/rv/monitors/pagefault/Kconfig"
source "kernel/trace/rv/monitors/sleep/Kconfig"
# Add new rtapp monitors here
# Add new monitors here
config RV_REACTORS
+7 -2
View File
@@ -6,12 +6,17 @@ obj-$(CONFIG_RV) += rv.o
obj-$(CONFIG_RV_MON_WIP) += monitors/wip/wip.o
obj-$(CONFIG_RV_MON_WWNR) += monitors/wwnr/wwnr.o
obj-$(CONFIG_RV_MON_SCHED) += monitors/sched/sched.o
obj-$(CONFIG_RV_MON_TSS) += monitors/tss/tss.o
obj-$(CONFIG_RV_MON_SCO) += monitors/sco/sco.o
obj-$(CONFIG_RV_MON_SNROC) += monitors/snroc/snroc.o
obj-$(CONFIG_RV_MON_SCPD) += monitors/scpd/scpd.o
obj-$(CONFIG_RV_MON_SNEP) += monitors/snep/snep.o
obj-$(CONFIG_RV_MON_SNCID) += monitors/sncid/sncid.o
obj-$(CONFIG_RV_MON_RTAPP) += monitors/rtapp/rtapp.o
obj-$(CONFIG_RV_MON_PAGEFAULT) += monitors/pagefault/pagefault.o
obj-$(CONFIG_RV_MON_SLEEP) += monitors/sleep/sleep.o
obj-$(CONFIG_RV_MON_STS) += monitors/sts/sts.o
obj-$(CONFIG_RV_MON_NRP) += monitors/nrp/nrp.o
obj-$(CONFIG_RV_MON_SSSW) += monitors/sssw/sssw.o
obj-$(CONFIG_RV_MON_OPID) += monitors/opid/opid.o
# Add new monitors here
obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o

Some files were not shown because too many files have changed in this diff Show More