diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ce88ac07d36756f6dcf7950398ce2f3db7528bb..f54f37afc2d588168eab1253abfc65903014caa5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,43 @@ { "cSpell.ignoreWords": [ + "alpha", + "ask", + "break", + "can", "cmake", + "controller", "core", "cstr", + "cyccnt", "datatypes", + "f", + "for", + "grepit", + "in", + "is", + "joh", + "johan", "ktest", "libklee", "libz", "llvm", "mkdir", + "nucleo", + "opel", + "present", + "primask", + "romeo", + "rtic", "runtest", "rustc", + "same", "satisfiability", "skylake", + "so", + "speedster", + "vcell", + "xx", + "you", "znver" ] } \ No newline at end of file diff --git a/README.md b/README.md index e7b7a9809f0049249aa17b50dab0d8a40a97259d..04fe3e58c30512cf1eab7d72facb335fa5b254c9 100644 --- a/README.md +++ b/README.md @@ -118,10 +118,7 @@ Here you learn: - an easy way to compile and run KLEE on Rust code - an easy way to replay test cases for Rust programs - -TODO: - -More interesting verification problems using `cargo klee`. +- an easy way to find "hard to find" errors in embedded code -- diff --git a/cargo_klee_examples/Cargo.toml b/cargo_klee_examples/Cargo.toml index c095282ba045e1842fbc9c92aff81c8a871ec560..e0d04a04df6483a138cfec8d126665639931bcba 100644 --- a/cargo_klee_examples/Cargo.toml +++ b/cargo_klee_examples/Cargo.toml @@ -4,6 +4,10 @@ version = "0.1.0" authors = ["pln <Per Lindgren>"] edition = "2018" +# [dependencies.stm32f4] +# version = "0.12.1" +# features = ["stm32f401"] + [dependencies.klee-sys] git = "https://gitlab.henriktjader.com/pln/klee-sys.git" version = "0.2.0" @@ -12,6 +16,16 @@ version = "0.2.0" git = "https://gitlab.henriktjader.com/pln/panic-klee.git" version = "0.1.0" +[dependencies.cortex-m] +version = "0.6.1" + +[dependencies] +vcell = "0.1.2" + +[patch.crates-io] +cortex-m = { git = "https://github.com/perlindgren/cortex-m.git", branch = "trustit" } +vcell = { git = "https://github.com/perlindgren/vcell.git", branch = "trustit" } + [profile.dev] panic = "abort" @@ -27,4 +41,8 @@ lto = true # better optimization codegen-units = 1 # better optimization [features] -klee-analysis = ["klee-sys/klee-analysis"] +klee-analysis = [ + "klee-sys/klee-analysis", + "cortex-m/klee-analysis", + "vcell/klee-analysis" +] diff --git a/cargo_klee_examples/examples/cyccnt.rs b/cargo_klee_examples/examples/cyccnt.rs new file mode 100644 index 0000000000000000000000000000000000000000..ddf590ecf8920b66a49d902d64ea6f2ee92b4f65 --- /dev/null +++ b/cargo_klee_examples/examples/cyccnt.rs @@ -0,0 +1,248 @@ +// cyccnt.rs + +#![no_std] +#![no_main] + +extern crate cortex_m; +extern crate panic_klee; + +#[no_mangle] +fn main() { + let mut core = cortex_m::Peripherals::take().unwrap(); + // core.DCB.enable_trace(); + // core.DWT.enable_cycle_counter(); + + let start: u32 = core.DWT.cyccnt.read(); + + // some code to measure + // ... + + let end = core.DWT.cyccnt.read(); + + let _time = end - start; +} + +// A) Running KLEE on embedded code: +// +// Let's now revisit the time measure example from `rtic_f4xx_nucleo`. +// For simplicity we omit the `rtic` specifics. +// +// `cortex-m` is an API to the ARM Cortex core peripherals. +// (Under RTIC it was passed to the `init::Context` as `core`.) +// +// In this example we access it directly. +// Since Rust forbids mutable aliasing, we cannot `take` it twice, +// but more about that later... +// +// Let's focus on the access to the core peripherals, and +// the relation to the `klee-analysis` feature. +// +// In the Rust embedded ecosystem, all hardware registers +// are accessed through `vcell`, which ensure `volatile access`. +// (This means that reads and writes goes to memory.) +// +// We override `vcell` by a custom implementation: +// +// Looking at the `Cargo.toml` we find: +// +// [patch.crates-io] +// cortex-m = { git = "https://github.com/perlindgren/cortex-m.git", branch = "trustit" } +// vcell = { git = "https://github.com/perlindgren/vcell.git", branch = "trustit" } +// +// and +// +// [features] +// klee-analysis = [ +// "klee-sys/klee-analysis", +// "cortex-m/klee-analysis", +// "vcell/klee-analysis" +// ] +// +// The `klee-analysis` feature is propagated to `vcell`. +// When active: +// - writes are suppressed (they have no effect), and +// - reads generate a fresh (new) unique symbolic value. +// +// In the code above Line 15, we get one symbolic value for `DWT.cyccnt.read()`, +// and in Line 20, we get another unique symbolic value. +// +// Among other things `cortex-m` provides an API to read/write the `PRIMASK`, +// core register. (This controls the global interrupt enable/disable). +// +// We override the `cortex-m` crate, and propagate the `klee-analysis` feature. +// Similarly to `vcell`, when the `klee-analysis` feature is active: +// - writes to PRIMASK are suppressed, and +// - reads generate a fresh (new) unique value. +// +// In the code above Line 11, `cortex-m` executes the `take()` operation +// in a "critical section". In more detail: +// It first reads the `PRIMASK`, if false (we are not already in a critical section) +// it sets the `PRIMASK`. +// +// Now let's run the analysis: +// +// > cargo klee --example cyccnt --release +// +// KLEE: Using Z3 solver backend +// KLEE: WARNING: undefined reference to function: rust_eh_personality +// +// KLEE: done: total instructions = 38 +// KLEE: done: completed paths = 1 +// KLEE: done: generated tests = 1 +// +// Only one path was found (no error occurred along the path). +// +// > ktest-tool target/release/examples/klee-last/test000001.ktest +// +// ktest file : 'target/release/examples/klee-last/test000001.ktest' +// args : ['/home/pln/courses/d7020e/klee_tutorial/cargo_klee_examples/target/release/examples/cyccnt-012d42640c9faf9d.ll'] +// num objects: 3 +// object 0: name: 'PRIMASK' +// object 0: size: 4 +// object 0: data: b'\x00\x00\x00\x00' +// object 0: hex : 0x00000000 +// object 0: int : 0 +// object 0: uint: 0 +// object 0: text: .... +// object 1: name: 'vcell' +// object 1: size: 4 +// object 1: data: b'\x00\x00\x00\x00' +// object 1: hex : 0x00000000 +// object 1: int : 0 +// object 1: uint: 0 +// object 1: text: .... +// object 2: name: 'vcell' +// object 2: size: 4 +// object 2: data: b'\x00\x00\x00\x00' +// object 2: hex : 0x00000000 +// object 2: int : 0 +// object 2: uint: 0 +// object 2: text: .... +// +// As expected we read the `PRIMASK` and then read `cyccnt` twice. +// +// KLEE does not know which register was read, just that a `vcell` +// was accessed. +// +// We can replay the test: +// +// > cargo klee --example cyccnt --release --replay --gdb +// ... +// Type "apropos word" to search for commands related to "word"... +// Reading symbols from cyccnt.replay... +// (gdb) break main +// Breakpoint 1 at 0x555555555127: file examples/cyccnt.rs, line 11. +// (gdb) set environment KTEST_FILE=klee-last/test000001.ktest +// (gdb) run +// Breakpoint 1, klee_sys::lib_klee_analysis::klee_make_symbolic<u32> (t=0x7fffffffd75c, +// name=<optimized out>) +// at /home/pln/.cargo/git/checkouts/klee-sys-7ee2aa8a1a6bbc46/de8fd90/src/lib_klee_analysis.rs:19 +// 19 crate::ll::klee_make_symbolic( +// +// Nice!, the very first thing that happens is that we get a symbolic value +// (this is the `PRIMASK`) +// +// (gdb) next +// cortex_m::interrupt::free<closure-0,core::option::Option<cortex_m::peripheral::Peripherals>> (f=...) +// at /home/pln/.cargo/git/checkouts/cortex-m-514878a7410beb63/07e7543/src/interrupt.rs:75 +// 75 let r = f(unsafe { &CriticalSection::new() }); +// +// This is the critical section mentioned above: +// +// (gdb) next +// cyccnt::main () at examples/cyccnt.rs:15 +// 15 let start: u32 = core.DWT.cyccnt.read(); +// +// (gdb) next +// cyccnt::main () at examples/cyccnt.rs:15 +// 20 let end = core.DWT.cyccnt.read(); +// +// (gdb) next +// 23 } +// +// Pretty much what we could expect. +// +// Now try to `run` the again and print the values of `start`/`end`. +// +// What do you get, and why? +// +// [your answer here] +// +// As you should have seen, this was not particularly informative, right? +// +// B) In debug/dev mode +// +// Let's replay the example in debug mode? +// +// > cargo klee --example cyccnt +// +// KLEE: ERROR: /home/pln/.cargo/git/checkouts/panic-klee-aa8d015442188497/3b0c897/src/lib.rs:8: abort failure +// KLEE: NOTE: now ignoring this error at this location +// +// KLEE: done: total instructions = 761 +// KLEE: done: completed paths = 4 +// KLEE: done: generated tests = 3 +// +// WHUT!!!!! +// An error occurred, this was unexpected, right? +// +// Try figure out what caused the error. +// +// Hint, look at the generated tests and open the `abort.err` file. +// The error file contains a backtrace. +// Replay the failing test with a breakpoint set to the failing operation. +// Print the values of `start` and `end`, when hitting the breakpoint. +// +// Value of `start`. +// +// [your answer here] +// +// Value of `end` +// +// [your answer here] +// +// Why does these values cause an error debug/dev build but not in a release build. +// +// [your answer here] +// +// C) Fix the problem! +// +// Come up with a solution to the problem, and alter the source code accordingly. +// +// Verify that the program now passes without errors in both debug/dev and +// release builds. +// +// There are numerous ways to solve the problem. +// Argue for your solution in your own words. +// +// [your answer here] +// +// D) Learning outcomes and major takeaways. +// +// In this exercise, you have validated embedded Rust code using KLEE +// and found a typical "hard to find" error. +// +// why are such errors so hard to find? +// +// Think about it? +// +// What is the condition for triggering the error? +// +// What is the behavior of CYCCNT? +// +// Assume the MCU is running at 8MHz. +// +// How long time would lines 16/17 take to run to trigger the error? +// +// [your answer here] +// +// Of course this is a contrived example, and may not occur in practice. +// But, it represents a class of problems/errors/bugs that is +// extremely hard to find by testing. +// +// Ask Jan Van Deventer about the anti-lock breaks he developed +// for Alpha Romeo! (His nick, was Fender Bender at the time.) +// +// Same break controller is present in Opel Speedster, so you can +// ask Johan at Grepit how it feels when you have zero-breaking +// after hitting a bump. (He now bypassed the anti-lock.)