Add a tool that prints information on the sizes of the examples.

The tool prints the size of .bss, .data, and .text (which currently includes .rodata). I tried to adjust the linker script to separate out .rodata, but unfortunately that caused RISC-V apps to fault. I'll take care of that in a later PR, when I refactor the entry point and layout script.

I intend to develop a GitHub Action similar to tock/tock's that runs print-sizes on incoming PRs and displays the diff relative to the PR's target branch.
diff --git a/Cargo.toml b/Cargo.toml
index 0016e7a..b8e0bec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,5 +62,6 @@
 members = [
     "codegen",
     "core",
-    "test-runner"
+    "test-runner",
+    "tools/print-sizes",
 ]
diff --git a/Makefile b/Makefile
index cf1a305..d205cc7 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,7 @@
 	@echo "    Set the FEATURES flag to enable features"
 	@echo "Run 'make flash-<board> EXAMPLE=<>' to flash EXAMPLE to that board"
 	@echo "Run 'make test' to test any local changes you have made"
+	@echo "Run 'make print-sizes' to print size data for the example binaries"
 
 ifdef FEATURES
 features=--features=$(FEATURES)
@@ -53,6 +54,11 @@
 	$(MAKE) -C tock/boards/hifive1 \
 		$(CURDIR)/tock/target/riscv32imac-unknown-none-elf/release/hifive1.elf
 
+# Prints out the sizes of the example binaries.
+.PHONY: print-sizes
+print-sizes: examples
+	cargo run --release -p print-sizes
+
 # Runs the libtock_test tests in QEMU on a simulated HiFive board.
 .PHONY: test-qemu-hifive
 test-qemu-hifive: kernel-hifive setup-qemu
@@ -62,11 +68,12 @@
 
 .PHONY: examples
 examples:
-	PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --examples
+	PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --examples -p libtock -p libtock-core
 	PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --examples --features=alloc
 	PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --example panic --features=custom_panic_handler,custom_alloc_error_handler
 	PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --example alloc_error --features=alloc,custom_alloc_error_handler
-	PLATFORM=opentitan cargo build --release --target=riscv32imc-unknown-none-elf --examples # Important: This is testing a platform without atomics support
+	# Important: This tests a platform without atomic instructions.
+	PLATFORM=opentitan cargo build --release --target=riscv32imc-unknown-none-elf --examples -p libtock -p libtock-core
 
 .PHONY: test
 test: examples test-qemu-hifive
diff --git a/core/examples/empty_main.rs b/core/examples/empty_main.rs
new file mode 100644
index 0000000..dd062bc
--- /dev/null
+++ b/core/examples/empty_main.rs
@@ -0,0 +1,14 @@
+// The most minimal libtock-core example possible. This file primarily exists
+// for code size measurement, as this should create the smallest-possible
+// libtock-core app.
+
+#![no_std]
+
+// If you don't *use* anything from libtock-core directly, cargo will not link
+// it into the executable. However, we still need the runtime and lang items.
+// Therefore a libtock-core app that doesn't directly mention anything in
+// libtock-core needs to explicitly declare its dependency on libtock-core as
+// follows.
+extern crate libtock_core;
+
+fn main() {}
diff --git a/tools/print-sizes/Cargo.toml b/tools/print-sizes/Cargo.toml
new file mode 100644
index 0000000..97bdc48
--- /dev/null
+++ b/tools/print-sizes/Cargo.toml
@@ -0,0 +1,13 @@
+# Finds all the libtock-core and libtock-rs examples and prints the sizes of
+# several of their sections. Searches the target/$ARCH/release directory. Note
+# that print-sizes will not build the examples; that is done by the
+# `print-sizes` Makefile action.
+
+[package]
+authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
+edition = "2018"
+name = "print-sizes"
+version = "0.1.0"
+
+[dependencies]
+elf = "0.0.10"
diff --git a/tools/print-sizes/src/main.rs b/tools/print-sizes/src/main.rs
new file mode 100644
index 0000000..dcd7777
--- /dev/null
+++ b/tools/print-sizes/src/main.rs
@@ -0,0 +1,142 @@
+// Architectures that we expect the examples to be built for.
+const ARCHITECTURES: [&str; 2] = ["riscv32imc-unknown-none-elf", "thumbv7em-none-eabi"];
+
+// The order of these fields actually matters, because it affects the derived
+// Ord impl. I have a suspicion that when I introduce size diffs into the CI,
+// this order will make the eventual diffs easier to understand than other
+// orderings.
+#[derive(Eq, PartialEq, PartialOrd, Ord)]
+struct Example {
+    name: String,
+    arch: &'static str,
+    path: std::path::PathBuf,
+}
+
+// Finds the example binaries and returns a list of their paths.
+fn find_examples() -> Vec<Example> {
+    // Find target/ using std::env::current_exe().
+    let exe_dir = std::env::current_exe().expect("Unable to find executable location");
+    let target_dir = exe_dir
+        .parent()
+        .expect("Unable to find target/ directory")
+        .parent()
+        .expect("Unable to find target/ directory");
+
+    let mut examples = Vec::new();
+
+    for arch in &ARCHITECTURES {
+        // Set examples_dir to target/$ARCH/examples/
+        let mut examples_dir = target_dir.to_path_buf();
+        examples_dir.push(arch);
+        examples_dir.push("release");
+        examples_dir.push("examples");
+
+        // If the architecture's examples directory exists, iterate through the
+        // files through it and search for examples. If the directory doesn't
+        // exist we skip this architecture.
+        if let Ok(read_dir) = examples_dir.read_dir() {
+            for file in read_dir.filter_map(Result::ok) {
+                use std::os::unix::ffi::OsStrExt;
+
+                // Skip entries that are not files. If file_type() returns
+                // Err(_) we skip the entry as well.
+                if !file.file_type().map_or(false, |t| t.is_file()) {
+                    continue;
+                }
+
+                // Skip files with dots (*.d files) and hyphens (-$HASH) in
+                // them.
+                if file.file_name().as_bytes().contains(&b'.')
+                    || file.file_name().as_bytes().contains(&b'-')
+                {
+                    continue;
+                }
+
+                examples.push(Example {
+                    name: file.file_name().to_string_lossy().into_owned(),
+                    arch,
+                    path: file.path(),
+                });
+            }
+        }
+    }
+
+    examples
+}
+
+struct ElfSizes {
+    bss: u64,
+    data: u64,
+    rodata: u64,
+    text: u64,
+}
+
+fn get_sizes(path: &std::path::Path) -> ElfSizes {
+    let file = elf::File::open_path(path).expect("Unable to open example binary");
+    let mut sizes = ElfSizes {
+        bss: 0,
+        data: 0,
+        rodata: 0,
+        text: 0,
+    };
+    for section in file.sections {
+        match section.shdr.name.as_ref() {
+            ".bss" => sizes.bss = section.shdr.size,
+            ".data" => sizes.data = section.shdr.size,
+            ".rodata" => sizes.rodata = section.shdr.size,
+            ".text" => sizes.text = section.shdr.size,
+            _ => {}
+        }
+    }
+    sizes
+}
+
+struct ExampleData {
+    name: String,
+    arch: &'static str,
+    sizes: ElfSizes,
+}
+
+fn main() {
+    let mut examples = find_examples();
+    examples.sort_unstable();
+    let example_data: Vec<_> = examples
+        .drain(..)
+        .map(|example| ExampleData {
+            name: example.name,
+            arch: example.arch,
+            sizes: get_sizes(&example.path),
+        })
+        .collect();
+
+    let name_width = 20;
+    let arch_width = example_data
+        .iter()
+        .map(|a| a.arch.len())
+        .max()
+        .expect("No examples found");
+    let section_width = 7;
+
+    // TODO: We do not currently print out .rodata's size. Currently, the linker
+    // script embeds .rodata in .text, so we don't see it as a separate section
+    // here. We should modify the linker script to put .rodata in its own
+    // section. Until that is done, .rodata's size will be counted as part of
+    // .text, so we'll just print .text's size for now.
+    println!(
+        "{0:1$} {2:3$} {4:>7$} {5:>7$} {6:>7$}",
+        "Example", name_width, "Architecture", arch_width, ".bss", ".data", ".text", section_width
+    );
+    for data in example_data {
+        println!(
+            "{0:1$} {2:3$} {4:7$} {5:7$} {6:7$}",
+            data.name,
+            name_width,
+            data.arch,
+            arch_width,
+            data.sizes.bss,
+            data.sizes.data,
+            data.sizes.text,
+            section_width
+        );
+    }
+}