Semihosting - The Embedded Rust Book

# Semihosting

Semihosting is a mechanism that lets embedded devices do I/O on the host and is mainly used to log messages to the host console. Semihosting requires a debug session and pretty much nothing else (no extra wires!) so it's super convenient to use. The downside is that it's super slow: each write operation can take several milliseconds depending on the hardware debugger (e.g. ST-Link) you use.

The `cortex-m-semihosting` crate provides an API to do semihosting operations on Cortex-M devices. The program below is the semihosting version of "Hello, world!":

    #![no_main]
    #![no_std]

    use panic_halt as _;

    use cortex_m_rt::entry;
    use cortex_m_semihosting::hprintln;

    #[entry]
    fn main() -> ! {
        hprintln!("Hello, world!").unwrap();

        loop {}
    }

If you run this program on hardware you'll see the "Hello, world!" message within the OpenOCD logs.

    $ openocd
    (..)
    Hello, world!
    (..)

You do need to enable semihosting in OpenOCD from GDB first:

    (gdb) monitor arm semihosting enable
    semihosting is enabled

QEMU understands semihosting operations so the above program will also work with `qemu-system-arm` without having to start a debug session. Note that you'll need to pass the `-semihosting-config` flag to QEMU to enable semihosting support; these flags are already included in the `.cargo/config` file of the template.

    $ # this program will block the terminal
    $ cargo run
         Running `qemu-system-arm (..)
    Hello, world!

There's also an `exit` semihosting operation that can be used to terminate the QEMU process. Important: do not use `debug::exit` on hardware; this function can corrupt your OpenOCD session and you will not be able to debug more programs until you restart it.

    #![no_main]
    #![no_std]

    use panic_halt as _;

    use cortex_m_rt::entry;
    use cortex_m_semihosting::debug;

    #[entry]
    fn main() -> ! {
        let roses = "blue";

        if roses == "red" {
            debug::exit(debug::EXIT_SUCCESS);
        } else {
            debug::exit(debug::EXIT_FAILURE);
        }

        loop {}
    }

    $ cargo run
         Running `qemu-system-arm (..)

    $ echo $?
    1

One last tip: you can set the panicking behavior to `exit(EXIT_FAILURE)`. This will let you write `no_std` run-pass tests that you can run on QEMU.

For convenience, the `panic-semihosting` crate has an "exit" feature that when enabled invokes `exit(EXIT_FAILURE)` after logging the panic message to the host stderr.

    #![no_main]
    #![no_std]

    use panic_semihosting as _; // features = ["exit"]

    use cortex_m_rt::entry;
    use cortex_m_semihosting::debug;

    #[entry]
    fn main() -> ! {
        let roses = "blue";

        assert_eq!(roses, "red");

        loop {}
    }

    $ cargo run
         Running `qemu-system-arm (..)
    panicked at 'assertion failed: `(left == right)`
      left: `"blue"`,
     right: `"red"`', examples/hello.rs:15:5

    $ echo $?
    1

NOTE: To enable this feature on `panic-semihosting`, edit your `Cargo.toml` dependencies section where `panic-semihosting` is specified with:

    panic-semihosting = { version = "VERSION", features = ["exit"] }

where `VERSION` is the version desired. For more information on dependencies features check the `specifying dependencies` section of the Cargo book.