Using LockLint |
24 |
![]() |
Basic Concepts
In the multithreading model, a process consists of one or more threads of control that share a common address space and most other process resources. Threads must acquire and release locks associated with the data they share. If they fail to do so, a data race may ensue--a situation in which a program may produce different results when run repeatedly with the same input.
LockLint Overview
LockLint is a utility that analyzes the use of mutex and multiple readers/single writer locks, and looks for inconsistent use of these locking techniques.
User |
Kernel |
rw_rdlock, rw_wrlock |
rw_enter rw_exit rw_tryenter rw_downgrade rw_tryupgrade |
Additionally, LockLint recognizes the thread types shown in Table 24-4.
Solaris |
POSIX |
Kernel (Solaris only) |
mutex_t |
pthread_mutex_t |
kmutex_t |
LockLint reports several kinds of basic information about the modules it analyzes, including:
Note - Add assertions liberally, and use the analysis phase to refine assertions and to make sure that new code does not violate the established locking conventions of the program.
Note - No .o file is produced when the -Zll flag is passed.
Use LockLint to refine the set of assertions you maintain for the implementation of your system. Maintaining a rich set of assertions enables LockLint to validate existing and new source code as you work.
Figure 24-1 LockLint Control Flow
Managing LockLint's Environment
The LockLint interface is provided through the lock_lint command, which is executed in a shell. By default, LockLint uses $SHELL. Alternatively, LockLint can execute any shell by specifying the shell to use on the lock_lint start command. For example:
% lock_lint start /bin/ksh |
starts a LockLint session up in the Korn shell.
if ($?LL_CONTEXT) then if ( -x $(HOME)/.ll_init ) source $(HOME)/.ll_init endif |
# Cause analyze subcommand to save state before analysis. alias analyze "lock_lint save before analyze;\ lock_lint analyze" # Change prompt to show we are in lock_lint. set prompt="lock_lint~$prompt" |
(Also see "start" on page 326.)
% lock_lint assert foo protects \Qlock_lint vars | grep ^:\Q |
% lock_lint vars -a \Qlock_lint members bar\Q | grep =bar::lock |
Since you are using a shell interface, a log of user commands can be obtained by using the shell's history function (the history level may need to be made large in the .ll_init file).
Temporary Files
LockLint puts temporary files in /var/tmp unless $TMPDIR is set. Makefile Rules
To modify your makefile to produce .ll files, first use the rule for creating a .o from a .c to write a rule to create a .ll from a .c. For example, from:
# Rule for making .o from .c in ../src. %.o: ../src/%.c $(COMPILE.c) -o $@ $< |
# Rule for making .ll from .c in ../src. %.ll: ../src/%.c cc $(CFLAGS) $(CPPFLAGS) $(FOO) $< |
If you use a suffix rule, you will need to define .ll as a suffix. For that reason some prefer to use % rules.
FOO_LLS = ${FOO_OBJS:%.o=%.ll} |
or, if they are in a subdirectory ll:
FOO_LLS = ${FOO_OBJS:%.o=ll/%.ll} |
.INIT: @if [ ! -d ll ]; then mkdir ll; fi |
Compiling Code
For LockLint to analyze your source code, you must first compile it using the -Zll option of the SunSoft ANSI C compiler. The SunSoft ANSI C compiler will then produce the LockLint database files (.ll files), one for each .c file compiled. Later you load the .ll files into LockLint with the load subcommand.-Zll
option automatically defines the macro __lock_lint.
LockLint Subcommands
The interface to LockLint consists of a set of subcommands that can be specified with the lock_lint command:
lock_lint [subcommand] |
In this example subcommand is one of a set of subcommands used to direct the analysis of the source code for data races and deadlocks. More information about subcommands can be found in "Command Reference" on page 297.
Starting and Exiting LockLint
The first subcommand of any LockLint session must be start, which starts a subshell of your choice with the appropriate LockLint context. Since a LockLint session is started within a subshell, you exit by exiting that subshell. For example, to exit LockLint when using the C shell, use the command exit. Setting the Tool State
LockLint's state consists of the set of databases loaded and the specified assertions. Iteratively modifying that state and rerunning the analysis can provide optimal information on potential data races and deadlocks. Since the analysis can be done only once for any particular state, the save, restore, and refresh subcommands are provided as a means to reestablish a state, modify that state, and retry the analysis. Suggested Approach for Checking an Application
Here are the basic steps involved in checking an application:
Note - These specifications may also be conveyed using source code annotations. See "Source Code Annotations" on page 281.
Note - These specifications may also be conveyed using source code annotations. See "Source Code Annotations" on page 281.
Note - These specifications may also be conveyed using source code annotations. See "Source Code Annotations" on page 281.
Note - It is best to handle the errors in order. Otherwise, problems with locks not being held on entry to a function, or locks being released while not held, can cause lots of misleading messages about variables not being properly protected.
All functions that are not called by any of the loaded files are called root functions. You may want to treat certain functions as root functions even though they are called within the loaded modules (for example, the function is an entry point for a library that is also called from within the library). Do this by using the declare root subcommand. You may also remove functions from the call graph by issuing the ignore subcommand.
LockLint knows about all the references to function pointers and most of the assignments made to them. Information about the function pointers in the currently loaded files is available through the funcptrs subcommand. Information about the calls made via function pointers is available via the pointer calls subcommand. If there are function pointer assignments that LockLint could not discover, they may be specified with the declare ... targets subcommand.
By default, LockLint tries to examine all possible execution paths. If the code uses function pointers, it's possible that many of the execution paths will not actually be followed in normal operation of the code. This can result in the reporting of deadlocks that do not really occur. To prevent this, use the lock_lint disallow and reallow subcommands to inform LockLint of execution paths that will never occur. To print out existing constraints, use the reallows and disallows subcommands.
One of LockLint's jobs is to determine if variable accesses are consistently protected. If you are unconcerned about accesses to a particular variable, you can remove it from consideration by using the ignore subcommand.
You may also consider using one of the source code annotations shown in Table 24-5, as appropriate.
SCHEME_PROTECTS_DATA |
read_only_data |
data_readable_without_lock |
now_invisible_to_other_threads |
now_visible_to_other_threads |
For more information, see "Source Code Annotations" on page 281.
Lock Management
Source code annotations are an efficient way to refine the assertions you make about the locks in your code. There are three types of assertions: protection, order, and side effects.
MUTEX_PROTECTS_DATA |
RWLOCK_PROTECTS_DATA |
SCHEME_PROTECTS_DATA |
DATA_READABLE_WITHOUT_LOCK |
RWLOCK_COVERS_LOCK |
A variation of the assert subcommand is used to assert that a given lock protects some piece of data or a function. Another variation, assert ... covers, asserts that a given lock protects another lock; this is used for hierarchical locking schemes.
You can also use the assert side effect subcommand to specify side effects. In some cases you may want to make side effect assertions about an external function and the lock is not visible from the loaded module (for example, it is static to the module of the external function). In such a case, you can "create" a lock by using a form of the declare subcommand.
Analysis
LockLint's primary role is to report on lock usage inconsistencies that may lead to data races and deadlocks. The analysis of lock usage occurs when you use the analyze subcommand. The result is a report on the following problems:
Another such subcommand is vars. The vars subcommand reports which locks are consistently held when a variable is read or written (if any). This information can be useful in determining the protection conventions in code where the original conventions were never documented, or the documentation has become outdated.
LockLint solves some of these problems by ignoring the likely cause or making simplifying assumptions. Some other problems can be avoided by using conditionally compiled code in the application. Towards this end, the compiler always defines the preprocessor macro __lock_lint when the -Zll switch is used. You can use this macro to make your code less ambiguous.
LockLint has trouble deducing:
if (x) pthread_mutex_lock(&lock1); |
#ifdef __lock_lint pthread_mutex_lock(&lock1); #else if (x) pthread_mutex_lock(&lock1); #endif |
if (x) {
pthread_mutex_lock(&lock1);
foo();
pthread_mutex_unlock(&lock1);
}
rc = pthread_mutex_trylock(&lock1); if (rc) ... |
pthread_mutex_t* lockp; pthread_mutex_lock(lockp); |
struct foo* p; pthread_mutex_lock(p->lock); p->bar = 0; |
if (rw_tryupgrade(&lock1)) { ... } |
if () { rw_tryupgrade(&lock1); ... } |
such that, wherever rw_tryupgrade() occurs, LockLint always assumes it succeeds.
void get_lock() { #ifdef __. struct bogus *p; pthread_mutex_lock(p->lock); #else <the real recursive locking code> #endif } |
Source Code Annotations
This section is organized as follows:
Assertions and NOTEs
An annotation is some piece of text inserted into your source code. (Generally, it has some semantic information that normal analyses cannot deduce.) You use annotations to tell LockLint things about your program that it cannot deduce for itself, either to keep it from excessively flagging problems or to have LockLint test for certain conditions. Annotations also serve to document code, in much the same way that comments do.
There are two types of ssource code annotations: assertions and NOTEs.
Why Use Source Code Annotations?
There are several reasons to use source code annotations. In many cases, such annotations are preferable to using a script of LockLint subcommands.
You may specify a location other than /usr/lib/note by setting the environment variable NOTEPATH, as in:
setenv NOTEPATH $NOTEPATH:other_locationTo use source code annotations, include the file note.h in your source or header files:
#include <note.h> |
LockLint NOTEs
NOTE Syntax
Many of the note-style annotations accept names--of locks or variables--as arguments. Names are specified using the syntax shown in Table 24-8.
In C, structure tags and types are kept in separate namespaces, making it possible to have two different structs by the same name as far as LockLint is concerned. When LockLint sees foo::bar, it will first look for a struct with tag foo; if it does not find one, it will look for a type foo and check that it represents a struct.
typedef struct { int a, b; } Foo;
These restrictions ensure that there is only one name by which the struct is known.NOTE(MUTEX_PROTECTS_DATA(p->lock, p->a p->b))
However, some of the annotations do accept expressions (rather than names); they are clearly marked.
struct_tag::{a b c d}is equivalent to the far more cumbersome text:
struct_tag::a struct_tag::b struct_tag::c struct_tag::dThis construct may be nested, as in:
foo::{a b.{c d} e}Where an annotation refers to a lock or another variable, a declaration or definition for that lock or variable should already have been seen.
If a name for data represents a structure, it refers to all non-lock (mutex or readers-writer) members of the structure. If one of those members is itself a structure, then all of its non-lock members are implied, and so on. However, LockLint understands the abstraction of a condition variable and therefore does not break it down into its constituent members.
NOTE(NoteInfo)or:
_NOTE(NoteInfo)The preferred use is NOTE rather than _NOTE. Header files that are to be used in multiple, unrelated projects, should use _NOTE to avoid conflicts. If NOTE has already been used, and you do not want to change, you should define some other macro (such as ANNOTATION) using _NOTE. For example, you might define an include file (say, annotation.h) that contains the following:
#define ANNOTATION _NOTE #include <sys/note.h> |
The NoteInfo that gets passed to the NOTE interface must syntactically fit one of the following:
This text uses NOTE to mean both NOTE and _NOTE, unless explicitly stated otherwise.
struct foo { int a, b; mutex_t lock; }; NOTE(MUTEX_PROTECTS_DATA(foo::lock, foo)) bar() {...} |
foo() { ...; NOTE(...) ...; ...; } |
if (x) NOTE(...) |
struct foo { int a; NOTE(...) int b; }; |
a = b NOTE(...) + 1;
typedef NOTE(...) struct foo Foo;
for (i=0; NOTE(...) i<10; i++) ...
NOTE(MUTEX_PROTECTS_DATA(Mutex, DataNameList))
NOTE(RWLOCK_PROTECTS_DATA(Rwlock, DataNameList))
NOTE(SCHEME_PROTECTS_DATA("description", DataNameList))The first two annotations tell LockLint that the lock should be held whenever the specified data is accessed.
The third annotation, SCHEME_PROTECTS_DATA, describes how data are protected if it does not have a mutex or readers-writer lock. The description supplied for the scheme is simply text and is not semantically significant; LockLint responds by ignoring the specified data altogether. You may make description anything you like.
Some examples will help show how these annotations are used. The first example is very simple, showing a lock that protects two variables:
mutex_t lock1; int a,b; NOTE(MUTEX_PROTECTS_DATA(lock1, a b)) |
mutex_t lock1; typedef struct { int mbr1, mbr2, mbr3, mbr4; } BAR; NOTE(MUTEX_PROTECTS_DATA(foo::lock, BAR)) NOTE(MUTEX_PROTECTS_DATA(lock1, BAR::mbr3)) |
Read-Only Variables
NOTE(READ_ONLY_DATA(DataNameList))
This annotation is allowed both outside and inside a function definition. It tells LockLint how data should be protected. In this case, it tells LockLint that the data should only be read, and not written.
This annotation is often used with data that is initialized and never changed thereafter. If the initialization is done at runtime before the data is visible to other threads, use annotations to let LockLint know that the data is invisible during that time.
Note - No error will be signaled if read-only data is written while it is considered invisible. Data is considered invisible when other threads cannot access it; for example, if other threads do not know about it.
Allowing Unprotected Reads
NOTE(DATA_READABLE_WITHOUT_LOCK(DataNameList))
This annotation is allowed both outside and inside a function definition. It informs LockLint that the specified data may be read without holding the protecting locks. This is useful with an atomically readable datum that stands alone (as opposed to a set of data whose values are used together), since it is valid to peek at the unprotected data if you do not intend to modify it. Hierarchical Lock Relationships
NOTE(RWLOCK_COVERS_LOCKS(RwlockName, LockNameList))
This annotation is allowed both outside and inside a function definition. It tells LockLint that a hierarchical relationship exists between a readers-writer lock and a set of other locks. Under these rules, holding the cover lock for write access affords a thread access to all data protected by the covered locks. Also, a thread must hold the cover lock for read access whenever holding any of the covered locks.NOTE(RWLOCK_COVERS_LOCKS(foo::lock, {bar zot}::lock))
Functions With Locking Side Effects
NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(MutexExpr))
NOTE(READ_LOCK_ACQUIRED_AS_SIDE_EFFECT(RwlockExpr))
NOTE(WRITE_LOCK_ACQUIRED_AS_SIDE_EFFECT(RwlockExpr))
NOTE(LOCK_RELEASED_AS_SIDE_EFFECT(LockExpr))
NOTE(LOCK_UPGRADED_AS_SIDE_EFFECT(RwlockExpr))
NOTE(LOCK_DOWNGRADED_AS_SIDE_EFFECT(RwlockExpr))
NOTE(NO_COMPETING_THREADS_AS_SIDE_EFFECT)
NOTE(COMPETING_THREADS_AS_SIDE_EFFECT)
These annotations are allowed only inside a function definition. Each tells LockLint that the function has the specified side effect on the specified lock--that is, that the function deliberately leaves the lock in a different state on exit than it was in when the function was entered. In the case of the last two of these annotations, the side effect is not about a lock but rather about the state of concurrency.
foo() { NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(&foo)) ... if (x && y) { ... } } |
foo() { ... if (x && y) { NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(&foo)) ... } } |
If a function has such a side effect, the effect should be the same on every path through the function. LockLint will complain about and refuse to analyze paths through the function that have side effects other than those specified.
Single-Threaded Code
NOTE(COMPETING_THREADS_NOW)
NOTE(NO_COMPETING_THREADS_NOW)
These two annotations are allowed only inside a function definition. The first annotation tells LockLint that after this point in the code, other threads exist that might try to access the same data that this thread will access. The second function specifies that this is no longer the case; either no other threads are running or whatever threads are running will not be accessing data that this thread will access. While there are no competing threads, LockLint does not complain if the code accesses data without holding the locks that ordinarily protect that data.
LockLint does not issue a warning if, during analysis, it encounters a COMPETING_THREADS_NOW annotation when it already thinks competing threads are present. The condition simply nests. No warning is issued because the annotation may mean different things in each use (that is the notion of which threads compete may differ from one piece of code to the next). On the other hand, a NO_COMPETING_THREADS_NOW annotation that does not match a prior COMPETING_THREADS_NOW (explicit or implicit) will cause a warning.
Note - LockLint realizes that when main() starts, no other threads are running.
Unreachable Code
NOTE(NOT_REACHED)
This annotation is allowed only inside a function definition. It tells LockLint that a particular point in the code cannot be reached, and therefore LockLint should ignore the condition of locks held at that point. This annotation need not be used after every call to exit(), for example, the way the lint annotation /* NOTREACHED */ is used. Simply use it in definitions for exit() and the like (primarily in LockLint libraries), and LockLint will figure out that code following calls to such functions is not reached. This annotation should seldom appear outside LockLint libraries. An example of its use (in a LockLint library) would be:
exit(int code) { NOTE(NOT_REACHED) } |
Lock Order
NOTE(LOCK_ORDER(LockNameList))
This annotation, which is allowed either outside or inside a function definition, specifies the order in which locks should be acquired. It is similar to the assert order and order subcommands. See "Command Reference" on page 297.
To avoid deadlocks, LockLint assumes that whenever multiple locks must be held at once they are always acquired in a well-known order. If LockLint has been informed of such orderings using this annotation, an informative message will be produced whenever the order is violated.
NOTE(LOCK_ORDER(a b c))
NOTE(LOCK_ORDER(b d))
LockLint will deduce the ordering:NOTE(LOCK_ORDER(a d))
It is not possible to deduce anything about the order of c with respect to d in this example. Variables Invisible to Other Threads
NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(DataExpr, ...))
NOTE(NOW_VISIBLE_TO_OTHER_THREADS(DataExpr, ...))
These annotations, which are allowed only within a function definition, tell LockLint whether or not the variables represented by the specified expressions are visible to other threads; that is, whether or not other threads could access the variables.
Foo* p = (Foo*) malloc(sizeof(*p)); NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*p)) p->a = bar; p->b = zot; NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*p)) add_entry(&global_foo_list, p); |
Calling a function never has the side effect of making variables visible or invisible. Upon return from the function, all changes in visibility caused by the function are reversed.
Assuming Variables Are Protected
NOTE(ASSUMING_PROTECTED(DataExpr, ...))
This annotation, which is allowed only within a function definition, tells LockLint that this function assumes that the variables represented by the specified expressions are protected in one of the following ways:
f(Foo* p, Bar* q) { NOTE(ASSUMING_PROTECTED(*p, *q)) p->a++; ... } |
Assertions Recognized by LockLint
Locklint recognizes some assertions as relevant to the state of threads and locks. (For more information, see the assert man page.)
Note - ASSERT() is used in kernel and driver code, whereas assert() is used in user (application) code. For simplicity's sake, this document uses assert() to refer to either one, unless explicitly stated otherwise.
Making Sure All Locks Are Released
assert(NO_LOCKS_HELD);
LockLint recognizes this assertion to mean that, when this point in the code is reached, no locks should be held by the thread executing this test. Violations will be reported during analysis. A routine that blocks might want to use such an assertion to ensure that no locks are held when a thread blocks or exits.
#define NO_LOCKS_HELD (!MUTEX_HELD(&a) && !MUTEX_HELD(&b)) #include <note.h> #include <synch.h> |
Doing so will not affect LockLint's testing of the assertion; that is, LockLint will still complain if any locks are held (not just a or b).
Making Sure No Other Threads Are Running
assert(NO_COMPETING_THREADS);
LockLint recognizes this assertion to mean that, when this point in the code is reached, no other threads should be competing with the one running this code. Violations (based on info provided by certain NOTE-style assertions) are reported during analysis. Any function that accesses variables without holding their protecting locks (operating under the assumption that no other relevant threads are out there touching the same data), should be so marked.
#define NO_COMPETING_THREADS (num_threads == 1) #include <note.h> #include <synch.h> |
Doing so will not affect LockLint's testing of the assertion.
Asserting Lock State
assert(MUTEX_HELD(lock_expr) && ...);
This assertion is widely used within the kernel. It performs runtime checking if assertions are enabled. The same capability exists in user code.
LockLint recognizes the use of MUTEX_HELD(), RW_READ_HELD(), RW_WRITE_HELD(), and RW_LOCK_HELD() macros, and negations thereof. Such macro calls may be combined using the && operators. For example, the following assertion will cause LockLint to check that a mutex is not held and that a readers-writer lock is write-held:
Note - The threads library performs a weaker test, only checking that some thread holds the lock. LockLint performs the stronger test.
assert(p && !MUTEX_HELD(&p->mtx) && RW_WRITE_HELD(&p->rwlock)); |
LockLint also recognizes expressions like:
MUTEX_HELD(&foo) == 0
Command Reference
This section is organized as follows:
Subcommand Summary
Table 24-9 identifies the LockLint subcommands.
Many LockLint subcommands require you to specify names of locks, variables, pointers, and functions. In C, it is possible for names to be ambiguous. See "Naming Conventions" on page 299 for details on specifying names to LockLint subcommands.
Exit Status of LockLint Subcommands
Table 24-10 lists the exit status of LockLint subcommands.
Naming Conventions
Many LockLint subcommands require you to specify names of locks, variables, pointers, and functions. In C, it is possible for names to be ambiguous; for example, there may be several variables named foo, one of them extern and others static.
Formal Name |
Meaning |
:func |
extern function |
file:func |
static function |
Table 24-12 lists the formal names for a variable, depending on its use as a lock, a pointer, or an actual variable.
In addition, any of these may be followed by an arbitrary number of .mbr specifications to denote members of a structure.
While LockLint refers to symbols in this way, you are not required to. You may use as little of the name as is required to unambiguously identify it. For example, you could refer to zot.c:foo/bar as foo/bar as long as there is only one function foo defining a variable bar. You can even refer to it simply as bar as long as there is no other variable by that name.
typedef struct { ... } foo; foo *p; func1() { p->bar = 0; } |
LockLint sees that as a reference to foo.c@42::bar.
int *ip = &i; *ip = 0; |
% lock_lint ignore foo in func \"func\" |
If two files with the same base name are included in an analysis, and these two files contain static variables by the same name, confusion can result. LockLint will think the two variables are the same.
int foo(void (*fp)()) { (*fp)(); { void (*fp)() = get_func(); (*fp)(); ... |
LockLint Subcommands
Some of these are equivalent to subcommands such as assert. Source code annotations are often preferable to subcommands, because they
analyze
analyze has the following syntax:
analyze [-hv] |
Analyzes the loaded files for lock inconsistencies that may lead to data races and deadlocks. This subcommand may produce a great deal of output, so you may want to redirect the output to a file. This subcommand can be run only once for each saved state. (See "save" on page 325).
-h (history) produces detailed information for each phase of the analysis. No additional errors are issued.
These subcommands tell LockLint how the programmer expects locks and variables to be accessed and modified in the application being checked. During analysis any violations of such assertions are reported.
Note - If a variable is asserted more than once, only the last assert takes effect.
assert side effect
Note - There is another kind of side effect called an inversion. See "Inversions" on page 331, and the locks or funcs subcommands, for more details.
Note - To avoid flooding the output with too many violations of a single assert... protects subcommand, a maximum of 20 violations of any given assertion is shown. This limit does not apply to the assert order subcommand.
declare |
mutex |
mutex . . . |
|
declare |
rwlocks |
rwlock ... |
|
declare |
func_ptr |
targets |
func ... |
declare |
nonreturning |
func ... |
|
declare |
one |
tag ... |
|
declare |
readable |
var ... |
|
declare |
root |
func ... |
|
These subcommands tell LockLint things that it cannot deduce from the source presented to it.
declare mutex mutex
declare rwlocks rwlock
struct foo { int (*fp)(); } foo1 = { bar };
% lock_lint declare foo::fp targets bar |
Caution - LockLint does not yet do the following (for the above example):
% lock_lint declare foo1.fp targets bar
However, it does manage to do both for assignments to function pointers. See "Inversions" on page 331.
disallow func ... |
% lock_lint disallow f g h |
Function pointers can make a program appear to follow many calling sequences that do not in practice occur. Bogus locking problems, particularly deadlocks, can appear in such sequences. (See also the description of the subcommand "reallow" on page 324.) disallow prevents LockLint from following such sequences.
disallows
disallows has the following syntax:
disallows |
Lists the calling sequences that are disallowed by the disallow subcommand.
exit
Actually, there is no exit subcommand for LockLint. To exit LockLint, use the exit command for the shell you are using. files
files has the following syntax:
files |
Lists the .ll versions of the source code files loaded with the load subcommand.
funcptrs
funcptrs has the following syntax:
funcptrs [-botu] func_ptr ... funcptrs [-blotuz] |
Lists information about the function pointers used in the loaded files. One line is produced for each function pointer.
-b
targets={ func ... } |
funcptrs [-botu] func_ptr ...
funcptrs [-blotuz]
funcs lists information about the functions defined and called in the loaded files. Exactly one line is printed for each function.
-a
asserts={ lock ... } read_asserts={ lock ... } |
side_effects={ effect [, effect] ... } |
-h
held={ lock ... }+{ lock ... } read_held={ lock ... }+{ lock ... } |
-i
f1() { pthread_mutex_unlock(&m); f2(); pthread_mutex_lock(&m); } f2() { f3(); } f3() { pthread_mutex_unlock(&m); pthread_mutex_lock(&m); } |
help [keyword] |
Without a keyword, help displays the subcommand set.
condvars |
locking |
example |
makefile |
ifdef |
names |
inversions |
overview |
limitations |
shell |
If environment variable PAGER is set, that program is used as the pager for help. If PAGER is not set, more is used.
ignore
ignore has the following syntax:
ignore func|var ... [ in func ... ] |
Tells LockLint to exclude certain functions and variables from the analysis. This exclusion may be limited to specific functions using the in func ... clause; otherwise the exclusion applies to all functions.
% lock_lint funcs -io | grep =ignored % lock_lint vars -io | grep =ignored |
show which functions and variables are ignored.
load
load has the following syntax:
load file ... |
% lock_lint load *.ll % lock_lint load ../foo/abcdef{1,2} % lock_lint load \Qfind . -name \*.ll -print\Q |
The text for load is changed extensively. To set the new text, type:
% lock_lint help load |
locks
locks has the following syntax:
locks [-co] lock ... locks [-col] locks [-col] [directly] affected by func ... locks [-col] [directly] inverted by func ... |
Lists information about the locks of the loaded files. Only those variables that are actually used in lock manipulation routines are shown; locks that are simply declared but never manipulated will not be shown.
-c
covered={ lock ... } |
cover=lock |
f1() { pthread_mutex_unlock(&m1); f2(); pthread_mutex_lock(&m1); } f2() { f3(); } f3() { pthread_mutex_unlock(&m2); pthread_mutex_lock(&m2); } |
members struct_tag |
Lists the members of the struct with the specified tag, one per line. For structures that were not assigned a tag, the notation file@line is used (for example, x.c@29), where the file and line number are the source location of the struct's declaration.
% lock_lint assert foo::lock protects \Qlock_lint members foo\Q |
Note - The members subcommand does not list any fields of the struct that are defined to be of type mutex_t, rwlock_t, krwlock_t, or kmutex_t.
order
order has the following syntax:
order [lock [lock]] order summary |
The order subcommand lists information about the order in which locks are acquired by the code being analyzed. It may be run only after the analyze subcommand.
order [lock [lock]]
% lock_lint order foo bar |
:foo :bar seen (first never write-held), valid |
:f :e :d :g :a :f :c :g :a |
:a :b :c :a
pointer calls |
Lists calls made through function pointers in the loaded files. Each call is shown as:
function [location of call] calls through funcptr func_ptr |
foo.c:func1 [foo.c,84] calls through funcptr bar::read |
means that at line 84 of foo.c, in func1 of foo.c, the function pointer bar::read (member read of a pointer to struct of type bar) is used to call a function.
reallow
reallow has the following syntax:
reallow func ... |
% lock_lint disallow f g h % lock_lint reallow d e f g h |
% lock_lint disallow f % lock_lint reallow e f |
Note - A reallow subcommand only suppresses the effect of a disallow subcommand if the sequences end the same. For example, after the following commands, the sequence d e f g h would still be disallowed:
% lock_lint disallow e f g h
% lock_lint reallow d e f g
reallows
reallows has the following syntax:
reallows |
Lists the calling sequences that are reallowed, as specified using the reallow subcommand.
refresh
refresh has the following syntax:
refresh |
restore |
Pops the saved state stack, restoring LockLint to the state of the top of the saved-state stack, and prints the description, if any, associated with that state.
save
save has the following syntax:
save description |
Saves the current state of the tool on a stack. The user-specified description is attached to the state. Saved states form a LIFO (Last-In-First-Out) stack, so that the last state saved is the first one restored.
saves
saves has the following syntax:
saves |
Lists the descriptions of the states saved on the saved stack via the save subcommand. The descriptions are shown from top to bottom, with the first description being the most recently saved state that has not been restored, and the last description being the oldest state saved that has not been restored.
start
start has the following syntax:
start [cmd] |
Starts a LockLint session. A LockLint session must be started prior to using any other LockLint subcommand. By default, start establishes LockLint's context and starts a subshell for the user, as specified via $SHELL, within that context. The only piece of the LockLint context exported to the shell is the environment variable LL_CONTEXT. LL_CONTEXT contains the path to the temporary directory of files used to maintain a LockLint session.
See "Limitations of LockLint" on page 277, for more on setting up the LockLint environment for a start subcommand.
Note - To exit a LockLint session use the exit command of the shell you are using.
Examples
The following examples show variations of the start subcommand.
% lock_lint start
% lock_lint start foo
% lock_lint start /bin/ksh |
sym name ... |
Lists the fully qualified names of various things the specified names could refer to within the loaded files. For example, foo might refer both to variable x.c:func1/foo and to function y.c:foo, depending on context.
unassert
unassert has the following syntax:
unassert vars var ... |
Undoes any assertion about locks protecting the specified variables. There is no way to remove an assertion about a lock protecting a function.
vars
vars has the following syntax:
vars [-aho] var ... vars [-ahilo] vars [-ahlo] protected by lock vars [-ahlo] [directly] read by func ... vars [-ahlo] [directly] written by func ... vars [-ahlo] [directly] accessed by func ... |
Lists information about the variables of the loaded files. Only those variables that are actually used are shown; variables that are simply declared in the program but never accessed will not be shown.
-a
assert=lock |
held={ <lock> ... } |
foo() { pthread_mutex_unlock(&mtx); ... pthread_mutex_lock(&mtx); } |
Lock inversions may be found using the commands:
% lock_lint funcs [directly] inverting lock ... % lock_lint locks [directly] inverted by func ... |
$ LOCKS=\Qlock_lint locks` $ lock_lint funcs calling \Qlock_lint funcs inverting $LOCKS\Q |
The following gives similar output, separated by lock:
for lock in \Qlock_lint locks\Q do echo "functions endangered by inversions of lock $lock" lock_lint funcs calling \Qlock_lint funcs inverting $lock\Q done |