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 &sectors {
+            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 &sectors {
+            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 != &copy[..] {
+        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();
+}