blob: 7bc3053db894e0c7c992f12a85f7630a2c414d69 [file] [log] [blame] [view]
# Runtime Lock Validation in Zircon
## Introduction
Zircon integrates a runtime lock validator to diagnose inconsistent lock
ordering that could lead to deadlocks. This document discusses how the
validator is integrated, how to enable and tune the validator at build time,
and what output the validator produces.
The theory of operation for the validator itself can be found in the
[design document](lockdep-design.md).
## Enabling the Lock Validator
Lock validation is disabled by default. **When disabled the lock instrumentation
is transparent, acting as a zero-overhead wrapper for the underlying locking
primitives**.
The validator is enabled at compile time by setting the GN build argument
`enable_lock_dep` to true. As of this writing logic for this variable is
handled by [zircon/kernel/BUILD.gn](/zircon/kernel/BUILD.gn).
You can set this variable in your GN invocation like this:
```
fx set <your build options> --args 'enable_lock_dep = true'
```
When the lock validator is enabled a set of global lock-free, wait-free data
structures are generated to track the relationships between the instrumented
locks. The acquire/release operations of the locks are augmented to update
these data structures.
## Lock Instrumentation
The current incarnation of the runtime lock validator requires manually
instrumenting each lock in kernel with a wrapper type. The wrapper type provides
the context the validator needs to properly identify the lock and generate a
global tracking structure for locks with the same context or role.
The kernel defines utility macros for this purpose in `kernel/spinlock.h` and
`kernel/mutex.h`.
### Member Locks
A type with a lock member like this:
```C++
#include <kernel/mutex.h>
class MyType {
public:
// ...
private:
mutable fbl::Mutex lock_;
// ...
};
```
May be instrumented like this:
```C++
#include <kernel/mutex.h>
class MyType {
public:
// ...
private:
mutable DECLARE_MUTEX(MyType) lock_;
// ...
};
```
Note that the containing type is passed to the macro
`DECLARE_MUTEX(containing_type)`. This type provides the context the validator
needs to distinguish locks that are members of `MyType` from locks that are
members of other types.
The macro `DECLARE_SPINLOCK(containing_type)` provides similar support for
instrumenting `SpinLock` members.
For those who are curious, the macro in the example above expands to this type
expression: `::lockdep::LockDep<containing_type, fbl::Mutex, __LINE__>`. This
expression results in a unique instantiation of the `lockdep::LockDep<>` type,
both across different containing types, and within a containing type where
there is more than one mutex.
### Global Locks
Global locks are instrumented using a singleton-type pattern. The kernel defines
utility macros for this purpose in `kernel/mutex.h` and `kernel/spinlock.h`.
In Zircon global locks are typically defined either at global/namespace scope or
within another type as a static member.
example.h:
```C++
#include <kernel/mutex.h>
extern fbl::Mutex a_global_lock;
class MyType {
public:
// ...
private:
static fbl::Mutex all_objects_lock_;
};
```
example.cpp:
```C++
#include "example.h"
fbl::Mutex a_global_lock;
fbl::Mutex MyType::all_objects_lock_;
```
The instrumentation simplifies declaring locks by declaring singleton types that
may be used in either scope and handles ODR-use automatically.
example.h:
```
#include <kernel/mutex.h>
DECLARE_SINGLETON_MUTEX(AGlobalLock);
class MyType {
public:
// ...
private:
DECLARE_SINGLETON_MUTEX(AllObjectsLock);
};
```
These macro invocations declare new singleton types, `AGlobalLock` and
`MyType::AllObjectsLock` respectively. These types have a static `Get()` method
that returns the underlying global lock with all of the necessary
instrumentation. Note that there is no need to separately define storage for the
locks, this is handled automatically by the supporting template types.
The macro `DECLARE_SINGLETON_SPINLOCK(name)` provides similar support for
declaring a global `SpinLock`.
### Lock Guards
Instrumented locks are acquired and released using the scoped capability types
`Guard` and `GuardMultiple`. In the kernel these types are defined in
`kernel/lockdep.h`.
The operation of `Guard` for simple mutexes is similar to `AutoLock`:
```C++
#include <kernel/mutex.h>
class MyType {
public:
// ...
int GetData() const {
Guard<fbl::Mutex> guard{&lock_};
return data_;
}
int DoSomething() {
Guard<fbl::Mutex> guard{&lock_};
int data_copy = data_;
guard.Release();
return DoWorkUnlocked(data_copy);
}
private:
mutable DECLARE_MUTEX(MyType) lock_;
int data_{0} TA_GUARDED(lock_);
};
```
`SpinLock` types require an additional template argument to `Guard` to select
one of a few possible options when acquiring the lock: `IrqSave`, `NoIrqSave`,
and `TryLockNoIrqSave`. Omitting one of these type tags results in a
compile-time error.
```C++
#include <kernel/spinlock.h>
class MyType {
public:
// ...
int GetData() const {
Guard<SpinLock, IrqSave> guard{&lock_};
return data_;
}
void DoSomethingInIrqContext() {
Guard<SpinLock, NoIrqSave> guard{&lock_};
// ...
}
bool TryToDoSomethingInIrqContext() {
if (Guard<SpinLock, TryLockNoIrqSave> guard{&lock_}) {
// ...
return true;
}
return false;
}
private:
mutable DECLARE_SPINLOCK(MyType) lock_;
int data_{0} TA_GUARDED(lock_);
};
```
Instrumented global locks work similarly:
```C++
#include <kernel/mutex.h>
#include <fbl/intrusive_double_list.h>
class MyType : public fbl::DoublyLinkedListable<MyType> {
public:
// ...
void AddToList(MyType* object) {
Guard<fbl::Mutex> guard{AllObjectsLock::Get()};
all_objects_list_.push_back(*object);
}
private:
DECLARE_SINGLETON_MUTEX(AllObjectsLock);
fbl::DoublyLinkedList<MyType> all_objects_list_ TA_GUARDED(AllObjectsLock::Get());
};
```
Note that instrumented locks do not have manual `Acquire()` and `Release()`
methods; using a `Guard` is the only way to acquire the locks directly. There
are two important reasons for this:
1. Manual acquire/release operations are more error prone than guard, plus
manual release when necessary.
2. When lock validation is enabled the guard provides the storage that the
validator uses to account for actively held locks. This approach permits
temporary storage of validator state on the stack only for the duration the
lock is held, which corresponds with the use patterns of guard objects.
Without this approach the tracking data would either have to be stored with
each lock instance, increasing memory use even when locks are not held, or
stored in heap allocated memory. Neither of these alternatives is desirable.
In rare circumstances the underlying lock may be accessed using the `lock()`
accessor of the instrumented lock. This should be done with care as manipulating
the underlying lock directly may result inconsistency between the state of the
lock and the state the lock validator; at best this may lead to missing a lock
order warning and at worst may lead to a deadlock. **You have been warned!**
## Clang Static Analysis and Instrumented Locks
The lock instrumentation is designed to interoperate with Clang static lock
analysis. In general usage, an instrumented lock may be used as a "mutex"
capability and specified in any of the static lock annotations.
There are two special cases that need some extra attention:
1. Returning pointers or references to capabilities.
2. Unlocking a guard passed by reference.
### Pointers and References to Capabilities
When returning a lock by pointer or reference it may be convenient or necessary
to use a uniform type. Recall from earlier that instrumented locks are wrapped
in a type that captures the containing type, the underlying lock type, and the
line number to disambiguate locks belonging to different types
(`::lockdep::LockDep<Class, Locktype, Index>`). This can lead to difficulty when
returning a lock from a uniform (virtual) interface (e.g. kernel
`Dispatcher::get_lock()`).
Fortunately there is a straightforward solution: every instrumented lock is also
a subclass of `::lockdep::Lock<LockType>` (or simply `Lock<LockType>` in the
kernel). This type only depends on the underlying `LockType`, not the context in
which the instrumented lock is declared, making it convenient to use as a
pointer or reference type to refer to an instrumented lock more generically.
This type may be used in type annotations as well.
The following illustrates the pattern, which is similar to that employed by the
kernel `Dispatcher` types.
```C++
#include <kernel/mutex.h>
struct LockableInterface {
virtual ~LockableInterface() {}
virtual Lock<fbl::Mutex>* get_lock() = 0;
virtual void DoSomethingLocked() TA_REQ(get_lock()) = 0;
};
class A : public LockableInterface {
public:
Lock<fbl::Mutex>* get_lock() override { return &lock_; }
void DoSomethingLocked() override {
data_++;
}
void DoSomething() {
Guard<fbl::Mutex> guard{get_lock()};
DoSomethingLocked();
// ...
}
private:
mutable DECLARE_MUTEX(A) lock_;
int data_ TA_GUARDED(get_lock());
};
class B : public LockableInterface {
public:
Lock<fbl::Mutex>* get_lock() override { return &lock_; }
void DoSomethingLocked() override {
// ...
}
void DoSomething() {
Guard<fbl::Mutex> guard{get_lock()};
DoSomethingLocked();
// ...
}
private:
mutable DECLARE_MUTEX(B) lock_;
char data_[32] TA_GUARDED(get_lock());
};
```
Note that the type of `A::lock_` is
`::lockdep::LockDep<A, fbl::Mutex, __LINE__>` and the type of `B::lock_` is
`::lockdep::LockDep<B, fbl::Mutex, __LINE__>`. However, both of these types are
subclasses of `Lock<fbl::Mutex>`, so we can treat them uniformly as this type in
pointer and reference expressions.
While this is very convenient, a limitation in Clang static analysis prevents it
from understanding that `LockableInterface::get_lock()` is equivalent to
`A::lock_` or `B::lock_`, even in their local contexts. For this reason is it
necessary to use `get_lock()` in all of the lock annotations.
### Unlocking a Guard Passed by Reference
In very rare circumstances it is useful to release a `Guard` instance held in
a function from a callee of the function.
**TODO(eieio): Complete documentation of this feature.**
## Lock Validation Errors
The lock validator detects and reports two broad classes of violations:
1. Pair-wise violations reported at the point of acquisition.
2. Multi-lock cycles reported asynchronously by a dedicated loop detection
thread.
### Violations Reported at Acquisition
When a violation is detected at the point of lock acquisition the validator
produces a message like the following in the kernel log:
{# Disable variable substitution to avoid {{ being interpreted by the template engine #}
{% verbatim %}
```
[00002.668] 01032:01039> ZIRCON KERNEL OOPS
[00002.668] 01032:01039> Lock validation failed for thread 0xffff000001e53598 pid 1032 tid 1039 (userboot:userboot):
[00002.668] 01032:01039> Reason: Out Of Order
[00002.668] 01032:01039> Bad lock: name=lockdep::LockClass<SoloDispatcher<ThreadDispatcher, 316111>, Mutex, 282, (lockdep::LockFlags)0> order=0
[00002.668] 01032:01039> Conflict: name=lockdep::LockClass<SoloDispatcher<ProcessDispatcher, 447439>, Mutex, 282, (lockdep::LockFlags)0> order=0
[00002.668] 01032:01039> {{{module:0:kernel:elf:0bf16acb54de1ceef7ffb6ee4449c6aafc0ab392}}}
[00002.668] 01032:01039> {{{mmap:0xffffffff10000000:0x1ae1f0:load:0:rx:0xffffffff00000000}}}
[00002.668] 01032:01039> {{{mmap:0xffffffff101af000:0x49000:load:0:r:0xffffffff001af000}}}
[00002.668] 01032:01039> {{{mmap:0xffffffff101f8000:0x1dc8:load:0:rw:0xffffffff001f8000}}}
[00002.668] 01032:01039> {{{mmap:0xffffffff10200000:0x76000:load:0:rw:0xffffffff00200000}}}
[00002.668] 01032:01039> {{{bt:0:0xffffffff10088574}}}
[00002.668] 01032:01039> {{{bt:1:0xffffffff1008f324}}}
[00002.668] 01032:01039> {{{bt:2:0xffffffff10162860}}}
[00002.668] 01032:01039> {{{bt:3:0xffffffff101711e0}}}
[00002.668] 01032:01039> {{{bt:4:0xffffffff100edae0}}}
```
{# Re-enable variable substitution #}
{% endverbatim %}
The error is informational and non-fatal. The first line identifies the thread
and process where the kernel lock violation occurred. The next line identifies
the type of violation. The next two lines identify which locks were found to be
inconsistent with previous observations: the "Bad lock" is the lock that is
about to be acquired, while "Conflict" is a lock that is already held by the
current context and is the point of inconsistency with the lock that is about to
be acquired. All of the lines following this are part of the stack trace leading
up to the bad lock.
### Multi-Lock Cycles
Circular dependencies between three or more locks are detected with a dedicated
loop detection thread. Because this detection happens in a separate context from
the lock operations that caused the cycle a stack trace is not provided.
Reports from the loop detection thread look like this:
```
[00002.000] 00000.00000> ZIRCON KERNEL OOPS
[00002.000] 00000.00000> Circular lock dependency detected:
[00002.000] 00000.00000> lockdep::LockClass<VmObject, fbl::Mutex, 249, (lockdep::LockFlags)0>
[00002.000] 00000.00000> lockdep::LockClass<VmAspace, fbl::Mutex, 198, (lockdep::LockFlags)0>
[00002.000] 00000.00000> lockdep::LockClass<SoloDispatcher<VmObjectDispatcher>, fbl::Mutex, 362, (lockdep::LockFlags)0>
[00002.000] 00000.00000> lockdep::LockClass<SoloDispatcher<PortDispatcher>, fbl::Mutex, 362, (lockdep::LockFlags)0>
```
Each of the locks involved in the cycle are reported in a group. Frequently only
two of the circularly-dependent locks are acquired by a single thread at any
given time, making manual detection difficult or impossible. However, the
potential for deadlock between three or more threads is real and should be
addressed for long-term system stability.
## Kernel Commands
When the lock validator is enabled the following kernel commands are available:
* `k lockdep dump` - dumps the dependency graph and connected sets (loops) for
all instrumented locks.
* `k lockdep loop` - triggers a loop detection pass and reports any loops found
to the kernel log.