Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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: