650 likes | 677 Views
Microsoft’s Slam Project. Alon Flaisher December 2002. Opening. Static analysis of programs Microsoft’s Slam project Usage – verification of device drivers. Reviewed Papers. All papers by Ball and Rajamani: The Slam Project: Debugging System Software via Static Analysis.
E N D
Microsoft’s Slam Project Alon Flaisher December 2002
Opening • Static analysis of programs • Microsoft’s Slam project • Usage – verification of device drivers A.F.
Reviewed Papers All papers by Ball and Rajamani: • The Slam Project: Debugging System Software via Static Analysis. • Slic : a Specification Language for Interface Checking. • Checking Temporal Properties of Software with Boolean Programs. • Automatically Validating Temporal Safety Properties of Interfaces. • Bebop: A Symbolic Model Checker for Boolean Programs. A.F.
Outline • Introduction to the Slam toolkit • Slic specification language • Design – the Slam process • Implementation – C2BP, Bebop and Newton tools • Example • Restrictions • More on C2BP, Bebop and Newton • Summary and related work A.F.
Introduction to Slam • The goal of the Slam project is to check whether or not a program obeys “API usage rules“ that specify what it means to be a good client of an API. • The Slam toolkit statically analyzes a C program to determine whether or not it violates given usage rules. A.F.
Introduction – Cont. • The SLAM toolkit provides a fully automatic way of checking temporal safety properties of system software. • Violations are reported by the SLAM toolkit as paths over the program P. It never reports spurious error paths. • Instead, it detects spurious error paths and uses them to automatically refine the abstraction. • Since property checking is undecidable, the SLAM refinement algorithm may not converge. A.F.
Boolean Abstraction • Slam models abstractions of C programs using Boolean programs. • Boolean programs are C programs in which all variables have Boolean type. • Each Boolean variable in a Boolean program has an interpretation as a predicate over the infinite state space of the C program. A.F.
Refinement • Such abstraction often cause spurious counter examples. • Slam’s refinement algorithm finds Boolean program abstractions that are precise enough to validate properties. A.F.
Unique Aspects of Slam • Does not require the programmer to annotate the source program. • Minimizes noise (false error messages) through counterexample-driven refinement. • Slam exploits and extends results from program analysis, model checking and automated deduction. A.F.
Outline • Introduction to the Slam toolkit • Slic specification language • Design – the Slam process • Implementation – C2BP, Bebop and Newton tools • Example • Restrictions • More on C2BP, Bebop and Newton • Summary and related work A.F.
Specification • Slam project checks temporal safety properties of sequential C programs. • Roughly stated, temporal safety properties are those properties whose violation is witnessed by a finite execution trace. • Slic – Specification Language for Interface Checking. A.F.
Slic Specification Language • A low-level specification language designed to specify the temporal safety properties of APIs implemented in the C programming language. • Allows the definition of a safety automaton that monitors the execution behavior of a program at the level of function calls and returns. • Given a program P and a Slic specification S, a preprocessor creates an instrumented program P’ such that a unique label ERROR is reachable in P’ iff P does not satisfy S. A.F.
Slic Design • Out-of-line specification (because legacy code is checked) • C like syntax • A Slic specification consists of two basic parts: • A state structure definition • A list of transfer functions A.F.
enum { Unlocked=0, Locked=1 } state = Unlocked; void slic_abort() { SLIC_ERROR: ; } void KeAcquireSpinLock_return() { if (state == Locked) slic_abort(); else state = Locked; } void KeReleaseSpinLock_return { if (state == Unlocked) slic_abort(); else state = Unlocked; } Compilation to C Slic Spec for Spin Locks and its Compilation into C state { enum { Unlocked=0, Locked=1 } state = Unlocked; } KeAcquireSpinLock.return { if (state == Locked) abort; else state = Locked; } KeReleaseSpinLock.return { if (state == Unlocked) abort; else state = Unlocked; } A.F. Example P'
Outline • Introduction to the Slam toolkit • Slic specification language • Design – the Slam process • Implementation – C2BP, Bebop and Newton tools • Example • Restrictions • More on Newton and Bebop • Summary and related work A.F.
Implementation Three basic tools comprise the Slam toolkit: • C2BP (predicate abstractor for C) • Bebop (model checker for Boolean programs) • Newton (predicate discoverer) A.F.
Implementation – C2BP • C2BP is a tool that transforms a C program P into a Boolean program BP(P, E) with respect to a set of predicates E. • C2BP translates each procedure of the C program separately, enabling it to scale to large programs. A.F.
Implementation – Bebop • Bebop is a tool for performing reachability analysis of Boolean programs. • It combines interprocedural data flow analysis with BDDs (to efficiently represent the reachable states. A.F.
Implementation - Newton • Newton is a tool that discovers additional predicates to refine the Boolean program, by analyzing the feasibility of paths in the C program. • It takes a C program and a potential error path as input. It then determines if the path is feasible. • If “Yes”, the error path is reported. • if “No”, it identifies a small set of predicates that explain why the path is infeasible. A.F.
Outline • Introduction to the Slam toolkit • Slic specification language • Design – the Slam process • Implementation – C2BP, Bebop and Newton tools • Example • Restrictions • More on C2BP, Bebop and Newton • Summary and related work A.F.
Slam Process • The basic design of the Slam process is to iterate the creation, analysis and refinement of program abstractions, until: • a feasible execution path in P to ERROR is found, or • ERROR is shown not to be reachable, or • we run out of resources. A.F.
Boolean Program Abstraction • The Slam process creates a sound Boolean program abstractionB’ of the C program P’. • Boolean programs have all the control flow constructs of C programs, but contain only Boolean variables. • Each boolean variable in B’ conservatively tracks the state of a predicate (Boolean expression) in the C program. A.F.
Boolean Program Abstraction Cont. • If a reachability analysis of B’ determines that the label ERROR is not reachable in B’ then it is not reachable in P’. • It is possible that B’ may be too coarse an abstraction of P’ (that is, ERROR is reachable in B’ via a path p but ERROR is not reachable in P’ via p). • Counterexample-driven refinement is applied to create a more precise boolean program. A.F.
Refinement Algorithm • In the first iteration (i = 0), we start with a set of predicates E0 that are present in the conditionals of the Slic specification. • Let Eibe some set of predicates over the state of P’. Then iteration i of SLAM is carried out using the following steps: A.F.
Refinement Algorithm Cont. 1. Apply C2BP to construct the boolean program BP(P’;Ei). 2. Apply Bebop to check if there is a path in BP(P’;Ei) that reaches the ERROR label. If ERROR is not reachable, then the property is valid in P, and the algorithm terminates. A.F.
Refinement Algorithm Cont. 3. If there is such a path p, then we use Newton to check if p is feasible in P. There are two outcomes: • “yes": the property has been invalidated in P, and the algorithm terminates with an error path . • “no": Newton finds a set of predicates Fi that explain the infeasibility of path in P. 4. Let Ei +1:= Ei Fi , and i := i + 1, and proceed to the next iteration. A.F.
Outline • Introduction to the Slam toolkit • Slic specification language • Design – the Slam process • Implementation – C2BP, Bebop and Newton tools • Example • Restrictions • More on C2BP, Bebop and Newton • Summary and related work A.F.
PCI Device Driver Example • We present a snippet of (simplified) C code from a PCI device driver that processes interrupt request packets (IRPs). • Of interest here are the calls the code makes to acquire and release spin locks (KeAcquireSpinLock and KeReleaseSpinLock). A.F.
void example() { do { KeAcquireSpinLock(); A: KeAcquireSpinLock_return(); nPacketsOld = nPackets; req = devExt->WLHV; if(req && req->status){ devExt->WLHV = req->Next; KeReleaseSpinLock(); B: KeReleaseSpinLock_return(); irp = req->irp; if(req->status > 0){ irp->IoS.Status = SUCCESS; irp->IoS.Info = req->Status; } else { irp->IoS.Status = FAIL; irp->IoS.Info = req->Status; } SmartDevFreeBlock(req); IoCompleteRequest(irp); nPackets++; } } while(nPackets!=nPacketsOld); KeReleaseSpinLock(); C: KeReleaseSpinLock_return(); } Slic Tool P’ void example() { do { KeAcquireSpinLock(); nPacketsOld = nPackets; req = devExt->WLHV; if(req && req->status) { devExt->WLHV = req->Next; KeReleaseSpinLock(); irp = req->irp; if(req->status > 0){ irp->IoS.Status = SUCCESS; irp->IoS.Info = req->Status; } else { irp->IoS.Status = FAIL; irp->IoS.Info = req->Status; } SmartDevFreeBlock(req); IoCompleteRequest(irp); nPackets++; } } while(nPackets!=nPacketsOld); KeReleaseSpinLock(); } P
Slic Spec Compiled by C2BP into a Boolean Program enum { Unlocked=0, Locked=1 } state = Unlocked; void slic_abort() { SLIC_ERROR: ; } void KeAcquireSpinLock_return() { if (state == Locked) slic_abort(); else state = Locked; } void KeReleaseSpinLock_return { if (state == Unlocked) slic_abort(); else state = Unlocked; } A.F.
Slic Spec Compiled by C2BP into a Boolean Program decl {state==Locked}, {state==Unlocked}; void slic_abort() begin ERROR: skip; end void KeAcquireSpinLock_return() begin if ({state==Locked}) slic_abort(); else {state==Locked},{state==Unlocked} := T,F; end void KeReleaseSpinLock_return() begin if ({state == Unlocked}) slic_abort(); else {state==Locked},{state==Unlocked} := F,T; end The set of initial global predicates E0 The statement c=locked in the C code affects both predicates A.F. Return
C2BP E0 P’’=BP(P’;E0) P’ BP(P’;E0) abstracts P’ Abstracting the Instrumented C Program A.F.
void example() { do { KeAcquireSpinLock(); A: KeAcquireSpinLock_return(); nPacketsOld = nPackets; req = devExt->WLHV; if(req && req->status){ devExt->WLHV = req->Next; KeReleaseSpinLock(); B: KeReleaseSpinLock_return(); irp = req->irp; if(req->status > 0){ irp->IoS.Status = SUCCESS; irp->IoS.Info = req->Status; } else { irp->IoS.Status = FAIL; irp->IoS.Info = req->Status; } SmartDevFreeBlock(req); IoCompleteRequest(irp); nPackets++; } } while(nPackets!=nPacketsOld); KeReleaseSpinLock(); C: KeReleaseSpinLock_return(); } P’ P’’ void example() begin do skip; A: KeAcquireSpinLock_return(); skip; skip; if (*) then skip; skip; B: KeReleaseSpinLock_return(); skip; if (*) then skip; skip; else skip; skip; fi skip; skip; skip; fi while (*); skip; C: KeReleaseSpinLock_return(); end
void example() { do { KeAcquireSpinLock(); A: KeAcquireSpinLock_return(); nPacketsOld = nPackets; req = devExt->WLHV; if(req && req->status){ devExt->WLHV = req->Next; KeReleaseSpinLock(); B: KeReleaseSpinLock_return(); irp = req->irp; if(req->status > 0){ irp->IoS.Status = SUCCESS; irp->IoS.Info = req->Status; } else { irp->IoS.Status = FAIL; irp->IoS.Info = req->Status; } SmartDevFreeBlock(req); IoCompleteRequest(irp); nPackets++; } } while(nPackets!=nPacketsOld); KeReleaseSpinLock(); C: KeReleaseSpinLock_return(); } P’ P’’ The condition in the C program cannot be abstracted precisely using the predicates in E0 void example() begin do skip; A: KeAcquireSpinLock_return(); skip; skip; if (*) then skip; skip; B: KeReleaseSpinLock_return(); skip; if (*) then skip; skip; else skip; skip; fi skip; skip; skip; fi while (*); skip; C: KeReleaseSpinLock_return(); end P’’actually consists of this piece of code, plus the Slic spec translation
Model Checking P’’ decl {state==Locked}, {state==Unlocked}; void slic_abort() begin ERROR: skip; end void KeAcquireSpinLock_return() begin if ({state==Locked}) slic_abort(); else {state==Locked},{state==Unlocked} := T,F; end void KeReleaseSpinLock_return() begin if ({state == Unlocked}) slic_abort(); else {state==Locked},{state==Unlocked} := F,T; end void example() begin do … A: KeAcquireSpinLock_return(); … if (*) then … B: KeReleaseSpinLock_return(); … if (*) then … else … fi … fi while (*); C: KeReleaseSpinLock_return(); end A.F.
Model Checking P’’ decl {state==Locked}, {state==Unlocked}; void slic_abort() begin ERROR: skip; end void KeAcquireSpinLock_return() begin if ({state==Locked}) slic_abort(); else {state==Locked},{state==Unlocked} := T,F; end void KeReleaseSpinLock_return() begin if ({state == Unlocked}) slic_abort(); else {state==Locked},{state==Unlocked} := F,T; end void example() begin do … A: KeAcquireSpinLock_return(); … if (*) then … B: KeReleaseSpinLock_return(); … if (*) then … else … fi … fi while (*); C: KeReleaseSpinLock_return(); end A.F.
Model Checking P’’ decl {state==Locked}, {state==Unlocked}; void slic_abort() begin ERROR: skip; end void KeAcquireSpinLock_return() begin if ({state==Locked}) slic_abort(); else {state==Locked},{state==Unlocked} := T,F; end void KeReleaseSpinLock_return() begin if ({state == Unlocked}) slic_abort(); else {state==Locked},{state==Unlocked} := F,T; end void example() begin do … A: KeAcquireSpinLock_return(); … if (*) then … B: KeReleaseSpinLock_return(); … if (*) then … else … fi … fi while (*); C: KeReleaseSpinLock_return(); end A.F.
Model Checking P’’ decl {state==Locked}, {state==Unlocked}; void slic_abort() begin ERROR: skip; end void KeAcquireSpinLock_return() begin if ({state==Locked}) slic_abort(); else {state==Locked},{state==Unlocked} := T,F; end void KeReleaseSpinLock_return() begin if ({state == Unlocked}) slic_abort(); else {state==Locked},{state==Unlocked} := F,T; end void example() begin do … A: KeAcquireSpinLock_return(); … if (*) then … B: KeReleaseSpinLock_return(); … if (*) then … else … fi … fi while (*); C: KeReleaseSpinLock_return(); end A.F.
Model Checking P’’ decl {state==Locked}, {state==Unlocked}; void slic_abort() begin ERROR: skip; end void KeAcquireSpinLock_return() begin if ({state==Locked}) slic_abort(); else {state==Locked},{state==Unlocked} := T,F; end void KeReleaseSpinLock_return() begin if ({state == Unlocked}) slic_abort(); else {state==Locked},{state==Unlocked} := F,T; end void example() begin do … A: KeAcquireSpinLock_return(); … if (*) then … B: KeReleaseSpinLock_return(); … if (*) then … else … fi … fi while (*); C: KeReleaseSpinLock_return(); end A.F.
Newton Detects Feasibility void example() { do { KeAcquireSpinLock(); A: KeAcquireSpinLock_return(); nPacketsOld = nPackets; req = devExt->WLHV; if(req && req->status){ … } SmartDevFreeBlock(req); IoCompleteRequest(irp); nPackets++; } } while(nPackets!=nPacketsOld); KeReleaseSpinLock(); C: KeReleaseSpinLock_return(); } • Is the path found by Bebop [A, A, ERROR] feasible in the program P? • Newton detects that the path is infeasible in P. A.F.
New Set of Predicates – E1 • In the second iteration of the process, the predicate (nPackets = nPacketsOld) is added to the set of predicates E0 to result in a new set of predicates E1. • BP(P’’,E1) has one additional boolean variable (b) to represent the predicate. • The assignment statement nPackets = nPacketsOld; makes this condition true. A.F.
void example() { do { KeAcquireSpinLock(); A: KeAcquireSpinLock_return(); nPacketsOld = nPackets; req = devExt->WLHV; if(req && req->status){ devExt->WLHV = req->Next; KeReleaseSpinLock(); B: KeReleaseSpinLock_return(); irp = req->irp; if(req->status > 0){ irp->IoS.Status = SUCCESS; irp->IoS.Info = req->Status; } else { irp->IoS.Status = FAIL; irp->IoS.Info = req->Status; } SmartDevFreeBlock(req); IoCompleteRequest(irp); nPackets++; } } while(nPackets!=nPacketsOld); KeReleaseSpinLock(); C: KeReleaseSpinLock_return(); } void example() begin do skip; A: KeAcquireSpinLock_return(); b := T; skip; if (*) then skip; skip; B: KeReleaseSpinLock_return(); skip; if (*) then skip; skip; else skip; skip; fi skip; skip; b := choose(F,b); fi while (!b); skip; C: KeReleaseSpinLock_return(); end P’ P’’ P’’’ void example() begin do skip; A: KeAcquireSpinLock_return(); skip; skip; if (*) then skip; skip; B: KeReleaseSpinLock_return(); skip; if (*) then skip; skip; else skip; skip; fi skip; skip; skip; fi while (*); skip; C: KeReleaseSpinLock_return(); end
The Choose Function • Using a theorem prover, C2BP determines that if the predicate is true before the statement nPackets++, then it is false afterwards. • This is captured by the assignment statement in the boolean program b := choose(F,b). A.F.
Example Results • Applying Bebop to the new boolean program shows that the label ERROR is not reachable. • In performing its fixpoint computation, Bebop discovers that the following loop invariant holds at the end of the do-while loop:(state = Locked nPackets = nPacketsOld) (state = Unlocked nPackets nPacketsOld) A.F.
Outline • Introduction to the Slam toolkit • Slic specification language • Design – the Slam process • Implementation – C2BP, Bebop and Newton tools • Example • Restrictions • More on C2BP, Bebop and Newton • Summary and related work A.F.
Restrictions • The C program obeys a logical memory model in which the expressions *p and *(p+i) refer to the same object. • Slam process is expected to work well for programs whose behavior is governed by an underlying finite state protocol. A.F.
Outline • Introduction to the Slam toolkit • Slic specification language • Design – the Slam process • Implementation – C2BP, Bebop and Newton tools • Example • Restrictions • More on C2BP, Bebop and Newton • Summary and related work A.F.
More on C2BP – Pointers • C2BP tool uses alias analysis to determine whether or not an assignment statement through a pointer dereference can affect a predicate. • This increases the precision of the C2BP. Without alias analysis, conservative assumptions which lead to invalidating many predicates would be made. • In the PCI example, the points-to (alias) analysis shows that no variable in the C program can alias the address of the global state variable. A.F.
C2BP – Procedure Calls • Since boolean programs support procedure calls, C2BP is able to abstract procedures modularly. • After a call, the caller must conservatively update local state that may have been modified by the callee. • C2BP provides a sound and precise approach to abstracting procedure calls that takes such side-effects into account. A.F.