Skip to content

Add a runner for libtock2 examples. #365

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cargo/config
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ rustflags = [
"-C", "relocation-model=static",
"-C", "link-arg=-Tlayout.ld",
]
runner = "./tools/flash.sh"
runner = ["cargo", "run", "-p", "runner", "--release"]
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- name: Build and Test
run: |
sudo apt-get install binutils-arm-none-eabi \
binutils-riscv64-unknown-elf
binutils-riscv64-unknown-elf ninja-build
cd "${GITHUB_WORKSPACE}"
echo "[target.'cfg(all())']" >> .cargo/config
echo 'rustflags = ["-D", "warnings"]' >> .cargo/config
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,15 @@ lto = true
debug = true

[workspace]
exclude = [ "tock" ]
exclude = ["tock", "tock2"]
members = [
"apis/low_level_debug",
"codegen",
"core",
"libtock2",
"panic_handlers/small_panic",
"platform",
"runner",
"runtime",
"syscalls_tests",
"test_runner",
Expand Down
28 changes: 25 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ usage:
@echo " Set the DEBUG flag to enable the debug build"
@echo " Set the FEATURES flag to enable features"
@echo "Run 'make flash-<board> EXAMPLE=<>' to flash EXAMPLE to that board"
@echo "Run 'make qemu-example EXAMPLE=<>' to run EXAMPLE in QEMU"
@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"

Expand All @@ -38,7 +39,7 @@ release=--release
endif

.PHONY: setup
setup: setup-qemu
setup: setup-qemu setup-qemu-2
cargo install elf2tab
cargo install stack-sizes
cargo miri setup
Expand All @@ -49,6 +50,12 @@ setup: setup-qemu
setup-qemu:
CI=true $(MAKE) -C tock ci-setup-qemu

# Sets up QEMU in the tock2/ directory. We use Tock's QEMU which may contain
# patches to better support boards that Tock supports.
.PHONY: setup-qemu-2
setup-qemu-2:
CI=true $(MAKE) -C tock2 ci-setup-qemu

# Builds a Tock kernel for the HiFive board for use by QEMU tests.
.PHONY: kernel-hifive
kernel-hifive:
Expand All @@ -63,11 +70,25 @@ kernel-hifive-2:
$(MAKE) -C tock2/boards/hifive1 \
$(CURDIR)/tock2/target/riscv32imac-unknown-none-elf/release/hifive1.elf

# Builds a Tock kernel for the OpenTitan board on the cw310 FPGA for use by QEMU
# tests.
.PHONY: kernel-opentitan
kernel-opentitan:
CARGO_TARGET_RISCV32IMC_UNKNOWN_NONE_ELF_RUNNER="[]" \
$(MAKE) -C tock2/boards/opentitan/earlgrey-cw310 \
$(CURDIR)/tock2/target/riscv32imc-unknown-none-elf/release/earlgrey-cw310.elf

# Prints out the sizes of the example binaries.
.PHONY: print-sizes
print-sizes: examples
cargo run --release -p print_sizes

# Runs a libtock2 example in QEMU on a simulated HiFive board.
.PHONY: qemu-example
qemu-example: kernel-hifive-2
LIBTOCK_PLATFORM="hifive1" cargo run --example "$(EXAMPLE)" -p libtock2 \
--release --target=riscv32imac-unknown-none-elf -- --deploy qemu

# Runs the libtock_test tests in QEMU on a simulated HiFive board.
.PHONY: test-qemu-hifive
test-qemu-hifive: kernel-hifive
Expand Down Expand Up @@ -100,7 +121,7 @@ EXCLUDE_MIRI := $(EXCLUDE_RUNTIME) --exclude libtock_codegen \
# Arguments to pass to cargo to exclude `std` and crates that depend on it. Used
# when we build a crate for an embedded target, as those targets lack `std`.
EXCLUDE_STD := --exclude libtock_unittest --exclude print_sizes \
--exclude syscalls_tests --exclude test_runner
--exclude runner --exclude syscalls_tests --exclude test_runner

# Some of our crates should build with a stable toolchain. This verifies those
# crates don't depend on unstable features by using cargo check. We specify a
Expand All @@ -112,7 +133,7 @@ test-stable:
$(EXCLUDE_RUNTIME) --exclude libtock --exclude libtock_core

.PHONY: test
test: examples test-qemu-hifive test-stable
test: examples test-stable
PLATFORM=nrf52 cargo test $(EXCLUDE_RUNTIME) --workspace
# TODO: When we have a working embedded test harness, change the libtock2
# builds to --all-targets rather than --examples.
Expand Down Expand Up @@ -238,3 +259,4 @@ flash-msp432:
clean:
cargo clean
$(MAKE) -C tock clean
$(MAKE) -C tock2 clean
15 changes: 15 additions & 0 deletions runner/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
authors = ["Tock Project Developers <[email protected]>"]
description = """Tool used to run libtock-rs process binaries."""
edition = "2018"
license = "Apache-2.0 OR MIT"
name = "runner"
publish = false
repository = "https://www.github.com/tock/libtock-rs"
version = "0.1.0"

[dependencies]
clap = { features = ["derive"], version = "3.0.10" }
elf = "0.0.10"
libc = "0.2.113"
termion = "1.5.6"
113 changes: 113 additions & 0 deletions runner/src/elf2tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use super::Cli;
use std::fs::{metadata, remove_file};
use std::io::ErrorKind;
use std::path::PathBuf;
use std::process::Command;

// Converts the ELF file specified on the command line into TBF and TAB files,
// and returns the paths to those files.
pub fn convert_elf(cli: &Cli) -> OutFiles {
let package_name = cli.elf.file_stem().expect("ELF must be a file");
let mut tab_path = cli.elf.clone();
tab_path.set_extension("tab");
let protected_size = TBF_HEADER_SIZE.to_string();
if cli.verbose {
println!("Package name: {:?}", package_name);
println!("TAB path: {}", tab_path.display());
println!("Protected region size: {}", protected_size);
}
let stack_size = read_stack_size(cli);
let elf = cli.elf.as_os_str();
let mut tbf_path = cli.elf.clone();
tbf_path.set_extension("tbf");
if cli.verbose {
println!("ELF file: {:?}", elf);
println!("TBF path: {}", tbf_path.display());
}

// If elf2tab returns a successful status but does not write to the TBF
// file, then we run the risk of using an outdated TBF file, creating a
// hard-to-debug situation. Therefore, we delete the TBF file, forcing
// elf2tab to create it, and later verify that it exists.
if let Err(io_error) = remove_file(&tbf_path) {
// Ignore file-no-found errors, panic on any other error.
if io_error.kind() != ErrorKind::NotFound {
panic!("Unable to remove the TBF file. Error: {}", io_error);
}
}

let mut command = Command::new("elf2tab");
#[rustfmt::skip]
command.args([
// TODO: libtock-rs' crates are designed for Tock 2.1's Allow interface,
// so we should increment this as soon as the Tock kernel will accept a
// 2.1 app.
"--kernel-major".as_ref(), "2".as_ref(),
"--kernel-minor".as_ref(), "0".as_ref(),
"-n".as_ref(), package_name,
"-o".as_ref(), tab_path.as_os_str(),
"--protected-region-size".as_ref(), protected_size.as_ref(),
"--stack".as_ref(), stack_size.as_ref(),
elf,
]);
if cli.verbose {
command.arg("-v");
println!("elf2tab command: {:?}", command);
println!("Spawning elf2tab");
}
let mut child = command.spawn().expect("failed to spawn elf2tab");
let status = child.wait().expect("failed to wait for elf2tab");
if cli.verbose {
println!("elf2tab finished. {}", status);
}
assert!(status.success(), "elf2tab returned an error. {}", status);

// Verify that elf2tab created the TBF file, and that it is a file.
match metadata(&tbf_path) {
Err(io_error) => {
if io_error.kind() == ErrorKind::NotFound {
panic!("elf2tab did not create {}", tbf_path.display());
}
panic!(
"Unable to query metadata for {}: {}",
tbf_path.display(),
io_error
);
}
Ok(metadata) => {
assert!(metadata.is_file(), "{} is not a file", tbf_path.display());
}
}

OutFiles { tab_path, tbf_path }
}

// Paths to the files output by elf2tab.
pub struct OutFiles {
pub tab_path: PathBuf,
pub tbf_path: PathBuf,
}

// The amount of space to reserve for the TBF header. This must match the
// TBF_HEADER_SIZE value in the layout file for the platform, which is currently
// 0x48 for all platforms.
const TBF_HEADER_SIZE: u32 = 0x48;

// Reads the stack size, and returns it as a String for use on elf2tab's command
// line.
fn read_stack_size(cli: &Cli) -> String {
let file = elf::File::open_path(&cli.elf).expect("Unable to open ELF");
for section in file.sections {
// This section name comes from runtime/libtock_layout.ld, and it
// matches the size (and location) of the process binary's stack.
if section.shdr.name == ".stack" {
let stack_size = section.shdr.size.to_string();
if cli.verbose {
println!("Found .stack section, size: {}", stack_size);
}
return stack_size;
}
}

panic!("Unable to find the .stack section in {}", cli.elf.display());
}
57 changes: 57 additions & 0 deletions runner/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
mod elf2tab;
mod output_processor;
mod qemu;
mod tockloader;

use clap::{ArgEnum, Parser};
use std::env::{var, VarError};
use std::path::PathBuf;

/// Converts ELF binaries into Tock Binary Format binaries and runs them on a
/// Tock system.
#[derive(Debug, Parser)]
pub struct Cli {
/// Where to deploy the process binary. If not specified, runner will only
/// make a TBF file and not attempt to run it.
#[clap(arg_enum, long, short)]
deploy: Option<Deploy>,

/// The executable to convert into Tock Binary Format and run.
elf: PathBuf,

/// Whether to output verbose debugging information to the console.
#[clap(long, short)]
verbose: bool,
}

#[derive(ArgEnum, Clone, Copy, Debug)]
pub enum Deploy {
Qemu,
Tockloader,
}

fn main() {
let cli = Cli::parse();
let paths = elf2tab::convert_elf(&cli);
let deploy = match cli.deploy {
None => return,
Some(deploy) => deploy,
};
let platform = match var("LIBTOCK_PLATFORM") {
Err(VarError::NotPresent) => {
panic!("LIBTOCK_PLATFORM must be specified to deploy")
}
Err(VarError::NotUnicode(platform)) => {
panic!("Non-UTF-8 LIBTOCK_PLATFORM value: {:?}", platform)
}
Ok(platform) => platform,
};
if cli.verbose {
println!("Detected platform {}", platform);
}
let child = match deploy {
Deploy::Qemu => qemu::deploy(&cli, platform, paths.tbf_path),
Deploy::Tockloader => tockloader::deploy(&cli, platform, paths.tab_path),
};
output_processor::process(&cli, child);
}
Loading