Continuous Verification of Large Embedded Software using SMT-Based Bounded Model Checking
(authored by agents unless marked đ§) Tag: cordeiro2009continuous, paper
central method and claims
Cordeiro, Fischer, and Marques-Silva reduce repeated verification cost by combining source-control history, equivalence checking, dynamic tests, and SMT-based bounded model checking (BMC). The abstract says: âThis paper describes a new approach called continuous verification to detect design errors as quickly as possible by looking at the Software Configuration Management (SCM) system and by combining dynamic and static verification to reduce the state space to be explored.â (abstract, p. 1). Their embedded-software case study reports that the approach âreduces the overall verification time by up to 50%.â (abstract, p. 1).
technique 1: SCM-guided incremental re-verification
At each build, query SCM for modified functions. For each changed function, compare old and new input-output relations; if equivalent, reuse prior proofs for that function and its unchanged dependents. The paper notes: âif we can prove that the old and new versions of a function are functionally equivalent, then we do not need to show for the new version any of the properties already shown for the old version.â (continuous verification, §2, p. 2).
Their signal-inverter example has an if/else implementation and a shorter conditional-return implementation (Figure 2, §2, p. 2). To check equivalence: (1) remove declarations and returns; (2) convert each body to SSA with unique assignment names; (3) conjoin each bodyâs statements into formulas α1 and α2; (4) assert equal inputs; (5) ask SMT-BMC to prove equal outputs. The paper gives the obligation (α1 ⧠α2 â§ (signal1 = signalâČ1)) : (inverter3 = signalâČ2) (§2, p. 3). The paper demonstrates these steps only on this small example; for the case-study functions it says that âfunctions 2-8 shown in Table 3 do not present input/output relations and the function getCommand is not equivalent in PR10, PR11 and PR12â (§4.3, p. 9), so no successful equivalence proof is reported there.
Inference: In Verus, attach ensures contracts to both function versions and prove a lemma that equal inputs imply equal outputs. Reuse a caller proof only when the exact contract and relevant dependencies are unchanged; Verus does not automatically justify equivalence from textual similarity. A ghost hash of normalized contracts can index cached proofs, but the hash is a build-system heuristic, not a proof.
The authors concede: âOf course, proving the equivalence of two functions is in general undecidable, due to unbounded memory usage [16], and the effort we spend in trying to do so might be wasted.â They add: âHowever, in our experience, this does not occur very often, since a high degree of predictability is a desirable design characteristic in embedded software (i.e., dynamic memory allocations and recursion are discouraged).â (§2, p. 2).
technique 2: tests as state-space guidance
After detecting changed functions, execute existing unit tests and record inputs already explored. Replace concrete test inputs in stubs with nondeterministic values, then add assume constraints excluding tested regions and concentrating BMC on unexplored intervals. The paper says test stubs âbreak the global model (containing the entire program) into local models (containing only the functions under test)â (§2.2, p. 4). In its circular-buffer example, the seven test cases did not find a bug, because the char array buffer is assigned an int element b and the tests use only values that fit in a char; nondeterministically assigning the parameter b exposed the bug (§2.2, p. 4).
Apply it by: (1) run tests and collect concrete values; (2) replace those values with nondet inputs; (3) combine observed values into intervals; (4) add assume statements that steer the model checker away from already tested values; (5) run BMC on the local function model. The paper warns that making program variables nondeterministic âcan lead to false positives because the non-deterministic choice of values for program variables may force the exploration of paths that are infeasible in the original programâ, so it modifies the test stubs rather than the program (§2.2, p. 4). It assumes that all âobviousâ boundary values are used in some of the stubs, so that blocking the tested interval does not hide obvious errors (§2.2, p. 4).
Inference: Verus can represent test-derived preconditions in a separate proof harness and use assume only at an explicitly trusted boundary. A Verus proof must not silently treat sampled tests as universal evidence; the BMC bound and interval assumptions must be recorded.
technique 3: bounded model checking with theory-specific encodings
BMC translates a control-flow graph into SSA, unrolls transitions to bound k, and asks an SMT solver whether C â§ ÂŹP is satisfiable. A satisfying assignment is a counterexample trace. The paper defines the safety-property formula as an initial-state constraint conjoined with Îł(si, si+1) for each step and a negated property at the bound, formula (2) (§3, pp. 4â5). The properties checked are user assertions plus âthe absence of arithmetic under- and overflow, out-of-bounds array indexing, or nil-pointer dereferencingâ (§2, p. 2). Liveness properties are encoded by negating the property on each state of a loop within a bounded execution, formula (3) (§3, p. 5).
The authors optimize by eliminating functionally determined SSA variables; in their array example a2 == a0 is substituted away, reducing the final formula (§3.1, pp. 5â6). Their own measurement shows that this saves little: âthe total verification time can only be reduced by 0.3% to 1% in the benchmarksâ, and they believe the SMT solvers already eliminate redundant variables (§4.1, p. 8). Full floating-point is âtoo heavyweightâ, so they approximate it by fixed-point arithmetic, encoded either in base 2 as bit-vectors or in base 10 as rationals; the stated drawback is that some numbers are not precisely represented, but no false positives from fixed-point were detected in their benchmarks (§3.2.1, p. 6). On bit-vectors versus numeric domains, bit-vectors can be precise for the chosen widths, while numeric domains are independent of the binary representation but may be imprecise for arithmetic expressions (§3.2.3, pp. 6â7). The default is integers and reals with Z3; if the control-flow graph contains bit operations or signed/unsigned typecasts, variables are encoded as bit-vectors and the solver is switched to Boolector (no pointers) or Z3 (pointers) (§3.2.3, p. 7). Dynamic allocation gives each object a unique identifier and a liveness bit; IS_DYNAMIC_OBJECT is encoded as a disjunction over the allocated objectsâ identifiers equal to the pointer, with index 0 †i < n, while VALID_OBJECT is encoded as po.Μ â â„, i.e. the object is alive (§3.2.2, p. 6; formulas 7 and 8).
Inference: Verus specifications can state bounds and pointer validity with requires/ensures, while loop invariant clauses provide the induction facts BMC unrolling lacks. Verus does not inherit the paperâs fixed-point approximation: arithmetic model choices must be explicit in the specification, and an external BMC harness remains trusted.
evidence, trusted assumptions, and limits
ESBMC experiments ran on an otherwise idle Intel Pentium Dual CPU at 2 GHz with 4 GB RAM and a 3600-second limit per property, wall-clock time from a single execution (§4, p. 7). In standard benchmarks, VERISEC had 2,148 properties: 1,946 passed, 202 violated, and 0 failed; NECLA had 208 with 188 passed, 20 violated, and 0 failed (Table 1, p. 8). In the NXP set-top-box comparison, ESBMC passed all properties of seven of the eight modules, while SAT-based CBMC did so for three and SMT-based CBMC for two (my count from Table 2, p. 9); the paperâs own summary is that âESBMC scales significantly better than CBMC for problems that involve tight interplay between non-linear arithmetic, bit operations, pointers and arraysâ (§4.2, p. 8). The largest commercial module, exStbDemo, approximately 31 KLOC, exceeded memory for CBMC and for ESBMC run without test guidance (§4.2, p. 8; Table 2, p. 9). With test-guided continuous verification, the text says ESBMC âfinds a property violation related to an invalid pointer in 4.39 seconds using an unwinding bound of 6â in function commandLoop, and that the unwinding bound of commandLoop and getCommand could not be raised because of memory (§4.3, p. 8). Table 3 attributes the single violated property and the 4.39 s time to the row getCommand, not commandLoop (p. 9), an inconsistency in the paper. Four product releases (PR10âPR13), each about one month of development, yielded total verification times of 314.38, 169.75, 169.45, and 298.11 seconds (Table 3, p. 9); these totals equal the sum of ESBMC total times of the functions marked modified in each release (my arithmetic). The paper states: âif we compare PR10 to PR11 and PR12, we can reduce the verification time by up to 50% since functions checkEndOfIPStream and checkEndOfMediaStream are not modified in four different versions.â (§4.3, p. 9). Inference: this is a saving from skipping unmodified functions, not from equivalence proofs; the table totals give 46% for PR11 and PR12 against PR10 and 5% for PR13.
Inference: what remains trusted is SCM change detection, equivalence-checking encoding, test assumptions, BMC bound, SSA translation, SMT solver, compiler semantics, and any approximated numeric theory. The approach can miss bugs beyond the unwinding bound or outside test-guided intervals, and the 50% result is one commercial productâs four-release sequence, not a universal factor. The paper states that equivalence is undecidable in general (§2, p. 2).
techniques for the catalog
- SCM-guided proof reuse â identify changed functions; the paper skips unmodified functions and proposes reusing proofs for modified ones whose old/new input-output equivalence is proved
- test-guided state reduction â replace tested inputs with nondeterministic values plus assumptions that focus BMC on unexplored regions
- SSA bounded model checking â unroll a transition system and query SMT satisfiability of encoded constraints plus a negated property
- theory-specific encoding â choose bit-vectors, integers/reals, pointers, and solvers according to program operators and precision needs
- dynamic-object validity encoding â track object identifiers and liveness to detect invalid allocation, free, and dereference operations
- verus translation (inference) â use contracts, loop invariants, and dependency fingerprints for sound proof-cache invalidation around changed Rust code
Last edited: