Explicit State Model Checker  Represents the system as a finite state machine  Visits each reachable state (state space) explicitly Promela/SPIN (using Nested DFS)  Performs on-the-fly computation Acknowledgements:  Uses partial order reduction These notes used some of  Efficient memory usage the material presented by ªState compression Flavio Lerda as part of Ed ªBit-state hashing Clarke’s model-checking  Version 4: course ªUninterpreted C code can be used as part of Promela model

•3

SPIN High Level Organization  For checking correctness of process interactions ªSpecified using buffered channels, shared variables or combination LTL formula Promela Model

ªFocus: asynchronous control in software systems LTL Translator ªPromela – program-like notation for specifying design choices Buchi Automaton ¾Models are bounded and have countably many distinct behaviors Buchi Translator

 Promela Parser Generate a C program that performs an efficient The Buchi automaton is online verification of the system’s correctness turned into a Promela Abstract Syntax Tree process and composed properties Automata Automata with the rest of the system. Generator The generated verifier is  Types of properties: C Generatorspecific to the model and property we started with. ªDeadlock, violated assertions, unreachable code C Code ªSystem invariants, general LTL properties C Pan Verifier  Random simulations of the system’s execution Verification Result  “Proof approximation” •2 •4

1 2 Promela (Process Meta Language) Process Instantiation  Need to execute processes  Asynchronous composition of independent processes ªproctype only defines them  How to do it?  Communication using channels and global ªBy default, process of type always executes variables init ªrun starts processes  Non-deterministic choices and interleavings ªAlternatively, define them as active (see later)

 Based on Dijkstra’s guarded command language  Processes can receive parameters ªEvery statement guarded by a condition and blocks until condition ª all basic data types and message channels. becomes true ªData arrays and process types are not allowed. Example: Example: while (a != b) proctype A (byte state; short foo) skip /* wait for a == b */ { (state == 1) -> state = foo } vs init (a == b) { run A(1, 3) } •5 •7

Process Types Example  State of variable or message channel can only be  As mentioned earlier, no distinction between a changed or inspected by processes (defined statement and condition. using proctype) bool a, b;  ; and -> are statement separators with same proctype p1() semantics. { a = true; ª-> used informally to indicate causal relation between statements a & b; These statements are enabled only if both a and b are true. Example: a = false; byte state = 2; } In this case b is always false proctype A() proctype p2() and therefore there is a { (state == 1) -> state = 3 { . } b = false; proctype B() a & b; { state = state -1 b = true; } } init { a = false; b = false; run p1(); run p2(); }  state here is a global variable •6 •8

3 4 An Example Other constructs mtype = { NONCRITICAL, TRYING, CRITICAL }; At most one mtype can be  Do loops mtype state[2]; declared proctype process(int id) { beginning:  Communication over channels noncritical: state[id] = NONCRITICAL; proctype sender(chan out) if { :: goto noncritical; NC :: true; int x; fi; trying: if state[id] = TRYING; T ::x=0; if :: goto trying; ::x=1; :: true; fi fi; C critical: out ! x; state[id] = CRITICAL; if } :: goto critical; :: true; fi; goto beginning;} init { run process(0); run process(1) } •9 •11

Other constructs Other constructs  Do loops  Do loops

do  Communication over channels :: count = count + 1; :: count = count - 1;  Assertions :: (count == 0) -> break od proctype receiver(chan in) { int value; in ? value; assert(value == 0 || value == 1) }

•10 •12

5 6 Other constructs Message Passing Example proctype A(chan q1) Â Do loops { chan q2; Â Communication over channels q1?q2; q2!123 Â Assertions } proctype B(chan qforb) Â Atomic Steps { int x; qforb?x; int value; proctype increment() print(“x=%d\n”, x) { atomic } { x = value; init { x = x + 1; chan qname = [1] of {chan }; value = x; chan qforb = [1] of {int }; } run A(gname); } run B(qforb); qname!qforb } Prints: 123 •13 •15

Message Passing Randez-vous Communications chan qname = [16] of {short} Â Buffer of size 0 – can pass but not store qname!expr – writing (appending) to channel messages qname?expr – reading (from head) of the channel ªMessage interactions by definition synchronous – “peaking” (without removing content) qname??expr Example: qname!!expr – checking if there is room to write #define msgtype 33 can declare channel for exclusive read or write: chan name = [0] of { byte, byte }; chan in, out; xr in; xs out; proctype A() { name!msgtype(123); qname!exp1, exp2, exp3 – writing several vars name!msgtype(121); /* non-executable */ qname!expr1(expr2, expr3) – type and params } qname?vari(var2, var3) proctype B() { byte state; qname?cons1, var2, cons2 – can send constants name?msgtype(state) ªLess parameters sent than received – others are undefined } ªMore parameters sent – remaining values are lost init ªConstants sent must match with constants received { atomic { run A(); run B() } •14 } •16

