├── .gitignore ├── README ├── bad.py ├── ffs_eqv.py ├── mc.py ├── mc_util.py ├── mod_eqv.py └── test_me.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This directory contains a "minimal" implementation to demonstrate 2 | the basic ideas of symbolic execution and concolic execution, using 3 | Z3's Python interface. You should read the KLEE and the SAGE papers 4 | listed on the CSE 551 webiste. 5 | 6 | * Install Z3/Python first: https://github.com/Z3Prover/z3 7 | 8 | * (optional) Install Pygments: pip install Pygments 9 | 10 | * Symbolic execution: run test_me.py. 11 | 12 | * Check the implementation: sched_fork() in mc.py. 13 | 14 | * Concolic execution: comment out the "test_me(x, y)" line, 15 | uncomment the next "mc_fuzz(...)" line, and run test_me.py. 16 | 17 | * Check the implementation: mc_fuzz() and sched_flip() in mc.py. 18 | 19 | * Try one more example: run bad.py. 20 | 21 | * Run equivalence checking examples: ffs_eqv.py and mod_eqv.py. 22 | 23 | Thanks to James Bornholt for the feedback. 24 | -------------------------------------------------------------------------------- /bad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2015 Xi Wang 4 | # 5 | # This file is part of the UW CSE 551 lecture code. It is freely 6 | # distributed under the MIT License. 7 | 8 | """ 9 | Resembles Figure 1 of the SAGE paper (NDSS'08). 10 | Will genereate five inputs. 11 | """ 12 | 13 | from mc import * 14 | 15 | def top(s): 16 | cnt = 0 ; 17 | if s[0] == ord('b'): 18 | cnt = cnt + 1 19 | if s[1] == ord('a'): 20 | cnt = cnt + 1 21 | if s[2] == ord('d'): 22 | cnt = cnt + 1 23 | if s[3] == ord('!'): 24 | cnt = cnt + 1 25 | if cnt >= 3: 26 | assert False 27 | 28 | n = 4 29 | names = " ".join(["s[%s]" % (i,) for i in range(n)]) 30 | s = BitVecs(names, 8) 31 | #top(s) 32 | mc_fuzz(lambda: top(s), s, [0] * n) 33 | -------------------------------------------------------------------------------- /ffs_eqv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2015 Xi Wang 4 | # 5 | # This file is part of the UW CSE 551 lecture code. It is freely 6 | # distributed under the MIT License. 7 | 8 | """ 9 | This resembles Figure 4 of the UC-KLEE paper (CAV'11). 10 | """ 11 | 12 | from mc import * 13 | 14 | def ffs_newlib(x): 15 | if x == 0: 16 | return 0 17 | i = 0 18 | while True: 19 | t = (1 << i) & x 20 | i = i + 1 21 | if t != 0: 22 | return i 23 | 24 | def ffs_uclibc(i): 25 | n = 1 26 | if (i & 0xffff) == 0: 27 | n = n + 16 28 | i = i >> 16 29 | if (i & 0xff) == 0: 30 | n = n + 8 31 | i = i >> 8 32 | if (i & 0x0f) == 0: 33 | n = n + 4 34 | i = i >> 4 35 | if (i & 0x03) == 0: 36 | n = n + 2 37 | i = i >> 2 38 | if i != 0: 39 | return n + ((i + 1) & 0x01) 40 | return 0 41 | 42 | x = BitVec("x", 32) 43 | assert ffs_newlib(x) == ffs_uclibc(x) 44 | -------------------------------------------------------------------------------- /mc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) 2015 Xi Wang 4 | # 5 | # This file is part of the UW CSE 551 lecture code. It is freely 6 | # distributed under the MIT License. 7 | 8 | # --------------------------------------------------------------- 9 | # symbolic 10 | # --------------------------------------------------------------- 11 | 12 | from mc_util import * 13 | 14 | def sched_fork(self): 15 | pid = os.fork() 16 | if pid: 17 | solver.add(self) 18 | r = True 19 | mc_log("assume (%s)" % (str(self),)) 20 | else: 21 | solver.add(Not(self)) 22 | r = False 23 | mc_log("assume ¬(%s)" % (str(self),)) 24 | if solver.check() != sat: 25 | mc_log("unreachable") 26 | sys.exit(0) 27 | return r 28 | 29 | setattr(BoolRef, "__bool__", sched_fork) 30 | setattr(BoolRef, "__nonzero__", getattr(BoolRef, "__bool__")) 31 | 32 | # --------------------------------------------------------------- 33 | # concolic 34 | # --------------------------------------------------------------- 35 | 36 | def sched_flip(self, trace): 37 | solver.push() 38 | solver.add(self) 39 | r = (solver.check() == sat) 40 | solver.pop() 41 | if r: 42 | cond = self 43 | else: 44 | cond = Not(self) 45 | trace.append(cond) 46 | mc_log("%s: %s" % (self, r)) 47 | return r 48 | 49 | def mc_fuzz(f, init_keys, init_vals, cnt = 0): 50 | assert len(init_keys) == len(init_vals) 51 | mc_log("=" * 60) 52 | mc_log("#%s: %s" % (cnt, ', '.join(["%s = %s" % (k, v) for k, v in zip(init_keys, init_vals)]))) 53 | 54 | trace = [] 55 | setattr(BoolRef, "__bool__", lambda self: sched_flip(self, trace)) 56 | setattr(BoolRef, "__nonzero__", getattr(BoolRef, "__bool__")) 57 | 58 | solver.push() 59 | for k, v in zip(init_keys, init_vals): 60 | solver.add(k == v) 61 | try: 62 | f() 63 | except: 64 | typ, value, tb = sys.exc_info() 65 | sys.excepthook(typ, value, tb.tb_next) 66 | solver.pop() 67 | 68 | delattr(BoolRef, "__bool__") 69 | delattr(BoolRef, "__nonzero__") 70 | 71 | # this path done 72 | if trace: 73 | solver.add(Not(And(*trace))) 74 | 75 | # choose a new path 76 | while trace: 77 | solver.push() 78 | solver.add(Not(trace[-1])) 79 | trace = trace[:-1] 80 | solver.add(*trace) 81 | r = solver.check() 82 | solver.pop() 83 | if r == sat: 84 | m = solver.model() 85 | new_init_vals = [m.eval(k, model_completion=True) for k in init_keys] 86 | cnt = mc_fuzz(f, init_keys, new_init_vals, cnt + 1) 87 | 88 | return cnt 89 | -------------------------------------------------------------------------------- /mc_util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Xi Wang 2 | # 3 | # This file is part of the UW CSE 551 lecture code. It is freely 4 | # distributed under the MIT License. 5 | 6 | from __future__ import print_function 7 | from z3 import * 8 | from multiprocessing import Lock 9 | import os, atexit 10 | 11 | solver = Solver() 12 | lock = Lock() 13 | 14 | def mc_log(s): 15 | # "atomic" print; less concern about performance 16 | with lock: 17 | print("[%s] %s" % (os.getpid(), s), file=sys.stderr) 18 | 19 | def mc_assume(b): 20 | return solver.add(b) 21 | 22 | def mc_model_repr(self): 23 | decls = sorted(self.decls(), key=str) 24 | return ", ".join(["%s = %s" % (k, self[k]) for k in decls]) 25 | 26 | setattr(ModelRef, "__repr__", mc_model_repr) 27 | 28 | def mc_unsignedBitVec(): 29 | conf = { 30 | "__div__" : UDiv, 31 | "__rdiv__" : lambda self, other: UDiv(other, self), 32 | "__mod__" : URem, 33 | "__rmod__" : lambda self, other: URem(other, self), 34 | "__rshift__" : LShR, 35 | "__rrshift__": lambda self, other: LShR(other, self), 36 | "__lt__" : ULT, 37 | "__le__" : ULE, 38 | "__gt__" : UGT, 39 | "__ge__" : UGE 40 | } 41 | for k, v in conf.items(): 42 | setattr(BitVecRef, k, v) 43 | 44 | def mc_excepthook(typ, value, tb): 45 | import traceback 46 | msg = ''.join(traceback.format_exception(typ, value, tb)).strip() 47 | # print input/model 48 | if solver.check() == sat: 49 | msg = "%s: %s" % (msg, solver.model()) 50 | # highlight if pygments installed 51 | try: 52 | from pygments import highlight 53 | from pygments.lexers import get_lexer_by_name 54 | from pygments.formatters import get_formatter_by_name 55 | 56 | lexer = get_lexer_by_name("pytb", stripall=True) 57 | formatter = get_formatter_by_name("terminal256") 58 | msg = highlight(msg, lexer, formatter).strip() 59 | except: 60 | pass 61 | mc_log(msg) 62 | 63 | if sys.stderr.isatty(): 64 | sys.excepthook = mc_excepthook 65 | 66 | def mc_exit(): 67 | # wait until all child processes done 68 | try: 69 | while True: 70 | os.waitpid(0, 0) 71 | except: 72 | pass 73 | mc_log("exit") 74 | 75 | atexit.register(mc_exit) 76 | -------------------------------------------------------------------------------- /mod_eqv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2015 Xi Wang 4 | # 5 | # This file is part of the UW CSE 551 lecture code. It is freely 6 | # distributed under the MIT License. 7 | 8 | """ 9 | This resembles Figure 11 of the KLEE paper (OSDI'08). 10 | """ 11 | 12 | from mc import * 13 | 14 | def mod_opt(x, y): 15 | if y & (-y) == y: 16 | return x & (y - 1) 17 | else: 18 | return x % y 19 | 20 | def mod(x, y): 21 | return x % y 22 | 23 | # Z3 is not very happy with n = 32 24 | n = 16 25 | mc_unsignedBitVec() 26 | x = BitVec("x", n) 27 | y = BitVec("y", n) 28 | # seems okay without mc_assume(y != 0) 29 | assert mod(x, y) == mod_opt(x, y) 30 | -------------------------------------------------------------------------------- /test_me.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2015 Xi Wang 4 | # 5 | # This file is part of the UW CSE 551 lecture code. It is freely 6 | # distributed under the MIT License. 7 | 8 | """ 9 | This resembles Section 2.4 of the DART paper (PLDI'05). 10 | """ 11 | 12 | from mc import * 13 | 14 | def test_me(x, y): 15 | z = 2 * x 16 | if z == y: 17 | if y == x + 10: 18 | assert False 19 | 20 | x = BitVec("x", 32) 21 | y = BitVec("y", 32) 22 | test_me(x, y) 23 | #mc_fuzz(lambda: test_me(x, y), [x, y], [0, 0]) 24 | --------------------------------------------------------------------------------