This closes #3.
Merge remote-tracking branch 'd3zd3z/sim'
* d3zd3z/sim:
sim: Create a small README.rst
sim: Use logging to control output
sim: Add simulator code
diff --git a/sim/.gitignore b/sim/.gitignore
new file mode 100644
index 0000000..9409674
--- /dev/null
+++ b/sim/.gitignore
@@ -0,0 +1,2 @@
+target
+.*.swp
diff --git a/sim/Cargo.lock b/sim/Cargo.lock
new file mode 100644
index 0000000..9c43c4b
--- /dev/null
+++ b/sim/Cargo.lock
@@ -0,0 +1,222 @@
+[root]
+name = "bootsim"
+version = "0.1.0"
+dependencies = [
+ "docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "error-chain 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "dbghelp-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "docopt"
+version = "0.6.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "error-chain"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gcc"
+version = "0.3.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "log"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex"
+version = "0.1.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc-serialize"
+version = "0.3.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "strsim"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "thread-id"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread_local"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "utf8-ranges"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
+"checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80"
+"checksum backtrace-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3602e8d8c43336088a8505fa55cae2b3884a9be29440863a11528a42f46f6bb7"
+"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
+"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
+"checksum docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)" = "4a7ef30445607f6fc8720f0a0a2c7442284b629cf0d049286860fae23e71c4d9"
+"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f"
+"checksum error-chain 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cd681735364a04cd5d69f01a4f6768e70473941f8d86d8c224faf6955a75799"
+"checksum gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "553f11439bdefe755bf366b264820f1da70f3aaf3924e594b886beb9c831bcf5"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b"
+"checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8"
+"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
+"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
+"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
+"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
+"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
+"checksum rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1430d286cadb237c17c885e25447c982c97113926bb579f4379c0eca8d9586dc"
+"checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b"
+"checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c"
+"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
+"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
+"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
diff --git a/sim/Cargo.toml b/sim/Cargo.toml
new file mode 100644
index 0000000..a93f299
--- /dev/null
+++ b/sim/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "bootsim"
+version = "0.1.0"
+authors = ["David Brown <davidb@davidb.org>"]
+build = "build.rs"
+
+[build-dependencies]
+gcc = "0.3.38"
+
+[dependencies]
+libc = "0.2.0"
+rand = "0.3.0"
+error-chain = "0.7.1"
+docopt = "0.6"
+rustc-serialize = "0.3"
+log = "0.3"
+env_logger = "0.3"
+
+[profile.test]
+opt-level = 1
diff --git a/sim/README.rst b/sim/README.rst
new file mode 100644
index 0000000..b3c7ecb
--- /dev/null
+++ b/sim/README.rst
@@ -0,0 +1,42 @@
+MCUboot Simulator
+#################
+
+This is a small simulator designed to exercise the mcuboot upgrade
+code, specifically testing untimely reset scenarios to make sure the
+code is robust.
+
+Prerequisites
+=============
+
+The simulator is written in Rust_, and you will need to install it to
+build it. The installation_ page describes this process. The
+simulator can be built with the stable release of Rust.
+
+.. _Rust: https://www.rust-lang.org/
+
+.. _installation: https://www.rust-lang.org/en-US/install.html
+
+Building
+========
+
+Once Rust is installed, build cargo by::
+
+ $ cargo build --release
+
+this should download and compile the necessary dependencies, compile
+the relevant modules from mcuboot, and build the simulator. The
+resulting executable will be placed in ``./target/release/bootsim``
+and can be run directly::
+
+ $ ./target/release/bootsim run --device k64f
+
+Calling with ``--help`` will give a more thorough usage.
+
+Debugging
+=========
+
+If the simulator indicates a failure, you can turn on additional
+logging by setting ``RUST_LOG=warn`` or ``RUST_LOG=error`` in the
+environment::
+
+ $ RUST_LOG=warn ./target/release/bootsim run ...
diff --git a/sim/build.rs b/sim/build.rs
new file mode 100644
index 0000000..c69c09a
--- /dev/null
+++ b/sim/build.rs
@@ -0,0 +1,15 @@
+// Build the library.
+
+extern crate gcc;
+
+fn main() {
+ let mut conf = gcc::Config::new();
+
+ conf.file("../boot/bootutil/src/loader.c");
+ conf.file("../boot/bootutil/src/bootutil_misc.c");
+ conf.file("csupport/run.c");
+ conf.include("../boot/bootutil/include");
+ conf.include("../zephyr/include");
+ conf.debug(true);
+ conf.compile("libbootutil.a");
+}
diff --git a/sim/csupport/run.c b/sim/csupport/run.c
new file mode 100644
index 0000000..e24531f
--- /dev/null
+++ b/sim/csupport/run.c
@@ -0,0 +1,195 @@
+/* Run the boot image. */
+
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <bootutil/bootutil.h>
+#include <bootutil/image.h>
+#include "flash_map/flash_map.h"
+
+#include "../../boot/bootutil/src/bootutil_priv.h"
+
+extern int sim_flash_erase(void *flash, uint32_t offset, uint32_t size);
+extern int sim_flash_read(void *flash, uint32_t offset, uint8_t *dest, uint32_t size);
+extern int sim_flash_write(void *flash, uint32_t offset, const uint8_t *src, uint32_t size);
+
+static void *flash_device;
+static jmp_buf boot_jmpbuf;
+int flash_counter;
+
+int jumped = 0;
+
+struct area {
+ struct flash_area whole;
+ struct flash_area *areas;
+ uint32_t num_areas;
+ uint8_t id;
+};
+
+struct area_desc {
+ struct area slots[16];
+ uint32_t num_slots;
+};
+
+static struct area_desc *flash_areas;
+
+int invoke_boot_go(void *flash, struct area_desc *adesc)
+{
+ int res;
+ struct boot_rsp rsp;
+
+ flash_device = flash;
+ flash_areas = adesc;
+ if (setjmp(boot_jmpbuf) == 0) {
+ res = boot_go(&rsp);
+ /* printf("boot_go result: %d (0x%08x)\n", res, rsp.br_image_addr); */
+ return res;
+ } else {
+ return -0x13579;
+ }
+}
+
+int hal_flash_read(uint8_t flash_id, uint32_t address, void *dst,
+ uint32_t num_bytes)
+{
+ // printf("hal_flash_read: %d, 0x%08x (0x%x)\n",
+ // flash_id, address, num_bytes);
+ return sim_flash_read(flash_device, address, dst, num_bytes);
+}
+
+int hal_flash_write(uint8_t flash_id, uint32_t address,
+ const void *src, int32_t num_bytes)
+{
+ // printf("hal_flash_write: 0x%08x (0x%x)\n", address, num_bytes);
+ // fflush(stdout);
+ if (--flash_counter == 0) {
+ jumped++;
+ longjmp(boot_jmpbuf, 1);
+ }
+ return sim_flash_write(flash_device, address, src, num_bytes);
+}
+
+int hal_flash_erase(uint8_t flash_id, uint32_t address,
+ uint32_t num_bytes)
+{
+ // printf("hal_flash_erase: 0x%08x, (0x%x)\n", address, num_bytes);
+ // fflush(stdout);
+ if (--flash_counter == 0) {
+ jumped++;
+ longjmp(boot_jmpbuf, 1);
+ }
+ return sim_flash_erase(flash_device, address, num_bytes);
+}
+
+uint8_t hal_flash_align(uint8_t flash_id)
+{
+ return 1;
+}
+
+void *os_malloc(size_t size)
+{
+ // printf("os_malloc 0x%x bytes\n", size);
+ return malloc(size);
+}
+
+int flash_area_id_from_image_slot(int slot)
+{
+ return slot + 1;
+}
+
+int flash_area_open(uint8_t id, const struct flash_area **area)
+{
+ int i;
+ struct area *slot;
+
+ for (i = 0; i < flash_areas->num_slots; i++) {
+ if (flash_areas->slots[i].id == id)
+ break;
+ }
+ if (i == flash_areas->num_slots) {
+ printf("Unsupported area\n");
+ abort();
+ }
+
+ /* Unsure if this is right, just returning the first area. */
+ *area = &flash_areas->slots[i].whole;
+ return 0;
+}
+
+void flash_area_close(const struct flash_area *area)
+{
+}
+
+/*
+ * Read/write/erase. Offset is relative from beginning of flash area.
+ */
+int flash_area_read(const struct flash_area *area, uint32_t off, void *dst,
+ uint32_t len)
+{
+ return hal_flash_read(area->fa_id,
+ area->fa_off + off,
+ dst, len);
+}
+
+int flash_area_write(const struct flash_area *area, uint32_t off, const void *src,
+ uint32_t len)
+{
+ return hal_flash_write(area->fa_id,
+ area->fa_off + off,
+ src, len);
+}
+
+int flash_area_erase(const struct flash_area *area, uint32_t off, uint32_t len)
+{
+ return hal_flash_erase(area->fa_id,
+ area->fa_off + off,
+ len);
+}
+
+int flash_area_to_sectors(int idx, int *cnt, struct flash_area *ret)
+{
+ int i;
+ struct area *slot;
+
+ for (i = 0; i < flash_areas->num_slots; i++) {
+ if (flash_areas->slots[i].id == idx)
+ break;
+ }
+ if (i == flash_areas->num_slots) {
+ printf("Unsupported area\n");
+ abort();
+ }
+
+ slot = &flash_areas->slots[i];
+
+ if (slot->num_areas > *cnt) {
+ printf("Too many areas in slot\n");
+ abort();
+ }
+
+ *cnt = slot->num_areas;
+ memcpy(ret, slot->areas, slot->num_areas * sizeof(struct flash_area));
+
+ return 0;
+}
+
+uint8_t sim_flash_align = 1;
+uint8_t flash_area_align(const struct flash_area *area)
+{
+ return sim_flash_align;
+}
+
+
+int bootutil_img_validate(struct image_header *hdr,
+ const struct flash_area *fap,
+ uint8_t *tmp_buf, uint32_t tmp_buf_sz,
+ uint8_t *seed, int seed_len, uint8_t *out_hash)
+{
+ if (hal_flash_read(fap->fa_id, fap->fa_off, tmp_buf, 4)) {
+ printf("Flash read error\n");
+ abort();
+ }
+
+ return (*((uint32_t *) tmp_buf) != 0x96f3b83c);
+}
diff --git a/sim/src/api.rs b/sim/src/api.rs
new file mode 100644
index 0000000..71d4643
--- /dev/null
+++ b/sim/src/api.rs
@@ -0,0 +1,34 @@
+//! HAL api for MyNewt applications
+
+use flash::{Result, Flash};
+use libc;
+use std::slice;
+
+// This isn't meant to call directly, but by a wrapper.
+
+#[no_mangle]
+pub extern fn sim_flash_erase(dev: *mut Flash, offset: u32, size: u32) -> libc::c_int {
+ let mut dev: &mut Flash = unsafe { &mut *dev };
+ map_err(dev.erase(offset as usize, size as usize))
+}
+
+#[no_mangle]
+pub extern fn sim_flash_read(dev: *const Flash, offset: u32, dest: *mut u8, size: u32) -> libc::c_int {
+ let dev: &Flash = unsafe { &*dev };
+ let mut buf: &mut[u8] = unsafe { slice::from_raw_parts_mut(dest, size as usize) };
+ map_err(dev.read(offset as usize, &mut buf))
+}
+
+#[no_mangle]
+pub extern fn sim_flash_write(dev: *mut Flash, offset: u32, src: *const u8, size: u32) -> libc::c_int {
+ let mut dev: &mut Flash = unsafe { &mut *dev };
+ let buf: &[u8] = unsafe { slice::from_raw_parts(src, size as usize) };
+ map_err(dev.write(offset as usize, &buf))
+}
+
+fn map_err(err: Result<()>) -> libc::c_int {
+ match err {
+ Ok(()) => 0,
+ Err(_) => -1,
+ }
+}
diff --git a/sim/src/area.rs b/sim/src/area.rs
new file mode 100644
index 0000000..9bb7d57
--- /dev/null
+++ b/sim/src/area.rs
@@ -0,0 +1,161 @@
+//! Describe flash areas.
+
+use flash::{Flash, Sector};
+use std::marker::PhantomData;
+use std::ptr;
+
+/// Structure to build up the boot area table.
+#[derive(Debug)]
+pub struct AreaDesc {
+ areas: Vec<Vec<FlashArea>>,
+ whole: Vec<FlashArea>,
+ sectors: Vec<Sector>,
+}
+
+impl AreaDesc {
+ pub fn new(flash: &Flash) -> AreaDesc {
+ AreaDesc {
+ areas: vec![],
+ whole: vec![],
+ sectors: flash.sector_iter().collect(),
+ }
+ }
+
+ /// Add a slot to the image. The slot must align with erasable units in the flash device.
+ /// Panics if the description is not valid. There are also bootloader assumptions that the
+ /// slots are SLOT0, SLOT1, and SCRATCH in that order.
+ pub fn add_image(&mut self, base: usize, len: usize, id: FlashId) {
+ let nid = id as usize;
+ let orig_base = base;
+ let orig_len = len;
+ let mut base = base;
+ let mut len = len;
+
+ while nid > self.areas.len() {
+ self.areas.push(vec![]);
+ self.whole.push(Default::default());
+ }
+
+ if nid != self.areas.len() {
+ panic!("Flash areas not added in order");
+ }
+
+ let mut area = vec![];
+
+ for sector in &self.sectors {
+ if len == 0 {
+ break;
+ };
+ if base > sector.base + sector.size - 1 {
+ continue;
+ }
+ if sector.base != base {
+ panic!("Image does not start on a sector boundary");
+ }
+
+ area.push(FlashArea {
+ flash_id: id,
+ device_id: 42,
+ pad16: 0,
+ off: sector.base as u32,
+ size: sector.size as u32,
+ });
+
+ base += sector.size;
+ len -= sector.size;
+ }
+
+ if len != 0 {
+ panic!("Image goes past end of device");
+ }
+
+ self.areas.push(area);
+ self.whole.push(FlashArea {
+ flash_id: id,
+ device_id: 42,
+ pad16: 0,
+ off: orig_base as u32,
+ size: orig_len as u32,
+ });
+ }
+
+ pub fn get_c(&self) -> CAreaDesc {
+ let mut areas: CAreaDesc = Default::default();
+
+ assert_eq!(self.areas.len(), self.whole.len());
+
+ for (i, area) in self.areas.iter().enumerate() {
+ if area.len() > 0 {
+ areas.slots[i].areas = &area[0];
+ areas.slots[i].whole = self.whole[i].clone();
+ areas.slots[i].num_areas = area.len() as u32;
+ areas.slots[i].id = area[0].flash_id;
+ }
+ }
+
+ areas.num_slots = self.areas.len() as u32;
+
+ areas
+ }
+}
+
+/// The area descriptor, C format.
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct CAreaDesc<'a> {
+ slots: [CArea<'a>; 16],
+ num_slots: u32,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct CArea<'a> {
+ whole: FlashArea,
+ areas: *const FlashArea,
+ num_areas: u32,
+ id: FlashId,
+ phantom: PhantomData<&'a AreaDesc>,
+}
+
+impl<'a> Default for CArea<'a> {
+ fn default() -> CArea<'a> {
+ CArea {
+ areas: ptr::null(),
+ whole: Default::default(),
+ id: FlashId::BootLoader,
+ num_areas: 0,
+ phantom: PhantomData,
+ }
+ }
+}
+
+/// Flash area map.
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[allow(dead_code)]
+pub enum FlashId {
+ BootLoader = 0,
+ Image0 = 1,
+ Image1 = 2,
+ ImageScratch = 3,
+ Nffs = 4,
+ Core = 5,
+ RebootLog = 6
+}
+
+impl Default for FlashId {
+ fn default() -> FlashId {
+ FlashId::BootLoader
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Clone, Default)]
+pub struct FlashArea {
+ flash_id: FlashId,
+ device_id: u8,
+ pad16: u16,
+ off: u32,
+ size: u32,
+}
+
diff --git a/sim/src/c.rs b/sim/src/c.rs
new file mode 100644
index 0000000..7ddb21b
--- /dev/null
+++ b/sim/src/c.rs
@@ -0,0 +1,50 @@
+/// Interface wrappers to C API entering to the bootloader
+
+use area::AreaDesc;
+use flash::Flash;
+use libc;
+
+/// Invoke the bootloader on this flash device.
+pub fn boot_go(flash: &mut Flash, areadesc: &AreaDesc) -> i32 {
+ unsafe { raw::invoke_boot_go(flash as *mut _ as *mut libc::c_void,
+ &areadesc.get_c() as *const _) as i32 }
+}
+
+/// Setter/getter for the flash counter. This isn't thread safe.
+pub fn get_flash_counter() -> i32 {
+ unsafe { raw::flash_counter as i32 }
+}
+
+/// Set the flash counter. Zero indicates the flash should not be interrupted. The counter will
+/// then go negative for each flash operation.
+pub fn set_flash_counter(counter: i32) {
+ unsafe { raw::flash_counter = counter as libc::c_int };
+}
+
+pub fn boot_trailer_sz() -> u32 {
+ unsafe { raw::boot_trailer_sz(raw::sim_flash_align) }
+}
+
+pub fn get_sim_flash_align() -> u8 {
+ unsafe { raw::sim_flash_align }
+}
+
+pub fn set_sim_flash_align(align: u8) {
+ unsafe { raw::sim_flash_align = align };
+}
+
+mod raw {
+ use area::CAreaDesc;
+ use libc;
+
+ extern "C" {
+ // This generates a warning about `CAreaDesc` not being foreign safe. There doesn't appear to
+ // be any way to get rid of this warning. See https://github.com/rust-lang/rust/issues/34798
+ // for information and tracking.
+ pub fn invoke_boot_go(flash: *mut libc::c_void, areadesc: *const CAreaDesc) -> libc::c_int;
+ pub static mut flash_counter: libc::c_int;
+
+ pub static mut sim_flash_align: u8;
+ pub fn boot_trailer_sz(min_write_sz: u8) -> u32;
+ }
+}
diff --git a/sim/src/flash.rs b/sim/src/flash.rs
new file mode 100644
index 0000000..8cf267c
--- /dev/null
+++ b/sim/src/flash.rs
@@ -0,0 +1,230 @@
+//! A flash simulator
+//!
+//! This module is capable of simulating the type of NOR flash commonly used in microcontrollers.
+//! These generally can be written as individual bytes, but must be erased in larger units.
+
+use std::iter::Enumerate;
+use std::slice;
+use pdump::HexDump;
+
+error_chain! {
+ errors {
+ OutOfBounds(t: String) {
+ description("Offset is out of bounds")
+ display("Offset out of bounds: {}", t)
+ }
+ Write(t: String) {
+ description("Invalid write")
+ display("Invalid write: {}", t)
+ }
+ }
+}
+
+fn ebounds<T: AsRef<str>>(message: T) -> ErrorKind {
+ ErrorKind::OutOfBounds(message.as_ref().to_owned())
+}
+
+fn ewrite<T: AsRef<str>>(message: T) -> ErrorKind {
+ ErrorKind::Write(message.as_ref().to_owned())
+}
+
+/// An emulated flash device. It is represented as a block of bytes, and a list of the sector
+/// mapings.
+#[derive(Clone)]
+pub struct Flash {
+ data: Vec<u8>,
+ sectors: Vec<usize>,
+}
+
+impl Flash {
+ /// Given a sector size map, construct a flash device for that.
+ pub fn new(sectors: Vec<usize>) -> Flash {
+ let total = sectors.iter().sum();
+ Flash {
+ data: vec![0xffu8; total],
+ sectors: sectors,
+ }
+ }
+
+ /// The flash drivers tend to erase beyond the bounds of the given range. Instead, we'll be
+ /// strict, and make sure that the passed arguments are exactly at a sector boundary, otherwise
+ /// return an error.
+ pub fn erase(&mut self, offset: usize, len: usize) -> Result<()> {
+ let (_start, slen) = self.get_sector(offset).ok_or_else(|| ebounds("start"))?;
+ let (end, elen) = self.get_sector(offset + len - 1).ok_or_else(|| ebounds("end"))?;
+
+ if slen != 0 {
+ bail!(ebounds("offset not at start of sector"));
+ }
+ if elen != self.sectors[end] - 1 {
+ bail!(ebounds("end not at start of sector"));
+ }
+
+ for x in &mut self.data[offset .. offset + len] {
+ *x = 0xff;
+ }
+
+ Ok(())
+ }
+
+ /// Writes are fairly unconstrained, but we restrict to only allowing writes of values that
+ /// are entirely written as 0xFF.
+ pub fn write(&mut self, offset: usize, payload: &[u8]) -> Result<()> {
+ if offset + payload.len() > self.data.len() {
+ bail!(ebounds("Write outside of device"));
+ }
+
+ let mut sub = &mut self.data[offset .. offset + payload.len()];
+ if sub.iter().any(|x| *x != 0xFF) {
+ bail!(ewrite("Write to non-FF location"));
+ }
+
+ sub.copy_from_slice(payload);
+ Ok(())
+ }
+
+ /// Read is simple.
+ pub fn read(&self, offset: usize, data: &mut [u8]) -> Result<()> {
+ if offset + data.len() > self.data.len() {
+ bail!(ebounds("Read outside of device"));
+ }
+
+ let sub = &self.data[offset .. offset + data.len()];
+ data.copy_from_slice(sub);
+ Ok(())
+ }
+
+ // Scan the sector map, and return the base and offset within a sector for this given byte.
+ // Returns None if the value is outside of the device.
+ fn get_sector(&self, offset: usize) -> Option<(usize, usize)> {
+ let mut offset = offset;
+ for (sector, &size) in self.sectors.iter().enumerate() {
+ if offset < size {
+ return Some((sector, offset));
+ }
+ offset -= size;
+ }
+ return None;
+ }
+
+ /// An iterator over each sector in the device.
+ pub fn sector_iter(&self) -> SectorIter {
+ SectorIter {
+ iter: self.sectors.iter().enumerate(),
+ base: 0,
+ }
+ }
+
+ pub fn device_size(&self) -> usize {
+ self.data.len()
+ }
+
+ pub fn dump(&self) {
+ self.data.dump();
+ }
+}
+
+/// It is possible to iterate over the sectors in the device, each element returning this.
+#[derive(Debug)]
+pub struct Sector {
+ /// Which sector is this, starting from 0.
+ pub num: usize,
+ /// The offset, in bytes, of the start of this sector.
+ pub base: usize,
+ /// The length, in bytes, of this sector.
+ pub size: usize,
+}
+
+pub struct SectorIter<'a> {
+ iter: Enumerate<slice::Iter<'a, usize>>,
+ base: usize,
+}
+
+impl<'a> Iterator for SectorIter<'a> {
+ type Item = Sector;
+
+ fn next(&mut self) -> Option<Sector> {
+ match self.iter.next() {
+ None => None,
+ Some((num, &size)) => {
+ let base = self.base;
+ self.base += size;
+ Some(Sector {
+ num: num,
+ base: base,
+ size: size,
+ })
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{Flash, Error, ErrorKind, Result, Sector};
+
+ #[test]
+ fn test_flash() {
+ // NXP-style, uniform sectors.
+ let mut f1 = Flash::new(vec![4096usize; 256]);
+ test_device(&mut f1);
+
+ // STM style, non-uniform sectors
+ let mut f2 = Flash::new(vec![16 * 1024, 16 * 1024, 16 * 1024, 64 * 1024,
+ 128 * 1024, 128 * 1024, 128 * 1024]);
+ test_device(&mut f2);
+ }
+
+ fn test_device(flash: &mut Flash) {
+ let sectors: Vec<Sector> = flash.sector_iter().collect();
+
+ flash.erase(0, sectors[0].size).unwrap();
+ let flash_size = flash.device_size();
+ flash.erase(0, flash_size).unwrap();
+ assert!(flash.erase(0, sectors[0].size - 1).is_bounds());
+
+ // Verify that write and erase do something.
+ flash.write(0, &[0]).unwrap();
+ let mut buf = [0; 4];
+ flash.read(0, &mut buf).unwrap();
+ assert_eq!(buf, [0, 0xff, 0xff, 0xff]);
+
+ flash.erase(0, sectors[0].size).unwrap();
+ flash.read(0, &mut buf).unwrap();
+ assert_eq!(buf, [0xff; 4]);
+
+ // Program the first and last byte of each sector, verify that has been done, and then
+ // erase to verify the erase boundaries.
+ for sector in §ors {
+ let byte = [(sector.num & 127) as u8];
+ flash.write(sector.base, &byte).unwrap();
+ flash.write(sector.base + sector.size - 1, &byte).unwrap();
+ }
+
+ // Verify the above
+ let mut buf = Vec::new();
+ for sector in §ors {
+ let byte = (sector.num & 127) as u8;
+ buf.resize(sector.size, 0);
+ flash.read(sector.base, &mut buf).unwrap();
+ assert_eq!(buf.first(), Some(&byte));
+ assert_eq!(buf.last(), Some(&byte));
+ assert!(buf[1..buf.len()-1].iter().all(|&x| x == 0xff));
+ }
+ }
+
+ // Helper checks for the result type.
+ trait EChecker {
+ fn is_bounds(&self) -> bool;
+ }
+
+ impl<T> EChecker for Result<T> {
+
+ fn is_bounds(&self) -> bool {
+ match *self {
+ Err(Error(ErrorKind::OutOfBounds(_), _)) => true,
+ _ => false,
+ }
+ }
+ }
+}
diff --git a/sim/src/main.rs b/sim/src/main.rs
new file mode 100644
index 0000000..1f17f17
--- /dev/null
+++ b/sim/src/main.rs
@@ -0,0 +1,351 @@
+#[macro_use] extern crate log;
+extern crate env_logger;
+extern crate docopt;
+extern crate libc;
+extern crate rand;
+extern crate rustc_serialize;
+
+#[macro_use]
+extern crate error_chain;
+
+use docopt::Docopt;
+use rand::{Rng, SeedableRng, XorShiftRng};
+use rustc_serialize::{Decodable, Decoder};
+use std::mem;
+use std::slice;
+
+mod area;
+mod c;
+mod flash;
+pub mod api;
+mod pdump;
+
+use flash::Flash;
+use area::{AreaDesc, FlashId};
+
+const USAGE: &'static str = "
+Mcuboot simulator
+
+Usage:
+ bootsim sizes
+ bootsim run --device TYPE [--align SIZE]
+ bootsim (--help | --version)
+
+Options:
+ -h, --help Show this message
+ --version Version
+ --device TYPE MCU to simulate
+ Valid values: stm32f4, k64f
+ --align SIZE Flash write alignment
+";
+
+#[derive(Debug, RustcDecodable)]
+struct Args {
+ flag_help: bool,
+ flag_version: bool,
+ flag_device: Option<DeviceName>,
+ flag_align: Option<AlignArg>,
+ cmd_sizes: bool,
+ cmd_run: bool,
+}
+
+#[derive(Debug, RustcDecodable)]
+enum DeviceName { Stm32f4, K64f }
+
+#[derive(Debug)]
+struct AlignArg(u8);
+
+impl Decodable for AlignArg {
+ // Decode the alignment ourselves, to restrict it to the valid possible alignments.
+ fn decode<D: Decoder>(d: &mut D) -> Result<AlignArg, D::Error> {
+ let m = d.read_u8()?;
+ match m {
+ 1 | 2 | 4 | 8 => Ok(AlignArg(m)),
+ _ => Err(d.error("Invalid alignment")),
+ }
+ }
+}
+
+fn main() {
+ env_logger::init().unwrap();
+
+ let args: Args = Docopt::new(USAGE)
+ .and_then(|d| d.decode())
+ .unwrap_or_else(|e| e.exit());
+ // println!("args: {:#?}", args);
+
+ if args.cmd_sizes {
+ show_sizes();
+ return;
+ }
+
+ let (mut flash, areadesc) = match args.flag_device {
+ None => panic!("Missing mandatory argument"),
+ Some(DeviceName::Stm32f4) => {
+ // STM style flash. Large sectors, with a large scratch area.
+ let flash = Flash::new(vec![16 * 1024, 16 * 1024, 16 * 1024, 16 * 1024,
+ 64 * 1024,
+ 128 * 1024, 128 * 1024, 128 * 1024]);
+ let mut areadesc = AreaDesc::new(&flash);
+ areadesc.add_image(0x020000, 0x020000, FlashId::Image0);
+ areadesc.add_image(0x040000, 0x020000, FlashId::Image1);
+ areadesc.add_image(0x060000, 0x020000, FlashId::ImageScratch);
+ (flash, areadesc)
+ }
+ Some(DeviceName::K64f) => {
+ // NXP style flash. Small sectors, one small sector for scratch.
+ let flash = Flash::new(vec![4096; 128]);
+
+ let mut areadesc = AreaDesc::new(&flash);
+ areadesc.add_image(0x020000, 0x020000, FlashId::Image0);
+ areadesc.add_image(0x040000, 0x020000, FlashId::Image1);
+ areadesc.add_image(0x060000, 0x001000, FlashId::ImageScratch);
+ (flash, areadesc)
+ }
+ };
+
+ // println!("Areas: {:#?}", areadesc.get_c());
+
+ // Install the boot trailer signature, so that the code will start an upgrade.
+ let primary = install_image(&mut flash, 0x020000, 32779);
+
+ // Install an upgrade image.
+ let upgrade = install_image(&mut flash, 0x040000, 41922);
+
+ // Set an alignment, and position the magic value.
+ c::set_sim_flash_align(args.flag_align.map(|x| x.0).unwrap_or(1));
+ let trailer_size = c::boot_trailer_sz();
+
+ // Mark the upgrade as ready to install. (This looks like it might be a bug in the code,
+ // however.)
+ mark_upgrade(&mut flash, 0x060000 - trailer_size as usize);
+
+ let (fl2, total_count) = try_upgrade(&flash, &areadesc, None);
+ info!("First boot, count={}", total_count);
+ assert!(verify_image(&fl2, 0x020000, &upgrade));
+
+ let mut bad = 0;
+ // Let's try an image halfway through.
+ for i in 1 .. total_count {
+ info!("Try interruption at {}", i);
+ let (fl3, total_count) = try_upgrade(&flash, &areadesc, Some(i));
+ info!("Second boot, count={}", total_count);
+ if !verify_image(&fl3, 0x020000, &upgrade) {
+ warn!("FAIL at step {} of {}", i, total_count);
+ bad += 1;
+ }
+ if !verify_image(&fl3, 0x040000, &primary) {
+ warn!("Slot 1 FAIL at step {} of {}", i, total_count);
+ bad += 1;
+ }
+ }
+ error!("{} out of {} failed {:.2}%",
+ bad, total_count,
+ bad as f32 * 100.0 / total_count as f32);
+
+ info!("Try revert");
+ let fl2 = try_revert(&flash, &areadesc);
+ assert!(verify_image(&fl2, 0x020000, &primary));
+
+ info!("Try norevert");
+ let fl2 = try_norevert(&flash, &areadesc);
+ assert!(verify_image(&fl2, 0x020000, &upgrade));
+
+ /*
+ // show_flash(&flash);
+
+ println!("First boot for upgrade");
+ // c::set_flash_counter(570);
+ c::boot_go(&mut flash, &areadesc);
+ // println!("{} flash ops", c::get_flash_counter());
+
+ verify_image(&flash, 0x020000, &upgrade);
+
+ println!("\n------------------\nSecond boot");
+ c::boot_go(&mut flash, &areadesc);
+ */
+}
+
+/// Test a boot, optionally stopping after 'n' flash options. Returns a count of the number of
+/// flash operations done total.
+fn try_upgrade(flash: &Flash, areadesc: &AreaDesc, stop: Option<i32>) -> (Flash, i32) {
+ // Clone the flash to have a new copy.
+ let mut fl = flash.clone();
+
+ c::set_flash_counter(stop.unwrap_or(0));
+ let (first_interrupted, cnt1) = match c::boot_go(&mut fl, &areadesc) {
+ -0x13579 => (true, stop.unwrap()),
+ 0 => (false, -c::get_flash_counter()),
+ x => panic!("Unknown return: {}", x),
+ };
+ c::set_flash_counter(0);
+
+ if first_interrupted {
+ // fl.dump();
+ match c::boot_go(&mut fl, &areadesc) {
+ -0x13579 => panic!("Shouldn't stop again"),
+ 0 => (),
+ x => panic!("Unknown return: {}", x),
+ }
+ }
+
+ let cnt2 = cnt1 - c::get_flash_counter();
+
+ (fl, cnt2)
+}
+
+fn try_revert(flash: &Flash, areadesc: &AreaDesc) -> Flash {
+ let mut fl = flash.clone();
+ c::set_flash_counter(0);
+
+ assert_eq!(c::boot_go(&mut fl, &areadesc), 0);
+ assert_eq!(c::boot_go(&mut fl, &areadesc), 0);
+ fl
+}
+
+fn try_norevert(flash: &Flash, areadesc: &AreaDesc) -> Flash {
+ let mut fl = flash.clone();
+ c::set_flash_counter(0);
+ let align = c::get_sim_flash_align() as usize;
+
+ assert_eq!(c::boot_go(&mut fl, &areadesc), 0);
+ // Write boot_ok
+ fl.write(0x040000 - align, &[1]).unwrap();
+ assert_eq!(c::boot_go(&mut fl, &areadesc), 0);
+ fl
+}
+
+/// Show the flash layout.
+#[allow(dead_code)]
+fn show_flash(flash: &Flash) {
+ println!("---- Flash configuration ----");
+ for sector in flash.sector_iter() {
+ println!(" {:2}: 0x{:08x}, 0x{:08x}",
+ sector.num, sector.base, sector.size);
+ }
+ println!("");
+}
+
+/// Install a "program" into the given image. This fakes the image header, or at least all of the
+/// fields used by the given code. Returns a copy of the image that was written.
+fn install_image(flash: &mut Flash, offset: usize, len: usize) -> Vec<u8> {
+ let offset0 = offset;
+
+ // Generate a boot header. Note that the size doesn't include the header.
+ let header = ImageHeader {
+ magic: 0x96f3b83c,
+ tlv_size: 0,
+ _pad1: 0,
+ hdr_size: 32,
+ key_id: 0,
+ _pad2: 0,
+ img_size: len as u32,
+ flags: 0,
+ ver: ImageVersion {
+ major: 1,
+ minor: 0,
+ revision: 1,
+ build_num: 1,
+ },
+ _pad3: 0,
+ };
+
+ let b_header = header.as_raw();
+ /*
+ let b_header = unsafe { slice::from_raw_parts(&header as *const _ as *const u8,
+ mem::size_of::<ImageHeader>()) };
+ */
+ assert_eq!(b_header.len(), 32);
+ flash.write(offset, &b_header).unwrap();
+ let offset = offset + b_header.len();
+
+ // The core of the image itself is just pseudorandom data.
+ let mut buf = vec![0; len];
+ splat(&mut buf, offset);
+ flash.write(offset, &buf).unwrap();
+ let offset = offset + buf.len();
+
+ // Copy out the image so that we can verify that the image was installed correctly later.
+ let mut copy = vec![0u8; offset - offset0];
+ flash.read(offset0, &mut copy).unwrap();
+
+ copy
+}
+
+/// Verify that given image is present in the flash at the given offset.
+fn verify_image(flash: &Flash, offset: usize, buf: &[u8]) -> bool {
+ let mut copy = vec![0u8; buf.len()];
+ flash.read(offset, &mut copy).unwrap();
+
+ if buf != ©[..] {
+ for i in 0 .. buf.len() {
+ if buf[i] != copy[i] {
+ info!("First failure at {:#x}", offset + i);
+ break;
+ }
+ }
+ false
+ } else {
+ true
+ }
+}
+
+/// The image header
+#[repr(C)]
+pub struct ImageHeader {
+ magic: u32,
+ tlv_size: u16,
+ key_id: u8,
+ _pad1: u8,
+ hdr_size: u16,
+ _pad2: u16,
+ img_size: u32,
+ flags: u32,
+ ver: ImageVersion,
+ _pad3: u32,
+}
+
+impl AsRaw for ImageHeader {}
+
+#[repr(C)]
+pub struct ImageVersion {
+ major: u8,
+ minor: u8,
+ revision: u16,
+ build_num: u32,
+}
+
+/// Write out the magic so that the loader tries doing an upgrade.
+fn mark_upgrade(flash: &mut Flash, offset: usize) {
+ let magic = vec![0x77, 0xc2, 0x95, 0xf3,
+ 0x60, 0xd2, 0xef, 0x7f,
+ 0x35, 0x52, 0x50, 0x0f,
+ 0x2c, 0xb6, 0x79, 0x80];
+ flash.write(offset, &magic).unwrap();
+}
+
+// Drop some pseudo-random gibberish onto the data.
+fn splat(data: &mut [u8], seed: usize) {
+ let seed_block = [0x135782ea, 0x92184728, data.len() as u32, seed as u32];
+ let mut rng: XorShiftRng = SeedableRng::from_seed(seed_block);
+ rng.fill_bytes(data);
+}
+
+/// Return a read-only view into the raw bytes of this object
+trait AsRaw : Sized {
+ fn as_raw<'a>(&'a self) -> &'a [u8] {
+ unsafe { slice::from_raw_parts(self as *const _ as *const u8,
+ mem::size_of::<Self>()) }
+ }
+}
+
+fn show_sizes() {
+ // This isn't panic safe.
+ let old_align = c::get_sim_flash_align();
+ for min in &[1, 2, 4, 8] {
+ c::set_sim_flash_align(*min);
+ let msize = c::boot_trailer_sz();
+ println!("{:2}: {} (0x{:x})", min, msize, msize);
+ }
+ c::set_sim_flash_align(old_align);
+}
diff --git a/sim/src/pdump.rs b/sim/src/pdump.rs
new file mode 100644
index 0000000..dbb42d5
--- /dev/null
+++ b/sim/src/pdump.rs
@@ -0,0 +1,76 @@
+// Printable hexdump.
+
+pub trait HexDump {
+ // Output the data value in hex.
+ fn dump(&self);
+}
+
+struct Dumper {
+ hex: String,
+ ascii: String,
+ count: usize,
+ total_count: usize,
+}
+
+impl Dumper {
+ fn new() -> Dumper {
+ Dumper {
+ hex: String::with_capacity(49),
+ ascii: String::with_capacity(16),
+ count: 0,
+ total_count: 0,
+ }
+ }
+
+ fn add_byte(&mut self, ch: u8) {
+ if self.count == 16 {
+ self.ship();
+ }
+ if self.count == 8 {
+ self.hex.push(' ');
+ }
+ self.hex.push_str(&format!(" {:02x}", ch)[..]);
+ self.ascii.push(if ch >= ' ' as u8 && ch <= '~' as u8 {
+ ch as char
+ } else {
+ '.'
+ });
+ self.count += 1;
+ }
+
+ fn ship(&mut self) {
+ if self.count == 0 {
+ return;
+ }
+
+ println!("{:06x} {:-49} |{}|", self.total_count, self.hex, self.ascii);
+
+ self.hex.clear();
+ self.ascii.clear();
+ self.total_count += 16;
+ self.count = 0;
+ }
+}
+
+impl<'a> HexDump for &'a [u8] {
+ fn dump(&self) {
+ let mut dump = Dumper::new();
+ for ch in self.iter() {
+ dump.add_byte(*ch);
+ }
+ dump.ship();
+ }
+}
+
+impl HexDump for Vec<u8> {
+ fn dump(&self) {
+ (&self[..]).dump()
+ }
+}
+
+#[test]
+fn samples() {
+ "Hello".as_bytes().dump();
+ "This is a much longer string".as_bytes().dump();
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f".as_bytes().dump();
+}