7 8 Randez-Vous Communications (Cont’d) Example Cont’d  If channel name has zero buffer capacity: mtype = {ack, nak, err, next, accept}; ªHandshake on message msgtype and transfer of value 123 to proctype transfer (chan in, out, chin, chout) variable state. { byte o, I; ªThe second statement will not be executable since no matching in?next(o); receive operation in B do  If channel name has size 1: :: chin?nak(I) -> ªProcess A can complete its first send but blocks on the second out!accept(I); since channel is filled. chout!ack(o) ªB can retrieve this message and complete. :: chin?ack(I) -> ªThen A completes, leaving the last message in the channel out!accept(I); in?next(o);  If channel name has size 2 or more: chout!ack(o) ªA can finish its execution before B even starts :: chin?err(I) -> chout!nak(o) od •17 } •19

Example – protocol Example (Cont’d) Â Channels Ain and Bin init ªto be filled with token messages of type next and arbitrary values { chan AtoB = [1] of { mtype, byte }; (ASCII chars)… chan BtoA = [1] of { mtype, byte }; ª by unspecified background processes: the users of the transfer service chan Ain = [2] of { mtype, byte }; chan Bin = [2] of { mtype, byte }; Â These users can also read received data from the channels Aout and Bout chan Aout = [2] of { mtype, byte }; Â The channels are initialized in a single atomic chan Bout = [2] of { mtype, byte }; statement… ªAnd started with the dummy err message. atomic { run transfer (Ain, Aout, AtoB, BtoA); run transfer (Bin, Bout, BtoA, AtoB); } Ain!next(0); •18 AtoB!err(0) •20

9 10 Mutual Exclusion in SPIN Â Peterson’s solution to the mutual exclusion problem _pid: bool turn, flag[2]; Identifier of the process

active [2] proctype user() assert: Checks that there are only { at most two instances with assert(_pid == 0 || __pid == 1); identifiers 0 and 1 flag =1 0 again: flag[_pid] = 1; turn = 1 - _pid; turn=0 flag0=0 (flag[1 - _pid] == 0 || turn == _pid); flag1 == 0 || turn == 1 /* */ flag1 != 0 && turn != 1

Critical flag[_pid] = 0; Section goto again; } •21 •23

Mutual Exclusion in SPIN Mutual Exclusion in SPIN bool turn, flag[2]; byte ncrit; ncrit: bool turn; Counts the number of active [2] proctype user() processes in the critical section bool flag[2]; { proctype mutex0() { assert(_pid == 0 || __pid == 1); again: again: flag[_pid] = 1; flag[0] = 1; flag0=1 turn = 1; turn = 1 - _pid; (flag[1 - _pid] == 0 || turn == _pid); (flag[1] == 0 || turn == 0); turn=1 flag0=0 /* critical section */ flag == 0 || turn == 0 ncrit++; flag[0] = 0; 1 assert(ncrit == 1); /* critical sectionassert: */ flag != 0 && turn != 0 goto again; 1 ncrit--; Checks that there is always } at most one process in the Critical critical section flag[_pid] = 0; Section goto again; •22 •24 }

11 12 Verification Mutual Exclusion in SPIN bool turn, flag[2]; Â Generate, compile and run the verifier byte ncrit;

ª to check for and other major problems: active [2] proctype user() { $ spin –a mutex assert(_pid == 0 || __pid == 1); $ cc –O pan pan.c again: flag[_pid] = 1; $ pan turn = 1 - _pid; full statespace search for: (flag[1 - _pid] == 0 || turn ==_pid); assertion violations and invalid endstates vector 20 bytes, depth reached 19, errors: 0 ncrit++; 79 states, stored /* critical section */ 0 states, linked ncrit--; 38 states, matched total: 117 hash conflicts: 4 (resolved) flag[_pid] = 0; (size s^18 states, stack frames: 3/0) goto again; } unreached code _init (proc 0); reached all 3 states active proctype monitor() unreached code P (proc 1): { assert (ncrit == 0 || ncrit == 1) } reached all 12 states •25 •27

Mutual Exclusion Finally,  Verifier: Assertion can be violated  Can specify an LTL formula and run the model- ªCan use -t -p to find out the trace checker ¾Or use XSpin Example: #define p count <= 1  Another way of catching the error ªLTL claim: ªHave another monitor process ran in parallel [] p ªAllows all possible relative timings of the processes  Note: all variables in LTL claims have to be global! ªElegant way to check validity of system invariant  LTL claim gets translated into NEVER claim and stored either in .ltl file or at the end of model file ªOnly one LTL property can be verified at a time

 Parameters can be set using XSpin ªDepth of search, available memory, etc.

•26 •28

13 14 Mutual Exclusion in SPIN Command Line Tools bool turn, flag[2]; Â Spin bool critical[2]; LTL Properties: ª Generates the Promela code for the LTL formula $ active [2] proctype user() [] (critial[0] || critical[1]) spin –f “[]<>p” { ¾ The proposition in the formula must correspond to #defines assert(_pid == 0 || __pid == 1); [] <> (critical[0]) ª Generates the C source code again: [] <> (critical[1]) $ spin –a source.pro flag[_pid] = 1; ¾ The property must be included in the source turn = 1 - _pid; [] (critical[0] -> (critial[0] U (flag[1 - _pid] == 0 ||turn == _pid); Â Pan (!critical[0] && ((!critical[0] && ª Performs the verification critical[_pid] = 1; !critical[1]) U critical[1])))) ¾ Has many compile time options to enable different features /* critical section */ ¾ Optimized for performance critical[_pid] = 0; [] (critical[1] -> (critial[1] U flag[_pid] = 0; (!critical[1] && goto again; ((!critical[1] && } !critical[0]) U critical[0])))) Note: critical[ ] is a global var! •29 •31

Alternatively, Xspin #define p ncrit <= 1 #define q ncrit = 0 LTL Properties: bool turn, flag[2]; Â GUI for Spin byte ncrit; [] (p) []<> (!q) active [2] proctype user() { assert(_pid == 0 || __pid == 1); again: flag[_pid] = 1; turn = 1 - _pid; (flag[1 - _pid] == 0 || turn == _pid);

ncrit++; /* critical section */ ncrit--;

flag[_pid] = 0; goto again; } •30 •32

15 16 Simulator Alternating Bit Protocol  Spin can also be used as a simulator  Two processes want to communicate ªSimulated the Promela program  They want acknowledgement of received  It is used as a simulator when a counterexample is messages generated  Sending window of one message ªSteps through the trace  Each message is identified by one bit ªThe trace itself is not “readable”  Alternating values of the identifier  Can be used for random and manually guided simulation as well

•33 •35

Alternating Bit Protocol

A few examples Sender Receiver

msg0

ack0

msg1 ÂAlternating Bit Protocol ack1

ÂLeader Election msg0

ack0

msg1

•36

17 18 Alternating Bit Protocol Sender Process active proctype Sender() { :: if do Sender Receiver :: receiver!msg1; :: :: skip msg0 if fi; :: receiver!msg0; do ack1 :: skip :: sender?ack1 -> break msg0 fi; :: sender?ack0 do :: timeout -> ack0 :: sender?ack0 -> break if :: sender?ack1 :: receiver!msg1; :: skip :: timeout -> fi; if od; :: receiver!msg0; od; :: skip } fi; •37 od; •39

Alternating Bit Protocol Receiver Process active proctype Receiver() mtype = { msg0, msg1, ack0, ack1 } { chan sender = [1] of { mtype }; do chan receiver = [1] of { mtype }; Sender Receiver :: do msg0 :: receiver?msg0 -> sender!ack0; break; msg0 :: receiver?msg1 -> server!ack1 ack0 od

msg1 do ack1 :: receiver?msg1 -> sender!ack1; break; :: receiver?msg0 -> server!ack0 od od } •38 •40

19 20 Leader Election Verification of Leader Election 21 if  Elect leader in unidirectional ring. 22 :: Active -> ªAll processes participate in election 23 if ªCannot join after the execution started 24 :: nr != maximum -> out!two(nr); neighbour = nr; 25 :: else ->  Global property: 26 /* max is the greatest number */ ªIt should not be possible for more than one process to declare to be 27 assert (nr == N); the leader of the ring 28 know_winner = 1; 29 out!winner(nr); LTL: [] (nr_leaders <= 1) Use assertion (line 57) 30 fi assert (nr_leaders == 1) 33 :: else -> this is much more efficient! 34 out!one(nr) ªEventually a leader is elected 35 fi 36 ¾ <> [] (nr_leaders == 1) 37 :: in?two(nr) -> 38 if 39 :: Active -> 40 if

•41 •43

Verification of Leader Election Verification of Leader Election 1 #define N 5 /* nr of processes */ 41 :: neighbour > nr && neighbour > maximum 2 #define I 3 /* node given the smallest number */ 42 maximum = neighbour; 3 #define L 10 /* size of buffer (>= 2*N) */ 43 out!one(neighbour) 4 44 :: else -> 5 mtype = {one, two, winner}; /* symb. message names */ 45 Active = 0 6 chan q[N] = [L] of {mtype, byte} /* asynch channel */ 46 fi 7 47 :: else -> out!two (nr) 8 byte nr_leaders = 0; /* count the number of processes 48 fi 9 that think they are leader of the ring */ 49 :: in?winner(nr) -> 10 proctype node (chan in, out; byte mynumber) 50 if 11 { bit Active = 1, know_winner = 0; 51 :: nr != mynumber -> printf (“LOST\n”); 52 :: else -> 12 byte nr, maximum = mynumber, neighbour; 53 printf (“Leader \n”); 13 54 nr_leaders++; 14 xr in; /* claim exclusive recv access to in */ 55 assert(nr_leaders == 1); 15 xs out; /* claims exclusive send access to out */ 56 fi 16 57 if 17 printf (“MSC: %d\n”, mynumber); 58 :: know_winner 18 out!one(mynumber) /* send msg of type one */ 59 :: else -> 19 one: do 60 out!winner(nr) 20 :: in?one(nr) -> /* receive msg of type one */ •42 •44

21 22 Verification of Leader Election Comments 62 fi; 63 break  DFS does not necessarily find the shortest 64 od counterexample 65 } ªThere might be a very short counterexample but the verification 66 67 init { might go out of memory 68 byte proc; ªIf we don’t finish, we might still have some sort of a result 69 atomic { /* activate N copies of proc template */ (coverage metrics) 70 proc = 1; 71 do 72 :: proc <= N -> 73 run node (q[proc-1], q[proc%N], 74 (N+I-proc)% N+1); 75 proc++ 76 :: proc > N -> break 77 od 78 } 79 }

•45 •47

Summary On-The-Fly  Distinction between behavior and requirements on  System is the asynchronous composition of behavior processes ªWhich are checked for their internal and mutual consistency  The global transition relation is never build  After verification, can refine decisions towards a  For each state the successor states are full system implementation enumerated using the transition relation of each ª Promela is not a full programming language process

 Can simulate the design before verification starts

•46 •48

23 24 Visited Set State Representation  Hash table  Global variables ªEfficient for testing even if the number of elements in it is very big  Processes and local variables (≥ 106)  Queues  Reduce memory usage ªCompress each state

 Reduce the number of states ªPartial Order Reduction When a transition is executed only a limited part of the state is modified

Global Variables Processes Queues

•49 •51

SPIN and Bit-state Hashing Compression  Command line:  Each transition changes only a small part of the ªcc –DBITSTATE –o run pan.c state  Can specify amount of available (non-virtual)  Assign a code to each element dynamically memory directly…  Encoded states + basic elements use considerably ªusing –w N option, e.g., -w 7 means 128 Mb of memory less spaces than the uncompressed states $ run assertion violated … pan aborted … hash factor: 67650.064516 (size 2^22 states, stack frames: 0/5)  Hash factor: ª max number of states / actual number ªMaximum number is 222 or about 32 million ªHash factor > 100 – coverage around 100% ªHash factor = 1 – coverage approaches 0% •50 •52

25 26 Compression Hash Compaction  Uses a hashing function to store each state using only 2 bits P0 P0 Q0 P0 P1 i=0 j=0  There is a non-zero probability that two states are x=0 x=1 {1} x=0 y=0 0 0 1 0 0 2 mapped into the same bits  If the number of states is much smaller than the number of bits available there is a pretty good chance of not having conflicts 3 3 3  The result is not (always) 100% correct! P1 2 2 2 y=0

P0 1 1 1 x=1

P0 Q0 0 i=0 j=0 0 0 x=0 {1} •53 •55

Compression Minimized Automata Reduction  Turns the state into a sequence of integers  Constructs an automaton which accepts the states P0 P0 Q0 P0 P1 i=0 j=0 in the visited set x=0 x=1 {1}{} x=0x=1 y=0 0 0 1 01 01 2 q ? x  Works like a BDD but on non-binary variables (MDD) ªThe variables are the components of the state 3 3 3 ªThe automaton is minimal ªThe automaton is updated efficiently P1 2 2 2 y=0

P0 Q0 1 1 1 x=1 {}

P0 Q0 0 i=0 j=0 0 0 x=0 {1} •54 •56

27 28 Partial Order Reduction  Optimal partial order reduction is as difficult as model checking!  Compute an approximation based on syntactical information Access to local variables ªIndependent Receive on exclusive receive-access queues ªInvisible Send on exclusive send-access queues ªCheck (at run-time) for actions postponed at infinitum

Not mentioned in the property

So called stack proviso

•57

References  http://spinroot.com/  Design and Validation of Computer Protocols by Gerard Holzmann  The Spin Model Checker by Gerard Holzmann  An automata-theoretic approach to automatic program verification, by Moshe Y. Vardi, and Pierre Wolper  An analysis of bitstate hashing, by G.J. Holzmann  An Improvement in Formal Verification, by G.J. Holzmann and D. Peled  Simple on-the-fly automatic verification of linear temporal logic, by Rob Gerth, Doron Peled, Moshe Vardi, and Pierre Wolper  A Minimized automaton representation of reachable states, by A. Puri and G.J. Holzmann

•58

29