260 likes | 277 Views
Bounded Model Checking of Concurrent Programs. Ishai Rabinovitz and Orna Grumberg CAV ‘05 Presented by Changki Hong. Software Model Checking. Main trends in formal verification SAT-based Bounded Model Checking (BMC) leading technique for model checking of hardware
E N D
Bounded Model Checking of Concurrent Programs Ishai Rabinovitz and Orna Grumberg CAV ‘05 Presented by Changki Hong
Software Model Checking • Main trends in formal verification • SAT-based Bounded Model Checking (BMC) • leading technique for model checking of hardware • Software verification using formal method • Traditional testing method often fails to find bugs in concurrent programs • How about adopting BMC technique for software? • Error traces are typically long large bound is needed • This results in a too large propositional formula handled by SAT solver • C-Bounded Model Checking (CBMC) • Handled problems stated above. • However, CBMC is only for verifying sequential programs TCBMC
CBMC in multi-threaded program thread 1 thread 2 thrd1_x1 = 0; thrd1_i1 = 0; if (thrd1_i1 < 3) { thrd1_x2 = thrd1_x1 + 1; thrd1_i2 = thrd1_i1 + 1; } if (thrd1_i2 < 3) { thrd1_x3 = thrd1_x2 + 1; thrd1_i3 = thrd1_i2 + 1; } if (thrd1_i3 < 3) { thrd1_x4 = thrd1_x3 + 1; thrd1_i4 = thrd1_i3 + 1; } thrd2_x1 = 0; thrd2_i1 = 0; if (thrd2_i1 < 3) { thrd2_x2 = thrd2_x1 + 1; thrd2_i2 = thrd2_i1 + 1; } if (thrd2_i2 < 3) { thrd2_x3 = thrd2_x2 + 1; thrd2_i3 = thrd2_i2 + 1; } if (thrd2_i3 < 3) { thrd2_x4 = thrd2_x3 + 1; thrd2_i4 = thrd2_i3 + 1; } int x; int i; thread1() { x=0; for (i=0; i<3; i++) x++; } thread2() { x=0; for (i=0; i<3; i++) x--; } • Can not extend CBMC’s approach to concurrent C programs • When there are assignments in two different threads to the same global variable, we can’t determine the order in which they will be executed.
Contents • TCBMC • Modeling Synchronization Primitives • Verifying Race Conditions and Deadlocks • Experimental Results • Conclusions
TCBMC • TCBMC (Threaded-C Bounded Model Checking) • TCBMC bounds the number of context switches • Claim : most bug patterns have only a few context switches. • TCBMC allows context switches to be anywhere in the code. • Different models are needed • We need to apply TCBMC for three times for detecting • regular bugs (assert) • data races • deadlocks
TCBMC Thread 0 Thread 1 thread 1 thread 2 thrd1_x1 = 0; thrd1_i1 = 0; if (thrd1_i1 < 3) { thrd1_x2 = thrd1_x1 + 1; thrd1_i2 = thrd1_i1 + 1; } if (thrd1_i2 < 3) { thrd1_x3 = thrd1_x2 + 1; thrd1_i3 = thrd1_i2 + 1; } if (thrd1_i3 < 3) { thrd1_x4 = thrd1_x3 + 1; thrd1_i4 = thrd1_i3 + 1; } thrd2_x1 = 0; thrd2_i1 = 0; if (thrd2_i1 < 3) { thrd2_x2 = thrd2_x1 + 1; thrd2_i2 = thrd2_i1 + 1; } if (thrd2_i2 < 3) { thrd2_x3 = thrd2_x2 + 1; thrd2_i3 = thrd2_i2 + 1; } if (thrd2_i3 < 3) { thrd2_x4 = thrd2_x3 + 1; thrd2_i4 = thrd2_i3 + 1; } CSB 0 Context switch block 0 CSB 1 Context switch block 1 CSB 2 Context switch block 2 CSB 3 Context switch block 3 CSB 4 CSB 5 Context switch block 5 Context switch block 4 • Context switch block • A set of consecutive lines of code executed with no interleaving context switch
Template variable threadt_cs • threadt_cs(l) • associate with each line l in the template a variable threadt_cs(l). • The value of threadt_cs(l) indicates the number of context switches that occurred before this line was executed. • threadt_cs(l) variables determine where the context switches occur. • The value of threadt_cs(l) will be decided by SAT solver using following constraints : • Monotonicity : 8l. threadt_cs(l) ·threadt_cs(l+1) • Interleaving bound : There is a bound on the number of context switches n. 8l. threadt_cs(l) ·n • Parity : For two threads, the value of threadt_cs(l) can be restricted to be even for t=0 and odd for t= 1. 8l. (threadt_cs(l) mod 2) = t • Extend the definition of threadt_cs(l) • threadt_cs(vj) = threadt_cs(lvj), wherelvj is the line in which vjis assigned.
Context switch block Thread 1 Thread 0 thread0_cs thread1_cs 0 0 0 2 2 2 2 2 2 2 2 2 2 4 4 4 4 4 1 1 1 1 1 1 1 3 3 3 3 5 5 5 5 5 5 5 Context switch block 0 Context switch block 1 Context switch block 2 Context switch block 3 Context switch block 5 Context switch block 4
The translation stages x1 = x2 + x3; x1 = x2 + x3; rax2; rbx3; rcra + rb; x1rc; y1=x2; y2=x3; x1=y1 + y2; • Translation process of TCBMC • Stage 1 - Preprocessing • A C statement which accesses more than one global variables is not always executed as an atomic statement. • Ex) Suppose each xiare global variables and ri are registers. • context switch may occur between these instructions • To allow such context switches in TCBMC, we need to break statements just as a complier does by introducing new temporary local variable.
The translation stages global variables : each xi local variables : each yi, zi • Stage 2 - Applying CBMC seperately on each thread. • generates a list of constraints for each thread applying CBMC • template : a list of constraints generated for each thread • template has four types of statements: • local variable local variable(s) • ex) y1=(guardr?z0:y0); • global variable local variable(s) • ex) x1=(guardr?z0:x0); • local variable global variable(s) • ex) y1=(guardr?x2:y0) • guard variable guard variable and global variable(s) • ex) guard1=guard0 && (x1 > y0)
Global variables • Stage 3 - Generating constraints for concurrency • Handling global variables using concept of context switch block • The bound n is the number of context switches • define n new variables x_vali for each global variable x (0· i<n) • x_vali is the value of variable x at the end of the i-th context switch block. • The value of x in context switch block i+1 is initialized to x_vali.
The translation stages thread1() { if(y%2 == 0) y = z; else ; } guard0 = (y0%2 == 0); y1 = (guard0? z0 : y0); thread1_guard0 = (thread1_y0%2 == 0); thread1_y1 = (thread1_guard0? thread1_z0 : thread1_y0); global variables : each xi local variables : each yi, zi • Stage 3 - Generating constraints for concurrency (cond.) • Translate each statement in the template (stage 2) into a constraints. 1. local variable local variable(s) - Simply add the thread prefix threadt. (to avoid name collision) - y1=(guardr?z0:y0); threadt_y1=(threadt_guardr?threadt_z0:threadt_y0);
The translation stages threadt_x1 = if (threadt_guardr)threadt_z0; else if (threadt_cs(x1) == threadt_cs(x0))threadt_x0 ; elsex_val; int x; thread1() { if(y%2 == 0) x = z; else ; } guard0 = (y0%2 == 0); x1 = (guard0? z0 : x0); (threadt_cs(x1)-1) thread1_guard0 = (thread1_y0%2 == 0); thread1_x1 = if (thread1_guard0) thread1_z0 ; else if (thread1_cs(x1) == thread1_cs(x0)) thread1_x0 ; else x_val ; 2. global variable local variable(s) - If the assignment to x0 is in the same context switch block as the assignment x1, the thread prefix can simply be added. - Otherwise, the x_val of the previous context switch block should be used for the value of x0 - x1=(guardr?z0:x0); (thread1_cs(x1)-1)
The translation stages threadt_y1 = if (threadt_guardr) if (threadt_cs(y1) == threadt_cs(x2))threadt_x2 ; elsex_val ; else threadt_y0; (threadt_cs(y1)-1) 3. local variable global variable(s) - If the assignment to y1 is in the same context switch block as the assignment x2, the thread prefix can simply be added. - Otherwise, the x_val of the previous context switch block should be used for the value of x2 - y1=(guardr?x2:y0) 4. guard variable guard variable and global variable(s) - ex) guard1=guard0 && (x1 > y0) - treat as third item.
Example thread1_x1 = 0; thread1_i1 = 0; thread1_guard1 = if (thread1_cs(thread1_guard1) == thread1_cs(thread1_i1)) (thread1_i1 < 3); else (i_val(thread1_cs(thread1_guard1)-1) < 3); thread1_x2 = if (thread1_guard1) if (thread1_cs(x2) == thread1_cs(x1)) thread1_x1 + 1; else x_val + 1; else if (thread1_cs(x2) == thread1_cs(x1)) thread1_x1; else x_val ; ... thread2_x1 = 0; thread2_i1 = 0; thread2_guard1 = if (thread2_cs(thread2_guard1) == thread2_cs(thread2_i1)) (thread2_i1 < 3); else (i_val(thread2_cs(thread2_guard1)-1) < 3); thread2_x2 = if (thread2_guard1) if (thread2_cs(x2) == thread2_cs(x1)) thread2_x1 - 1; else x_val - 1 ; else if (thread2_cs(x2) == thread2_cs(x1)) thread2_x1; else x_val ; ... int x; int i; thread1() { x=0; for (i=0; i<3; i++) x++; } thread2() { x=0; for (i=0; i<3; i++) x--; } (thread1_cs(x2)-1) (thread2_cs(x2)-1) + (thread1_cs(x2)-1) (thread2_cs(x2)-1) • Monotonicity :threadt_cs(l) ·threadt_cs(l+1) • Interleaving bound : threadt_cs(l) ·n • Parity : (threadt_cs(l) mod 2) = t
Contents • TCBMC • Modeling Synchronization Primitives • Verifying Race Conditions and Deadlocks • Experimental Results • Conclusions
Modeling synchronization primitives thread 1 add additional constraints: thread1_cs(1) = thread1_cs(2); thread1_cs(2) = thread1_cs(3); thread1_cs(3) = thread1_cs(4); • atomic { • x1 = y1; • x2 = y2; • } • Modeling atomic section • Do not allow context switch in the section marked with atomic. • just add constraints that force the threadt_cs values of the lines in an atomic section to be identical.
Modeling synchronization primitives lock (mutex) unlock (mutex) atomic { assume(mutex==U); mutex = L; } atomic { assert(mutex==L); mutex = U; } • Modeling mutex • A mutex has two states, L(locked) and U(unlocked) • Lock operation : Lock waits until the mutex is in state U and changes its state to L. • Unlock operation : Unlock is applied to a mutex in state L and changes its state to U.
Contents • TCBMC • Modeling Synchronization Primitives • Verifying Race Conditions and Deadlocks • Experimental Results • Conclusions
Verifying race conditions atomic { assert(x_write_flag==0); x_write_flag=1; x=3; } x_write_flag=0; assert(x_write_flag==0); y = x; Translation of y =x Translation of x=3 • Race condition • A race condition is a state in which the next instruction of different threads access the same memory location and at least one of them is a write. • Detecting races • add to each global variable x a new global bit variable x_write_flag. • x_write_flag is raised whenever x is assigned to some value and lowered in the next instruction. • on every access to x we assert that its x_write_flag is low.
Verifying Deadlocks • Deadlock • Global deadlocks • All the threads are waiting for a mutex • Local deadlocks • Some of threads form a waiting cycle • ex) thread1 is waiting for mutex ma which is held by thread2 which is waiting for mutex mb which is held by thread1 • Finding deadlocks • Model should be changed • Model of lock() function we learnt was implemented by assume() function • Global deadlock occurs when all threads are in a waiting state. • We must change the modeling of lock() function. • trds_in_wait : global counter for the number of threads in wait state. • When modeling lock(m), if mutex m is already in state L • increase trds_in_wait • assert that trds_in_wait < Total number of threads • If the assertion fails, a global deadlock was detected.
Modeling of lock for deadlock detection if(!dd) {//dd = deadlock_detected atomic { unlocked = (mutex == U); if(unlocked) mutex = L else trds_in_wait++; } atomic { if(!unlocked) { dd = (trds_in_wait == T); assert(!dd); } } } lock(mutex)
Contents • TCBMC • Modeling Synchronization Primitives • Verifying Race Conditions and Deadlocks • Experimental Results • Conclusions
Experimental results ** * *Windows operating system, Pentium4 1.8Ghz with 1GB memory **Linux operating system, Pentium4 2Ghz with 250MB memory • TCBMC v.s. Zing • Concurrent implementation of bubble sort • Bugs are dependent on both data and the interleaving. • Two threads. • TCBMC scales better with respect to integer widths • TCBMC performs better for detecting bugs dependent on both data and interleavings
Contents • TCBMC • Modeling Synchronization Primitives • Verifying Race Conditions and Deadlocks • Experimental Results • Conclusions
Conclusions TCBMC extended CBMC to verify concurrent C programs. TCBMC bounds the number of context switches among threads. Using TCBMC, we can detect safety properties, races and deadlocks which is crucial for concurrent programs.