Catching bugs in software - the Department of Computer and
Download
Report
Transcript Catching bugs in software - the Department of Computer and
Catching Bugs in Software
Rajeev Alur
Systems Design Research Lab
University of Pennsylvania
www.cis.upenn.edu/~alur/
Software Reliability
Software bugs are pervasive
Bugs can be expensive
Bugs can cost lives
Bulk of development cost is in validation, testing, bug fixes
Old problem that just won’t go away
Many approaches and decades of research
Systematic testing
Programming languages technology (e.g. types)
Formal methods (specification and verification)
Grand challenge for computer science:
Tools for designing “correct” software
software/model
correctness
specification
Verifier
Yes/proof
No/bug
Correctness is formalized as a mathematical claim
to be proved or falsified rigorously
always with respect to the given specification
A brief history of formal verification
1.
Structured programs; Hoare logic; 1969
2. Network protocols; State-space search; 1990
3. Cache coherency protocols; Symbolic search; 1995
4. Device drivers; Automated abstraction; 2001
1. Program Verification
Hoare logic for formalizing correctness of structured
programs (late 1960s)
Typical examples: sorting, graph algorithms
Specification for sorting
Permute(A,B): array B is a permutation of elements in
array A
Sorted(A): for 0<i<n, A[i]<=A[i+1]
Function sort is correct if following holds
{True} B := sort(A) {Permute(A,B)&Sorted(B)}
Provides calculus for pre/post conditions of structured
programs
Sample Proof: Bubble Sort
Key to proof:
BubbleSort (A : array[1..n] of int) {
B = A : array[1..n] of int;
Finding suitable
for (i=0; i<n; i++) {
loop invariants
Permute(A,B)
Sorted(B[n-i,n])
for 0<k<=n-i-1 and n-i<=k’<=n B[k]<=B[k’]
for (j=0; j<n-i; j++) {
Permute(A,B), Sorted(B[n-i,n],
for 0<k<=n-i-1 and n-i<=k’<=n B[k]<=B[k’]
for 0<k<j B[k] <= B[j]
if (B[j]>B[j+1]) swap(B,j,j+1)
}
};
return B;
}
Program Verification
Powerful mathematical logic (e.g. first-order logic,
Higher-order logics) needed for formalization
Automation extremely difficult
Finding proof decomposition requires great expertise
Alive and well, but not booming
Contemporary theorem provers: HOL, PVS, ACL2
provide decision procedures and tactics for
decomposition
Main applications: Microprocessor verification,
Correctness of JVM…
2. Protocol Analysis
Automated analysis of finite-state protocols
Network protocols, Distributed algorithms
Great progress in the last 20 years
Protocol modeled as communicating finite-state
processes
Correctness specified using temporal logic
Verification performed automatically to reveal errors
Highly optimized state-space search techniques
Model checker SPIN from Bell Labs
ACM Software Systems award (2001)
Success in finding high-quality bugs in real systems
(NASA space shuttle, Lucent’s Pathstar switch)
Example: X.21 Communication Protocol
State-space Explosion !!
Analysis is basically a reachability problem in a graph
Nodes are states, where each state gives values of all the variables of all
the communicating processes
An edge represents execution of a single action of one of the processes
(asynchronous communication)
Size of graph grows exponentially as the number of bits
required for state encoding, but…
Graph is constructed only incrementally, on-the-fly
Clever hashing and state compaction techniques
Many techniques for exploiting structure: symmetry, data
independence, partial order reduction …
Millions of states can be explored quickly to reveal bugs
Great flexibility in modeling
Abstract many details, simplify
Scale down parameters (buffer size, number of network nodes…)
3. Symbolic Model Checking
Constraint-based analysis of Boolean systems
Cache coherency protocols, Memory controllers,…
Active in the past 12 years
Symbolic Boolean representations (propositional
formulas, BDDs) used to encode system dynamics
Correctness specified using temporal logic CTL
Fix-point computation over state sets
Highly optimized memory management
Model checker SMV from CMU
ACM Kannellakis Theory in Practice Award (1999)
Success in finding high-quality bugs in hardware
applications (VHDL/Verilog code)
Cache consistency: Gigamax
Real design of a distributed multiprocessor
Global bus
UIC
UIC
M
UIC
P
M
P
Cluster bus
Read-shared/read-owned/write-invalid/write-shared/…
Deadlock found using SMV
Similar successes: IEEE Futurebus+ standard, IBM/Intel/Motorola…
Symbolic Reachability Problem
Model variables X ={x1, … xn}
Each var is of finite type, say, boolean
Initialization: I(X) condition over X
Update: T(X,X’)
How new vars X’ are related to old vars X as a result of
executing one step of the program
Target set: F(X)
Computational problem:
Can F be satisfied starting with I by repeatedly applying T ?
Graph Search problem
Symbolic Solution
Data type: region to represent state-sets
R:=I(X)
Repeat
If R intersects T report “yes”
else if R contains Post(R) report “no”
else R := R union Post(R)
Post(R(X))= (Exists X. R(X) and T(X,X’))[X’ -> X]
Operations needed: union, intersection, test for
inclusion/emptiness, projection, renaming
Binary Decision Diagrams
Popular representations for Boolean functions
0
0
0
c
a
0
1
0
d
1
b
1
1
1
Like a decision graph
No redundant nodes
No isomorphic subgraphs
Variables tested in fixed order
Function: (a and b) or (c and d)
Key properties:
Canonical!
Size depends on choice of ordering of variables
Operations such as union/intersection are efficient
Symbolic Search Techniques
Size of BDDs can explode during search, and is
quite unpredictable
Years of research leading to plethora of heuristics
Significant industrial interest
In-house groups: Cadence, Synopsis, IBM, NEC…
Commercial model checkers/verification consultants
Recent focus: SAT solvers
Checking whether F can be reached within k steps can be
formulated as a satisfiability of a propositional formula
with nk variables
Extremely fast solvers such as zChaff (from Princeton)
can solve problems with 1000 vars fast !
SAT + BDD can be combined to great effects
4. Software Model Checking via Abstraction
Can we apply model checking to C programs?
SPIN approach is fine for analyzing models, but constructing
models is expensive, and models have no relation to code
Given a program P, build an abstract finite-state (Boolean)
model A such that set of behaviors of P is a subset of those
of A (conservative abstraction)
Basic ideas around for a while, but all components put
together effectively only recently by Microsoft Research
team in the project SLAM
Shown to be effective on Windows device drivers, Linux
source code (about 10K lines of code)
Program Abstraction
int x, y;
if x>0 {
…………
y:=x+1
……….}
else {
…………
y:=x+1
……….}
Predicate Abstraction
bx: x>0; by : y>0
bool bx, by;
if bx {
…………
by:=true
……….}
else {
…………
by:={true,false}
……….}
Verification Example
Does this code
obey the
locking spec?
do {
KeAcquireSpinLock();
Rel
nPacketsOld = nPackets;
Acq
Unlocked
Locked
Rel
Acq
Error
Specification
if(request){
request = request->Next;
KeReleaseSpinLock();
nPackets++;
}
} while (nPackets != nPacketsOld);
KeReleaseSpinLock();
Initial Abstraction
do {
KeAcquireSpinLock();
U
L
if(*){
L
L
KeReleaseSpinLock();
U
L
U
L
U
U
E
}
} while (*);
KeReleaseSpinLock();
Model checking
boolean program
Using BDDs
Feasibility Analysis
do {
KeAcquireSpinLock();
U
Is error path feasible
in C program?
Requires theorem
prover for constraint
propagation
L
nPacketsOld = nPackets;
L
L
U
L
U
L
U
U
E
if(request){
request = request->Next;
KeReleaseSpinLock();
nPackets++;
}
} while (nPackets != nPacketsOld);
KeReleaseSpinLock();
Predicate Discovery
b : (nPacketsOld == nPackets)
do {
KeAcquireSpinLock();
U
Add new predicate
to boolean program
New techniques
L
nPacketsOld = nPackets; b = true;
L
L
U
L
U
L
U
U
E
if(request){
request = request->Next;
KeReleaseSpinLock();
nPackets++; b = b ? false : *;
}
} while (nPackets != nPacketsOld); !b
KeReleaseSpinLock();
Revised Abstraction
b : (nPacketsOld == nPackets)
do {
KeAcquireSpinLock();
U
L
b = true;
b L
if(*){
b L
b U
b L
b L
b U
!b U
KeReleaseSpinLock();
b = b ? false : *;
}
} while ( !b );
KeReleaseSpinLock();
Model checking
refined
boolean program
Abstraction Based Techniques
Tools for verifying source code combine many techniques
Program analysis techniques such as slicing
Abstraction
Model checking
Refinement from counter-examples
New challenges for model checking (beyond finite-state
reachability analysis)
Recursion gives pushdown control
Pointers, dynamic creation of objects, inheritence….
A very active and emerging research area
Research in Formal Methods
software
Modeling languages
Hierarchy, recursion
Real-time, Hybrid
Stochastic
model
correctness
specification
Bridging the gap
Model extraction
Model-based design:
from models to code
Decision procedures
Algorithms engineering
Automated abstraction
Compositional analysis
Verifier
proof
bug
Temporal logics
Automata
From requirements to specs
Current Research Projects
Foundations
Analysis of context-free models
Stochastic hybrid systems
Decision problems for timed automata
Algorithms Engineering
Combining SAT, BDDs, Abstraction
Symbolic solutions to games
Model-based design
From hybrid automata to embedded software
From state-machine models to Java card policies
Software verification for Java classes
Classical Model Checking
Both model M and specification S are regular (finite-state)
M as a generator of all possible behaviors
S as an acceptor of “good” behaviors (verification is language
inclusion of M in S) or as an acceptor of “bad” behaviors (verification
is checking emptiness of intersection of M and S)
Typical specifications (using automata or temporal logic)
Safety: Always not ( both P1 and P2 have write-exclusive copy)
Liveness: Always (if P1 requests, eventually it gets response)
Robustness of theory of regular languages helps in many ways
M can be product of several components (closure under intersection)
For liveness properties, one needs to consider automata over
infinite words, but corresponding theory of omega-regular
languages is well developed and well understood
Boolean Programs
main() {
bool y;
…
x = P(y);
…
z = P(x);
…
}
bool P(u: bool) {
…
return Q(u);
}
bool Q(w: bool) {
if …
else return P(~w)
}
Recursive State Machines
A1
A2
A2
A2
A3
A3
Entry-point
A3
Box (superstate)
A1
Exit-point
Model Checking of Recursive Models
Control-flow requires stack, so model M defines a
context-free language
Algorithms exist for checking regular specifications
against context-free models
Emptiness of pushdown automata is solvable
Product of a regular language and a context-free
language is context-free
But, checking context-free spec against a context-free
model is undecidable!
Context-free languages are not closed under intersection
Inclusion as well as emptiness of intersection undecidable
Are Context-free Specs Interesting?
Classical Hoare-style pre/post conditions
If p holds when procedure A is invoked, q holds upon return
Total correctness: every invocation of A terminates
Integral part of emerging standard JML
Stack inspection properties (security/access control)
If a variable x is being accessed, procedure A must be in
the call stack
Above requires matching of calls with returns, or
finding unmatched calls
Recall: Language of words over [, ] such that brackets are well
matched is not regular, but context-free
Caret for Context-free Specifications
Caret: Temporal Logic of Calls and Returns [AEM03]
Context-free extension of Pnueli’s Linear Temporal Logic LTL
Allows specification of pre/post conditions
Allows specification of stack inspection properties
Main result: Checking Caret specifications against a
context-free model is decidable
Polynomial in the size of the model and exponential in the
size of formula (as in case of classical model checking)
Proof technique: Product of pushdown model M and Caret
specification S is again a pushdown automaton
Key to success: The notion of calls and returns is the
same for M as well as S
Caret Definition
Interpreted over “structured” words in which positions
are marked with calls { and returns }
p’=Always(p or q)
p
q’
{q
{r
p
r
q’
q
{p
p
p}
r
q’
p’
p’
p’
q}
p
p
q’=Next(q)
Caret provides classical temporal operators such as Next
and Always
Caret Abstract Operators
Abstract versions of operators jump from a call to the
matching return
p’=abstract-always(p or q)
p’
p’
p
{q
q’
p’
{r
q’
p
r
q
{p
p’
p
q’
p’
p}
r
q’
Sample specification: pre/post:
Always( p & call -> abstract-next q )
p’
p’
p’
q}
p
p
q’=abstract-next(q)
Visibly Pushdown Languages [AM03]
Subclass of context-free languages that is suitable for program
analysis / algorithmic verification
Alphabet is structured: Symbols are tagged with calls and
returns
A visibly pushdown automaton’s moves are constrained by input
If current symbol is a call, it must push
If current symbol is a return it must pop
Else it can only update control state
Class of languages defined by these automata is very robust
Closed under union, intersection, complement, Kleene-*.
Emptiness, inclusion, equivalence decidable
Alternative characterizations: Embeddings of regular tree languages,
Monadic Second Order theory with a binary matching predicate
Caret is a subset of visibly pushdown languages
Synthesis of Behavioral Interfaces
Behavioral type of a class specifies the allowed sequences of
method calls
Type for a file class may be (open; (read+open)*;close)*
Can we synthesize this type automatically?
Given source code for the class implementation
Construct a regular language over the method calls so that a
particular exception is never raised
This is useful for compositional verification also: behavioral
interface is a suitable abstraction of the class
Proposed route (ongoing project)
Use abstraction to get a finite-state model
Solve a symbolic game to get the most general strategy for invoking
methods to keep the abstract model “safe”
Extract interface type from the game solution
AbstractList.ListItr
public Object next() {
…
lastRet = cursor++;
…}
public Object prev() {
…
lastRet = cursor;
…}
public void remove() {
if (lastRet==-1)
throw new IllegalExc();
…
lastRet = -1;
…}
public void add(Object o) {
…
lastRet = -1;
…}
Behavioral Interface
Start
next
add
next,prev
Safe
Unsafe
remove,add
add
next,prev
Game in Abstracted Program
next
prev
From black states,
Player0 gets to choose
the input method call
From purple states,
Player1 gets to choose
a path in the abstract
program till call returns
Objective for Player0: Ensure error states (from
which exception can be rasied) are avoided
Winning strategy: Correct method sequence calls
Challenges
Techniques for generating finite-state abstractions
How to solve large games symbolically?
In fact, a partial information game (Player0 should choose the next
method call only based on values returned so far)
How to construct an understandble behavioral type from the
winning strategy?
Abstraction refinement
If Player0 does not invoke any method, exceptions can never be
raised
How to refine the current abstraction based on quality of current
behavioral type?
Integrating all these into a working tool