From ccf21da99f8ddbb0742fac22b795cf944f9e449f Mon Sep 17 00:00:00 2001 From: Per Lindgren <per.lindgren@ltu.se> Date: Fri, 10 Jan 2020 16:09:19 +0100 Subject: [PATCH] mostly comments and README.md --- README.md | 81 +++++++++++++++++++++++-------- examples/assume_assert.rs | 45 ++++++++++++++--- examples/cortex_m_test1.rs | 6 +-- examples/cortex_m_test_nightly.rs | 21 ++++++-- examples/paths.rs | 4 +- 5 files changed, 124 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index dc1a2d1..4712f56 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,84 @@ # klee-examples -This repo contains a set of usage examples for `klee-sys` low-level KLEE bindings. +This repo contains a set of usage examples for `klee-sys` low-level KLEE bindings. For more information on internal design behind see the [klee-sys](https://gitlab.henriktjader.com/pln/klee-sys) repo. -## paths.rs +See section `Cargo.toml` for detaled information on features introduced. -This example showcase the different path termintaiton conditions possible and their effect to KLEE test case generation. +### General dependencies -## assume_assert.rs +- llvm toolchain tested with (9.0.1) +- rustup tested with 1.40.0 (73528e339 2019-12-16) +- klee tested with KLEE 2.1-pre (https://klee.github.io) -This example showcase contract based verification, and the possibilies to extract proofs. +- cargo-klee (installed from git) -## struct.rs +--- -This example show the case of partially symbolic structures. +## Basic test examples -## vcell_test.rs +- `paths.rs` -Simple test to showcase low-level `vcell` access. + This example showcase the different path termintaiton conditions possible and their effect to KLEE test case generation. -## register_test.rs +- `assume_assert.rs` -Simple test to showcase the use of the `volatile-register` abstraction. + This example showcase contract based verification, and the possibilies to extract proofs. -## f401_minimal.rs +- `struct.rs` -This example showcase the execution of code on the stm32f401 (and similar targets from the f4). + This example show the case of partially symbolic structures. + +--- + +## Hardware oriented test examples + +- `vcell_test.rs` + + Simple test to showcase low-level [vcell](https://github.com/perlindgren/vcell) access. `vcell` underlies all machine generated hardware accesses in the Rust embedded ecosystem. + + The `klee-analysis` feature replaces `read` operations to memory by symbolic values (using `klee-sys`). `write` operations are suppressed as for analysis we are not interested in the side effects. + +- `register_test.rs` + + Simple test to showcase the use of the `volatile-register` abstraction. `volitile-register` builds on `vcell` and is used by both hand written and machine generated hardware accesses in the Rust embedded ecosystem. + +- `cortex-m-test1.rs` + + Simple test to showcase the [cortex-m](https://github.com/perlindgren/vcell) abstraction of ARM-core peripherals ARM thumb assembly instructions and ARM thumb CPU registers. `cortex-m` uses the `volatile-register` abstraction for ARM-core peripherals. The `klee-analysis` feature replaces read operations on CPU registers by symbolic data and suppresses write operations as for analysis we are not interested in the side effects. -### dependencies + Moreover the example showcase the discrepancies between Rust source code paths and paths in the generated (semantically equivalent) LLVM-IR. -- stm32401 nucleo board or similiar with recent stlink firmware -- (recent) openocd (tested with version 0.10.0) -- arm-none-eabi-gdb (tested with version 0.8.3) -- llvm target thumbv7em-none-eabi (`rustup target add thumbv7em-none-eabi`) + TODO: perhaps the latter part should be moved to Basic test examples as it is not `cortex-m` specific. + +- `cortex-m-test-nightly.rs` + + This example showcase how compiler "intrinsics" can be safely adopted by proving the absence of errors. + + TODO: perhaps this part should also be moved to Basic test examples as it is not `cortex-m` specific. + +--- + +## Testing on hardware + +### Additional Dependencies: + +- `stm32401` Nucleo64 board or similar with recent `stlink` firmware (tested with latest firmware as of 2020-01-10). +- `openocd` (tested with version 0.10.0) +- `arm-none-eabi-gdb` (tested with version 0.8.3) +- llvm target `thumbv7em-none-eabihf` + - `> rustup show`, to show current Rust tool-chain and installed targets. + - `> rustup target add <target>`, to add target, e.g., `thumbv7em-none-eab¡hf`. + +### Examples + +- `f401_minimal.rs` + +This example showcase the execution of code on the stm32f401 (and similar targets from the f4). +-- ## Licencse Copyright Per Lindgren. -All rights reserved, use restricted for non-commercial purpose. \ No newline at end of file +All rights reserved, use restricted for non-commercial purpose. diff --git a/examples/assume_assert.rs b/examples/assume_assert.rs index bf3c8e8..788ae13 100644 --- a/examples/assume_assert.rs +++ b/examples/assume_assert.rs @@ -23,7 +23,7 @@ fn f1(a: u32) -> u32 { // This example showcase how contracts can be encoded // -// Let us start with the functian f1 (above). +// Let us start with the function f1 (above). // Intuitively `f1(a) > a` right? // // Well let's check that.... @@ -59,23 +59,56 @@ fn f1(a: u32) -> u32 { // // > cargo klee --example assume_assert // -// We can now finalize the contract, by uncommenting line 20 (with the post condition). +// We can now finalize the contract, by un-commenting line 20 (with the post condition). // // So our pre-condition is that a < u32.MAX and the post condition is that r > a. // // Can assumptions go wrong? // Well they can? And we will spot it! // -// Try uncommenting line 18, re-run `cargo klee` +// Try un-commenting line 18, re-run `cargo klee` // // You should get... // KLEE: ERROR: /home/pln/.cargo/git/checkouts/klee-sys-7ee2aa8a1a6bbc46/c8275a3/src/lib.rs:19: invalid klee_assume call (provably false) // // So KLEE tracks the "path condition", i.e., at line 18 it knows (assumes) that that -// a < u32::MAX, and finds that the assumtion a == u32::MAX cannot be satisfied. +// a < u32::MAX, and finds that the assumption a == u32::MAX cannot be satisfied. // -// This is exeteremely powerful as KLEE tracks all known "constraints" and all their raliaitons +// This is extremely powerful as KLEE tracks all known "constraints" and all their raliaitons // and mathematically checks for the satisfiability of each "assume" and "assert". -// // So what we get here is not a mere test, but an actual proof!!!! // This is the way! +// +// In the future we could provide some additional syntactic support for +// contracts. It could look something like: +// +//#[contract: { +// pre: a < u32::MAX, +// post: f1(a) > a, +// } ] +//fn f1(a: u32) -> u32 { +// a + 1 +//} +// +// The corresponding generated code could look something like: +// fn f1(a: u32) -> u32 { +// klee_assume(a < u32::MAX); // pre +// let r = f1_body(a); +// klee_assert!(r > a); // post +// r +// } +// #[inline(always)] +// fn f1_body(a: u32) -> u32 { +// a + 1 +// } +// +// It might even be possible to derive post condtitions from pre conditions, +// and report them to the user. Problem is that the conditions are +// represented as "first order logic" (FOL) constraints, which need to be +// converted into readable form (preferably Rust expressions.) +// +// Another potential extension is the extend contracts, assumptions and assertions +// to support FOL natively, this would give us logic quantifiers: +// `forall` and `exists`. Using FOL we can reason on programs in a more +// general way, however the system will be significantly more difficult +// to understand and use, so it remains to be seen/evaluated. diff --git a/examples/cortex_m_test1.rs b/examples/cortex_m_test1.rs index b32155c..08ac0ea 100644 --- a/examples/cortex_m_test1.rs +++ b/examples/cortex_m_test1.rs @@ -45,7 +45,7 @@ fn main() { // // This is due to that the `dwt.read` has rendered `a` as a symbolic value. // -// Now let's comment out line 18 and uncamment line 19. +// Now let's comment out line 18 and un-comment line 19. // // > cargo klee --example cortex_m_test1 // ... @@ -76,7 +76,7 @@ fn main() { // So what we see here, is that the if statement is nowhere to be found // and we call @obort(), and the code below is unreachable (as abort() -> !) // -// This is semantically equivallent to: +// This is semantically equivalent to: // if a == unsafe { S } { // // panic!(); // klee_abort!(); // (A) @@ -104,6 +104,6 @@ fn main() { // Our test provides "complete" coverage regarding the semantics of the source code // (as we have not missed any potential error). // -// This example is of course contrieved, but showcase that "classical" measures/ +// This example is of course contrived, but showcase that "classical" measures/ // quality goals restated and certification procedures updated accordingly! // This is the way! diff --git a/examples/cortex_m_test_nightly.rs b/examples/cortex_m_test_nightly.rs index 499a803..7151685 100644 --- a/examples/cortex_m_test_nightly.rs +++ b/examples/cortex_m_test_nightly.rs @@ -2,7 +2,7 @@ #![no_std] #![no_main] -use klee_sys::klee_abort; +use klee_sys::*; extern crate cortex_m; extern crate panic_klee; @@ -13,18 +13,25 @@ use core::{intrinsics::unchecked_div, num::Wrapping, ptr::read_volatile}; #[no_mangle] fn main() { let peripherals = Peripherals::take().unwrap(); - // let peripherals = Peripherals::take().unwrap(); + let mut dwt = peripherals.DWT; dwt.enable_cycle_counter(); let a = dwt.cyccnt.read(); let b = dwt.cyccnt.read(); let c = dwt.cyccnt.read(); + // let d = (Wrapping(c) - (Wrapping(b) - Wrapping(100))).0; + // klee_assume(d != 0); unsafe { let some_time_quota = unchecked_div(a, (Wrapping(c) - (Wrapping(b) - Wrapping(100))).0); read_volatile(&some_time_quota); // prevent optimization in release mode } } +// Notice this example currently requires the nightly build of Rust. +// > rustup override set nightly +// When you are done with this example, you may return to stable Rust. +// > rust override unset +// // > cargo klee --example cortex_m_test_nightly -r -k -g -v // ... // KLEE: WARNING: undefined reference to function: rust_eh_personality @@ -133,5 +140,13 @@ fn main() { // We can fearlessly apply optimisations (including intrinsic/primitive operations) // and let the tool prove that the code is free of potential errors. // -// Thus we get BOTH improved performance and improved reliability/correctness at the same time. +// Try uncommenting lines 22 and 23. This introduces a sufficient assumption +// for KLEE to prove the absence of errors. (Beware that this guarantee +// holds only for the exact source code given. If you change anything +// the analysis needs to be re-done.) +// +// In conclusion, programs proven free of errors offer both +// - improved performance (allow safe use of intrinsics), and +// - improved reliability/correctness +// at the same time. // This is the way! diff --git a/examples/paths.rs b/examples/paths.rs index df641d0..71a096c 100644 --- a/examples/paths.rs +++ b/examples/paths.rs @@ -11,7 +11,7 @@ fn main() { let mut a: i32 = 0; klee_make_symbolic!(&mut a, "a"); // Rust panic on a == 200 (div by zero), or a - 200 (causing an arithmetic overflow). - let _ = 100 / (a - 200); // let _ = 100 / a.wrapping_sub(200); + let _ = 100 / a.wrapping_sub(200); let _ = match a { 0 => klee_abort!(), @@ -23,7 +23,7 @@ fn main() { 6 => klee_assert_eq!(false, true), _ => (), }; - panic!("at end"); // just one instane of panic! will be spotted + panic!("at end"); // just one instance of panic! will be spotted } // KLEE will generate one test per explicit error: -- GitLab