mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
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:
@@ -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.
|
||||
@@ -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, 1343–1355.
|
||||
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
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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; \
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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) \
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user