Professional Documents
Culture Documents
Slam
Slam
SLAM
Thomas Ball
Testing, Verification and Measurement
Sriram K. Rajamani
Software Productivity Tools
Microsoft Research
http://research.microsoft.com/slam/
People behind SLAM
Summer interns
– Sagar Chaki, Todd Millstein, Rupak Majumdar (2000)
– Satyaki Das, Wes Weimer, Robby (2001)
– Jakob Lichtenberg, Mayur Naik (2002)
Visitors
– Giorgio Delzanno, Andreas Podelski, Stefan Schwoon
Windows Partners
– Byron Cook, Vladimir Levin, Abdullah Ustuner
Outline
• Part I: Overview (30 min)
– overview of SLAM process
– demonstration (Static Driver Verifier)
– lessons learned
Source Code
SLAM – Software Model Checking
• SLAM innovations
– boolean programs: a new model for software
– model creation (c2bp)
– model checking (bebop)
– model refinement (newton)
• SLAM toolkit
– built on MSR program analysis infrastructure
SLIC
• Finite state language for stating rules
– monitors behavior of C code
– temporal safety properties (security automata)
– familiar C syntax
KeReleaseSpinLock.entry {
if (s==Unlocked) abort;
else s = Unlocked;
}
The SLAM Process
c2bp boolean
program
prog. P
slic prog. P’
bebop
SLIC rule
predicates path
newton
Example
Does this code
obey the
locking rule?
do {
KeAcquireSpinLock();
nPacketsOld = nPackets;
if(request){
request = request->Next;
KeReleaseSpinLock();
nPackets++;
}
} while (nPackets != nPacketsOld);
KeReleaseSpinLock();
Example
Model checking
boolean program
(bebop)
U do {
KeAcquireSpinLock();
L
L if(*){
L
KeReleaseSpinLock();
U
}
L U } while (*);
L U KeReleaseSpinLock();
U E
Example
Is error path feasible
in C program?
(newton)
U do {
KeAcquireSpinLock();
L
nPacketsOld = nPackets;
L if(request){
L request = request->Next;
KeReleaseSpinLock();
U nPackets++;
}
L U } while (nPackets != nPacketsOld);
L U KeReleaseSpinLock();
U E
Example
Add new predicate
b : (nPacketsOld == nPackets) to boolean program
(c2bp)
U do {
KeAcquireSpinLock();
L
nPacketsOld = nPackets; b = true;
L if(request){
L request = request->Next;
KeReleaseSpinLock();
U nPackets++; b = b ? false : *;
}
L U } while (nPackets != nPacketsOld); !b
L U KeReleaseSpinLock();
U E
Example Model checking
b : (nPacketsOld == nPackets)
refined
boolean program
(bebop)
U do {
KeAcquireSpinLock();
L
b = true;
b L if(*){
b L
KeReleaseSpinLock();
b U b = b ? false : *;
}
b L !b U } while ( !b );
b L U KeReleaseSpinLock();
b U E
Example Model checking
b : (nPacketsOld == nPackets)
refined
boolean program
(bebop)
U do {
KeAcquireSpinLock();
L
b = true;
b L if(*){
b L
KeReleaseSpinLock();
b U b = b ? false : *;
}
b L !b U } while ( !b );
b L KeReleaseSpinLock();
b U
Observations about SLAM
• Automatic discovery of invariants
– driven by property and a finite set of (false) execution paths
– predicates are not invariants, but observations
– abstraction + model checking computes inductive invariants
(boolean combinations of observations)
• Counterexample-driven refinement
– terminates in practice
– incompleteness of theorem prover not an issue
What is hard?
• Abstracting
– from a language with pointers (C)
– to one without pointers (boolean programs)
• Repercussions:
– newton has to copy between scopes
– c2bp has to model side-effects by value-result
– finite depth precision on the heap is all
boolean programs can do
What changed?
• Interface between newton and c2bp
• March 2002
– Bill Gates review
• May 2002
– Windows committed to hire two people with model checking background to
support Static Driver Verifier (SLAM+driver rules)
• July 2002
– running SLAM on 100+ drivers, 20+ properties
• September 3, 2002
– made initial release of SDV to Windows (friends and family)
• April 1, 2003
– made wide release of SDV to Windows (any internal driver developer)
Part II: Basic SLAM
C-
Types τ ::= void | bool | int | ref τ
Expressions e ::= c | x | e1 op e2 | &x | *x
LExpression l ::= x | *x
L3: S3;
Example, in C
int g;
boolean
c2bp
program
prog. P
slic prog. P’
bebop
SLIC rule
predicates path
newton
c2bp: Predicate Abstraction for
C Programs
Given
• P : a C program
• F = {e1,...,en}
– each ei a pure boolean expression
– each ei represents set of states for which ei is true
• Mixed predicates:
– predicates using both local variables and global variables
– complicate “return” processing
– covered in advanced topics
C2bp Algorithm
• Performs modular abstraction
– abstracts each procedure in isolation
Preds: {x==y}
{g==0}
{a==b}
int g; void cmp (int a , int b) {
goto L1, L2
main(int x, int y){
L1: assume(a==b);
cmp(x, y); g = 0;
return;
assume(!g);
assume(x != y) L2: assume(a!=b);
assert(0); g = 1;
} return;
}
main( {x==y} ) {
Preds: {x==y}
{g==0}
{a==b}
}
}
int g; void equal (int a , int b) {
goto L1, L2
main(int x, int y){
L1: assume(a==b);
cmp(x, y); g = 0;
return;
assume(!g);
assume(x != y) L2: assume(a!=b);
assert(0); g = 1;
} return;
}
main( {x==y} ) {
Preds: {x==y}
L1: assume( {a==b} );
{g==0} = T;
cmp( {x==y} ); {g==0} return;
• WP(y=y+1, y<5) =
(y<5) [y -> y+1] =
(y+1<5) =
(y<4)
WP Problem
• WP(s, ei) not always expressible via { e1,...,en }
• Example
• ImpliesF(e)
– best boolean function over F that implies e
• ImpliedByF(e)
– best boolean function over F implied by e
– ImpliedByF(e) = !ImpliesF(!e)
ImpliesF(e) and ImpliedByF(e)
ImpliedByF(e)
ImpliesF(e)
Computing ImpliesF(e)
• minterm m = d1 && ... && dn
– where di = ei or di = !ei
• ImpliesF(e)
– disjunction of all minterms that imply e
• Naïve approach
– generate all 2n possible minterms
– for each minterm m, use decision procedure to
check validity of each implication m⇒e
Weakest Precondition:
WP(y=y+1, x==y) = x==y+1
Abstraction of assignment in B:
{x==y} = {x==y} ? false : *;
Absracting Assumes
• WP( assume(e) , Q) = e⇒Q
• Example:
F = {x==2, x<5}
assume(x < 2) is abstracted to:
assume( {x<5} && !{x==2} )
Abstracting Procedures
• Each predicate in F is annotated as being
either global or local to a particular
procedure
• Procedure return
– NOP for C-- with assumption that all predicates
mention either only globals or only locals
– with pointers and with mixed predicates:
• Most complicated part of c2bp
• Covered in the advanced topics section
int g; void cmp (int a , int b) {
Goto L1, L2
main(int x, int y){
L1: assume(a==b);
cmp(x, y); g = 0;
return;
assume(!g);
assume(x != y) L2: assume(a!=b);
assert(0); g = 1;
} return;
}
main( {x==y} ) {
L1: assume( {a==b} );
{g==0} {g==0} = T;
cmp( {x==y} );
return;
assume( {g==0} );
{a==b}
L2: assume( !{a==b} );
assume( !{x==y} );
{g==0} = F;
assert(0);
return;
}
}
Precision
• For program P and E = {e1,...,en}, there exist two “ideal”
abstractions:
– Boolean(P,E) : most precise abstraction
– Cartesian(P,E) : less precise abtraction, where each boolean
variable is updated independently
– [See Ball-Podelski-Rajamani, TACAS 00]
• Theory:
– with an “ideal” theorem prover, c2bp can compute
Cartesian(P,E)
• Practice:
– c2bp computes a less precise abstraction than Cartesian(P,E)
– we use Das/Dill’s technique to incrementally improve precision
– with an “ideal” theorem prover, the combination of c2bp +
Das/Dill can compute Boolean(P,E)
The SLAM Process
c2bp boolean
program
prog. P
slic prog. P’
bebop
SLIC rule
predicates path
newton
Bebop
What is PE(v)?
A set of bit-vectors
[4] assert(F);
} e2=e
void cmp ( e2 ) {
[5]Goto L1, L2 gz=gz’& e2=e2’
[6]L1: assume( e2 );
gz=gz’& e2=e2’& e2’=T
[7] gz = T; goto L3; e2=e2’& e2’=T & gz’=T
[8]L2: assume( !e2 ); gz=gz’& e2=e2’& e2’=F
[9]gz = F; goto L3 e2=e2’& e2’=F & gz’=F
c2bp boolean
program
prog. P
slic prog. P’
bebop
SLIC rule
predicates path
newton
Newton
• Given an error path p in boolean program
B
– is p a feasible path of the corresponding C
program?
• Yes: found an error
• No: find predicates that explain the infeasibility
Assume(e):
let val = Eval(e) in
Cond := Cond and val
let result = CheckConsistency(Cond) in
if (result == “inconsistent”) then
GenerateInconsistentPredicates()
End
• Dependencies
– for each condition or store, keep track of which values
where used to generate this value
– traverse dependency during predicate generation
int g; void cmp (int a , int b) {
Goto L1, L2
main(int x, int y){
L1: assume(a==b);
cmp(x, y); g = 0;
return;
assume(!g);
assume(x != y) L2: assume(a!=b);
assert(0); g = 1;
} return;
}
int g; void cmp (int a , int b) {
Goto L1, L2
main(int x, int y){
L1: assume(a==b);
cmp(x, y); g = 0;
return;
assume(!g);
assume(x != y) L2: assume(a!=b);
assert(0); g = 1;
} return;
Global: }
main: Conditions:
(1) x: X
(2) y: Y
int g; void cmp (int a , int b) {
Goto L1, L2
main(int x, int y){
L1: assume(a==b);
cmp(x, y); g = 0;
return;
assume(!g);
assume(x != y) L2: assume(a!=b);
assert(0); g = 1;
} return;
Global: }
main: Conditions:
(1) x: X
(2) y: Y Map:
cmp: X→A
(3) a: A Y→B
(4) b: B
int g; void cmp (int a , int b) {
Goto L1, L2
main(int x, int y){
L1: assume(a==b);
cmp(x, y); g = 0;
return;
assume(!g);
assume(x != y) L2: assume(a!=b);
assert(0); g = 1;
} return;
Global: }
(6) g: 0
main: Conditions:
(1) x: X
(2) y: Y Map: (5) (A == B) [3, 4]
cmp: X→A
(3) a: A Y→B
(4) b: B
int g; void cmp (int a , int b) {
Goto L1, L2
main(int x, int y){
L1: assume(a==b);
cmp(x, y); g = 0;
return;
assume(!g);
assume(x != y) L2: assume(a!=b);
assert(0); g = 1;
} return;
Global: }
(6) g: 0
main: Conditions:
(1) x: X
(2) y: Y Map: (5) (A == B) [3, 4]
(6) g: 0
main: Conditions:
(1) x: X
(2) y: Y (5) (A == B) [3, 4]
(6) g: 0
main: Conditions:
(1) x: X
(2) y: Y Contradictory! (5) (A == B) [3, 4]
(6) g: 0
main: Conditions:
(1) x: X
(2) y: Y Contradictory! (5) (A == B) [3, 4]
• Extra complications:
– address operator (&) in C
– multiple levels of pointer dereference in C
What changes with pointers?
• C2bp
– abstracting assignments
– abstracting procedure returns
• Newton
– simulation needs to handle pointer accesses
– need to copy local heap across scopes to match Bebop’s
semantics
– need to refine imprecise alias analysis using predicates
• Bebop
– remains unchanged!
Assignments + Pointers
Statement in P: Predicates in E:
*p = 3 {x==5}
Weakest Precondition:
WP( *p=3 , x==5 ) = x==5 What if *p and x alias?
int R (int f) {
Q() { f=x
int r = f+1; {f==pre(f)}
{x==1} int x = 1;
pre(f) == pre(x) f = 0;
{r==pre(f)+1}
{x==2} x = R(x)
x=r return r;
}
}
WP(x=r, x==2) = r==2 pre(f)==pre(x) and pre(x)==1 and r==pre(f)+1 implies r==2
{x==1} s
Q() { bool R ( {f==pre(f)} ) {
{x==1},{x==2} = T,F;
{r==pre(f)+1} = {f==pre(f)};
s = R(T); {f==pre(f)} = *;
{x==2} = s & {x==1}; return {r==pre(f)+1};
} }
predicate call/return relation call/return assign
int R (int f) {
Q() { f=x
int r = f+1; {f==pre(f)}
{x==1} int x = 1;
pre(f) == pre(x) f = 0;
{r==pre(f)+1}
{x==2} x = R(x)
x=r return r;
}
}
WP(x=r, x==2) = r==2 pre(f)==pre(x) and pre(x)==1 and r==pre(f)+1 implies r==2
{x==1} s
Q() { bool R ( {f==pre(f)} ) {
{x==1},{x==2} = T,F;
{r==pre(f)+1} = {f==pre(f)};
s = R(T); {f==pre(f)} = *;
{x==1}, {x==2} = *, s & {x==1}; return {r==pre(f)+1};
} }
Extending Pre-states
• Suppose formal parameter is a pointer
– eg. P(int *f)
• pre( *f )
– value of *f upon entry to P
– can’t change during P
• * pre( f )
– value of dereference of pre( f )
– can change during P
predicate call/return relation call/return assign
{a==pre(a)}
Q() { {*pre(a)==pre(*a)}
{x==1} a = &x int R (int *a) {
int x = 1;
R(&x); pre(a) = &x *a = *a+1;
{x==2} {*pre(a)=pre(*a)+1}
}
}
x = 0; Pts-to(p) {x==0} = T;
y = 0; = skip;
{ &x, &y}
p = &y skip;
*p = *p + 1; {x==0} = *;
{x==0}
assume(x!=0); assume( !{x==0} );
p = &x; skip;
Newton: Path is Infeasible
x = 0;
{x==0} = T; x = 0; y = 0;
skip; y = 0; p = &y
skip; p = &y
if (p == &x)
{x==0} = *; *p = *p + 1; x = x + 1;
else
assume(!{x==0} ); assume(x!=0); y = y + 1;
p = &x;
Consider values in abstract trace
{x==0} = T; x = 0;
skip; y = 0;
{x==0} is true
skip; p = &y
{x==0} = *; *p = *p + 1; T
E N
T
{x==0} IisSfalse
assume(!{x==0} ); assume(x!=0); N S
CO
IN
skip; p = &x;
Consider values in abstract trace
{x==0} = T; x = 0;
skip; y = 0;
skip; p = &y
WP(*p = *p +1, !(x==0) )
= ((p == &x) and !(x==-1))
{x==0} = *; *p = *p + 1; or ((p != &x) and !(x==0))
• Newton
– simulation needs to handle pointer accesses
– need to copy local heap across scopes to match Bebop’s
semantics
– need to refine imprecise alias analysis using predicates
• Bebop
– remains unchanged!
What worked well?
• Specific domain problem
• Safety properties
• Shoulders & synergies
• Separation of concerns
• Summer interns & visitors
– Sagar Chaki, Todd Millstein, Rupak Majumdar (2000)
– Satyaki Das, Wes Weimer, Robby (2001)
– Jakob Lichtenberg, Mayur Naik (2002)
– Giorgio Delzanno, Andreas Podelski, Stefan Schwoon
• Windows Partners
– Byron Cook, Vladimir Levin, Abdullah Ustuner
Future Work
• Concurrency
– SLAM analyzes drivers one thread at a time
– Work in progress to analyze interleavings between threads
• Rules and environment-models
– Large scale development or rules and environment-models is a
challenge
– How can we simplify and manage development of rules?
• Modeling C semantics faithfully
• Theory:
– Prove that SLAM will make progress on any property and any
program
– Identify classes of programs and properties on which SLAM will
terminate
Further Reading
See papers, slides from:
http://research.microsoft.com/slam
Glossary
Model checking Checking properties by systematic exploration of the state-space of a
model. Properties are usually specified as state machines, or using
temporal logics
Safety properties Properties whose violation can be witnessed by a finite run of the system.
The most common safety properties are invariants
Boolean programs “C”-like programs with only boolean variables. Invariant checking and
reachability is decidable for boolean programs.
Predicate A Boolean expression over the state-space of the program eg. (x < 5)
Predicate abstraction A technique to construct a boolean model from a system using a given set
of predicates. Each predicate is represented by a boolean variable in the
model.
Weakest precondition The weakest precondition of a set of states S with respect to a statement T
is the largest set of states from which executing T, when terminating,
always results in a state in S.