Download presentation
Presentation is loading. Please wait.
Published byGilbert Burke Modified over 6 years ago
2
ESP: Program Verification Of Millions of Lines of Code
Manuvir Das Researcher PPRC Reliability Team Microsoft Research
3
Motivation No Buffer Overruns! No Resource Leaks! No Privilege Misuse!
Don't like this box, want new packaging Many OSs have these problems We know Windows doesn't Would like to verify the code and prove it so we can put rubber stamps on the box Not done yet, would be on a beach We think we've invented the technology that will get us there
4
Approach Redundency is good Redundancy exposes inconsistency
Inconsistency points to errors Compare what programmer should do what her code actually does
5
Lightweight specifications
Rules Describe correct behavior Readable/writable by programmers Specify limited properties not total correctness/verification Compare rules against code
6
Types are rules Programmers use types to
document interface syntax represent program abstractions Types are written, read and checked routine part of development process Why are types successful? types are lightweight specifications type checking is fast & routine errors are found early, at compile-time
7
Can we extend this approach?
Specify and check other properties languages to express rules tools to check that code obeys rules Goal is partial correctness detect and report important classes of errors no guarantee of program correctness Systematic tools of various flavors compile-time verifiers and bug-finders run-time monitors and fault injectors document generators
8
Program Analysis Engine
Rule-based programming Rules Development Testing Static Verification Tool Read for understanding Drive testing tools Precise Rules New API rules 100% path coverage Defects elapsed time: 2 Program Analysis Engine Source Code
9
Path-sensitive Dataflow Analysis
ESP Rules ESP OPAL Rules 100% path coverage Defects elapsed time: 2 Path-sensitive Dataflow Analysis C/C++ Code
10
Requirements Scalability Usability Complete coverage
Millions of lines of code All features of C/C++ Usability Low number of false positives Simple rule description language Informative error reports
11
The bottom line Can ESP verify a million lines of code?
We’re not sure …. yet We’ve done 150 KLOC in 70s and 50MB So, we’re cautiously optimistic Segway: Verification has been around a long while More than a few lines of code is difficult How can we even contemplate large programs?
12
Are we running into a wall?
Verification demands precision Need to minimize false error reports Must analyze each execution path Big programs demand scalability Exponentially/infinitely many paths Cannot analyze each execution path Must use approximate analysis PREfix/Metal Drop some paths on the floor Can't do this if we want to rubber stamp We're up against a well known problem in program analysis
13
Research problem Can we invent a verification method that
is always conservative, is always scalable, is almost always precise, and matches our intuition? Yes, for a certain class of rules Finite state, temporal safety properties This is my last marketing slide Time to roll up our sleeves and look at some good old fashioned C code
14
Finite state safety properties
Property is described by an FSA As the program executes, a monitor tracks the current state of the FSA updates the current state signals an error when the FSA transitions into special error states Goal of verification: Is there some execution path that would cause the monitor to signal an error?
15
Example: stdio usage in gcc
void main () { if (dump) fil = fopen(dumpFile,”w”); if (p) x = 0; else x = 1; fclose(fil); } void main () { if (dump) Open; if (p) x = 0; else x = 1; Close; } Closed Opened Error Open Print Close Print/Close * Blue code shows interactions with stdio library Let's call these events, and name them There are rules we must follow Can only close a file if we've opened it, and we haven't already closed it Partial program verification Specify these rules using a state machine Verify code against these rules Program execution drives the FSM Transitions to $error represent violations
16
Path-sensitive property analysis
Symbolically evaluate the program Track FSA state and execution state At branch points: Execution state implies branch direction? Yes: process appropriate branch No: split state and process both branches Can use static analysis to verify the code One option it to perform a very precise path-by-path analysis
17
Example T F entry dump p x = 0 x = 1 Open Close exit [Closed]
[Closed|dump=T] [Opened|dump=T] [Opened|dump=T,p=T] [Opened|dump=T,p=F] [Opened|dump=T,p=T,x=0] [Opened|dump=T,p=F,x=1] Shows the state just before the program point No errors 4 different states after 2nd branch So downstream code is analyzed 4 times [Opened|dump=T,p=T,x=0] [Opened|dump=T,p=F,x=1] [Closed|dump=T,p=T,x=0] [Closed|dump=T,p=F,x=1]
18
Dataflow property analysis
Track only FSA state Ignore non-state-changing code At control flow join points: Accumulate FSA states This is like a standard compiler dataflow analysis, but the only data we're tracking is the FSM state
19
Example T F entry dump p x = 0 x = 1 Open Close exit {Closed}
{Closed,Opened} {Closed,Opened} This analysis is imprecise In this case, the imprecision matters, because it causes a false error report Very efficient, unlike regular dataflow So these are the two previously known endpoints on the curve How do we find the right point in between? {Error,Closed,Opened}
20
Why is this code correct?
void main () { if (dump) Open; if (p) x = 0; else x = 1; Close; } Closed Opened Error Open Print Close Print/Close * Must be precise. So, must understand correct code Why is this code correct? Simple answer: Open and Close execute under the same conditions Examine more closely: at end of code, must close file, but only if FSM is in Opened state Type system of language does not allow her to write this So, relies on correlation between dump & FSM Analysis must track correlation after 1st branch No correlation with p
21
When is a branch relevant?
Precise answer When the value of the branch condition determines the property FSA state Heuristic answer When the property FSA is driven to different states along the arms of the branch statement Precise answer is undecidable Using this heuristic, we have designed an algorithm called property simulation
22
Property simulation Modification of path-sensitive analysis
At control flow join points: States agree on property FSA state? Yes: merge states No: process states separately
23
Example T F entry dump p x = 0 x = 1 Open Close exit [Closed]
[Opened|dump=T] [Closed|dump=F] At every point, there is a map from FSM state to simulation state. Hence the name. 2nd branch causes temporary blowup. Then effect is merged away so it doesn't come into play downstream. Think of downstream code being analyzed in 2 configurations rather than 4 states Much more in the paper Great, so now we can go implement this and start rubber stamping, right? [Opened|dump=T,p=T,x=0] [Opened|dump=T,p=F,x=1] [Opened|dump=T] [Closed|dump=F] [Closed|dump=T] [Closed|dump=F] [Closed|dump=T] [Closed]
24
Loop example T T F F entry [Closed] new = old [Closed|new=old+1] Open
[Opened|new=old] * T T Close F new++ [Closed|new=old+1] new != old [Opened|new=old] F Close exit [Closed|new=old]
25
Making property simulation work
Real programs are complex Multiple FSAs Aliasing Real code bases are very large Well beyond a million lines ESP = Property Simulation + Multiple FSAs + Aliasing + Component-wise Analysis
26
Problem: Multiple FSAs
void main () { if (dump1) Open(fil1); if (dump2) Open(fil2); Close(fil1); Close(fil2); } Transition Source code pattern Close fclose(e) Open e = fopen(_) void main () { if (dump1) fil1 = fopen(dumpFile1,”w”); if (dump2) fil2 = fopen(dumpFile2,”w”); fclose(fil1); fclose(fil2); } void main () { if (dump1) fil1 = fopen(dumpFile1,”w”); if (dump2) fil2 = fopen(dumpFile2,”w”); fclose(fil1); fclose(fil2); } Transition Source code pattern Close fclose(e) Open e = fopen(_) Closed Opened Error Open Print Close Print/Close * Wrong - real code is complex More than one file handle, so multiple FSMs Let's steal a very cool idea from Engler Use syntactic patterns to identify FSMs e is the file handle Events are parameterized Track as before Oops, vector of states now, so exponential property state. Not good.
27
Property simulation, bit by bit
Problem: property state can be exponential Solution: track one FSA at a time void main () { if (dump1) Open; if (dump2) ID; Close; } void main () { if (dump1) ID; if (dump2) Open; Close; } Let's steal an idea from optimizing compilers: bit vector analysis Analyze one FSM at a time through the code Only events for that FSm remain
28
Property simulation, bit by bit
One FSA at a time + Avoids exponential property state + Fewer branches are relevant + Lifetimes are often short + Smaller memory footprint + Embarassingly parallel − Cannot correlate FSAs Amplifies effect of merge heuristic Lifetime - reason for use in compilers Cannot express "A is opened and B is closed" So, we beat that problem, we're done
29
Problem: Aliasing void main () { if (dump1)
fil1 = fopen(dumpFile1,”w”); if (dump2) fil2 = fopen(dumpFile2,”w”); fil3 = fil1; fclose( fil3 ); fclose( fil2 ); } Not quite! Same code, assign from fil1 to fil3 Open/close have syntactically different params Will lead to false errors and/or missed errors Need to look beyond syntax, at semantics ESP semantic model Track one value at a time When we come to an event parameterized by a name, we need to decide if there is a state change on the value we're tracking
30
ESP Model: Values Have State
During execution, the program creates stateful values changes the state of stateful values The programmer defines how values are created (syntactic patterns) how values change state (syntactic patterns) Syntactic expressions are aliases for values
31
OPAL Rule Descriptions
Object Property Automata Language State Closed State Opened State Error Initial Event Open { _object_ ASTFUNCTIONCALL { ASTSYMBOL “fopen” } { _anyargs_ } } Event Close { ASTFUNCTIONCALL { ASTSYMBOL “fclose” } { _object_ } } Transition _ -> Opened on Open Transition Opened -> Closed on Close Transition Closed -> Error on Close “File already closed”
32
Parameterized transitions
void main () { if (dump1) fil1 = fopen(dumpFile1,”w”); if (dump2) fil2 = fopen(dumpFile2,”w”); fil3 = fil1; fclose( fil3 ); fclose( fil2 ); } Not quite! Same code, assign from fil1 to fil3 Open/close have syntactically different params Will lead to false errors and/or missed errors Need to look beyond syntax, at semantics ESP semantic model Track one value at a time When we come to an event parameterized by a name, we need to decide if there is a state change on the value we're tracking
33
Parameterized transitions
void main () { if (dump1) { t1 = fopen(dumpFile1,”w”); Open(t1); fil1 = t1; } if (dump2) { t2 = fopen(dumpFile2,”w”); Open(t2); fil2 = t2; fil3 = fil1; fclose( fil3 ); Close(fil3); fclose( fil2 ); Close(fil2); How? Time to steal another idea. This time, from ourselves So, we've invented some ideas, stolen others, now we can put it all together
34
Expressions are value aliases
void main () { if (dump1) { t1 = fopen(dumpFile1,”w”); Open(t1); fil1 = t1; } if (dump2) { t2 = fopen(dumpFile2,”w”); Open(t2); fil2 = t2; fil3 = fil1; fclose( fil3 ); Close(fil3); fclose( fil2 ); Close(fil2); Not quite! Same code, assign from fil1 to fil3 Open/close have syntactically different params Will lead to false errors and/or missed errors Need to look beyond syntax, at semantics ESP semantic model Track one value at a time When we come to an event parameterized by a name, we need to decide if there is a state change on the value we're tracking
35
Value-alias analysis Is expression e an alias for value v?
ESP uses GOLF to answer this query Generalized One Level Flow Context-sensitive Largely flow-insensitive Millions of lines of code, in seconds How? Time to steal another idea. This time, from ourselves So, we've invented some ideas, stolen others, now we can put it all together
36
Putting it all together
Property simulation Identify and track relevant execution state Syntactic patterns + value-alias analysis Identify and isolate individual FSAs One FSA at a time Bit vector analysis for safety properties These ideas form the basis of ESP, and distinguish it from other projects The implemented system is much more complex
37
Case study: stdio usage in gcc
cc1 from gcc version (Spec95) Does cc1 always print to opened files? cc1 is a complex program: 140K non-blank, non-comment lines of C 2149 functions, 66 files, 1086 globals Call graph includes one 450 function SCC What have we done with ESP? cc1 is the gcc from-end (parser etc) Prints output to many files depending on what the user wants Anyone who has written a recursive descent parser should understand where the big SCC comes from
38
Skeleton of cc1 source FILE *f1, … , *f15; int p1, … , p15;
void compileFile() { if (p1) f1 = fopen(…); … if (p15) f15 = fopen(…); restOfComp(); fclose(f1); fclose(f15); } void restOfComp() { if (p1) printRtl(f1); … if (p15) printRtl(f15); restOfComp(); } void printRtl(FILE *f) fprintf(f); For each compilation unit Conditionally open 15 files Compile code Conditionally close files Path-sensitive analysis would lead to 215 states In ESP, only one branch is relevant for each file handle Code is processed in two configs Path-sensitivity + context-sensitive value flow are needed
39
OPAL rules for stdio usage
State Uninit State Closed State Opened State Error Initial Event Decl {ASTDECLARATION {_object_ ASTSYMBOL _any_}} Initial Event Open {_object_ ASTFUNCTIONCALL {ASTSYMBOL “fopen”} {_anyargs_}} Event Print {ASTFUNCTIONCALL {ASTSYMBOL “fprintf”} {_object_,_anyargs_}} Event Close {ASTFUNCTIONCALL {ASTSYMBOL “fclose”} {_object_}} Transition _ -> Uninit on Decl Transition _ -> Opened on Open Transition Uninit -> Error on Print “File not opened” Transition Opened -> Opened on Print Transition Closed -> Error on Print “Printing to closed file” Transition Opened -> Closed on Close Transition Closed -> Error on Close “File already closed”
40
Experimental results Precision Scalability We have proved that:
Verification succeeds for every file handle No transitions to Error; no false errors Scalability Ave. per handle: 72.9 seconds, 49.7 MB Single 1GHz PIII laptop with 512 MB RAM We have proved that: Each of the 646 calls to fprintf in the source code prints to a valid, open file We've run on parts of the Windows code base. Of course, there are no bugs to find. Instead, we're using the code in Windows as a test suite, to find bugs in our own implementation. Which is backwards, isn't it?
41
Ongoing research Path-sensitive value-alias analysis
Value-alias sets Expressions that hold tracked value Track value-alias sets during simulation Add value-alias sets to property state When things get complicated, use GOLF Component-wise analysis Identify and analyze components Link using less precise analysis
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.