瀏覽代碼

Merge upstream cortex-m-semihosting.

Jonathan Perkin 3 年之前
父節點
當前提交
4e8ad013fd
共有 8 個文件被更改,包括 431 次插入127 次删除
  1. 101 5
      CHANGELOG.md
  2. 14 1
      Cargo.toml
  3. 18 2
      README.md
  4. 2 4
      src/debug.rs
  5. 51 0
      src/export.rs
  6. 18 34
      src/hio.rs
  7. 131 76
      src/lib.rs
  8. 96 5
      src/macros.rs

+ 101 - 5
CHANGELOG.md

@@ -5,6 +5,91 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
 
+## [v0.4.1] - 2020-10-20
+
+0.4.1 was yanked because the pre-built binaries contain conflicting symbols
+with a supported version of cortex-m.
+
+- Fix missing prebuilt binaries (#271)
+
+## [v0.4.0] - 2020-10-14
+
+v0.4.0 was yanked because it did not include the required pre-built binaries
+in the final crate.
+
+- Moved into cortex-m repository
+- Merge `HStdout` and `HStderr` into one type: `HostStream`
+- Support cortex-m v0.7
+- Semihosting macros no longer return a Result, instead errors are ignored.
+
+## [v0.3.7] - 2020-12-02
+
+- Replaces the yanked v0.3.6 by reverting #48, so the semihosting macros
+  continue to return a Result.
+
+## [v0.3.6] - 2020-12-01
+
+v0.3.6 was yanked because it incorrectly included #48, which was a breaking
+change.
+
+### Added
+
+- Update cortex-m dependency to support version 0.7.
+- Add `no-semihosting` feature to disable all semihosting calls.
+
+## [v0.3.5] - 2019-08-29
+
+### Added
+
+- Adds a feature to work around JLink quirks
+- Adds a dbg! macro using heprintln
+- Added thumbv8m.main support on stable
+
+### Fixed
+
+- Now Rust 2018 edition
+
+## [v0.3.4] - 2019-08-13
+
+### Fixed
+
+- Support for thumbv8 mainline hf target
+
+## [v0.3.3] - 2019-04-22
+
+### Added
+
+- Adds support for thumbv8 and cortex-m v0.6.0
+
+## [v0.3.2] - 2018-11-04
+
+### Added
+
+- Added a family of `hprint` macros for printing to the host standard output /
+  error via globally shared `HStdout` / `HStderr` handles .
+
+## [v0.3.1] - 2018-08-27
+
+### Changed
+
+- This crate no longer depends on `arm-none-eabi-gcc`.
+
+## [v0.3.0] - 2018-05-10
+
+### Changed
+
+- [breaking-change] `inline-asm` is no longer a default feature (i.e. a feature that's enabled by
+  default). The consequence is that this crate now compiles on 1.27 (beta) by default, and opting
+  into `inline-asm` requires nightly.
+
+## [v0.2.1] - 2018-04-25
+
+### Added
+
+- An opt-out "inline-asm" Cargo feature. When this feature is disabled semihosting is implemented
+  using an external assembly file instead of using the unstable inline assembly (`asm!`) feature
+  meaning that this crate can be compiled on stable.
+
 ## [v0.2.0] - 2017-07-07
 
 ### Added
@@ -56,8 +141,19 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 - Initial release
 
-[Unreleased]: https://github.com/japaric/cortex-m-semihosting/compare/v0.2.0...HEAD
-[v0.2.0]: https://github.com/japaric/cortex-m-semihosting/compare/v0.1.3...v0.2.0
-[v0.1.3]: https://github.com/japaric/cortex-m-semihosting/compare/v0.1.2...v0.1.3
-[v0.1.2]: https://github.com/japaric/cortex-m-semihosting/compare/v0.1.1...v0.1.2
-[v0.1.1]: https://github.com/japaric/cortex-m-semihosting/compare/v0.1.0...v0.1.1
+[Unreleased]: https://github.com/rust-embedded/cortex-m/compare/c-m-sh-v0.4.1...HEAD
+[v0.4.1]: https://github.com/rust-embedded/cortex-m/compare/c-m-sh-v0.4.0...c-m-sh-v0.4.1
+[v0.4.0]: https://github.com/rust-embedded/cortex-m/compare/c-m-sh-v0.3.5...c-m-sh-v0.4.0
+[v0.3.7]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.6...v0.3.7
+[v0.3.6]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.5...v0.3.6
+[v0.3.5]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.4...v0.3.5
+[v0.3.4]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.3...v0.3.4
+[v0.3.3]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.2...v0.3.3
+[v0.3.2]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.1...v0.3.2
+[v0.3.1]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.0...v0.3.1
+[v0.3.0]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.2.1...v0.3.0
+[v0.2.1]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.2.0...v0.2.1
+[v0.2.0]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.1.3...v0.2.0
+[v0.1.3]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.1.2...v0.1.3
+[v0.1.2]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.1.1...v0.1.2
+[v0.1.1]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.1.0...v0.1.1

+ 14 - 1
Cargo.toml

@@ -1,11 +1,24 @@
 [package]
-authors = ["Jorge Aparicio <japaricious@gmail.com>"]
+authors = [
+    "The Cortex-M Team <cortex-m@teams.rust-embedded.org>",
+    "Jorge Aparicio <japaricious@gmail.com>",
+]
 description = "Semihosting for RISCV processors"
 documentation = "https://docs.rs/riscv-semihosting"
 keywords = ["semihosting", "riscv"]
+categories = ["no-std", "embedded"]
 license = "MIT OR Apache-2.0"
 name = "riscv-semihosting"
+readme = "README.md"
 repository = "https://github.com/riscv-rust/riscv-semihosting"
 version = "0.0.1"
+edition = "2018"
+
+[features]
+default = ["inline-asm", "jlink-quirks"]
+inline-asm = []
+jlink-quirks = []
+no-semihosting = []
 
 [dependencies]
+riscv = "0.7.0"

+ 18 - 2
README.md

@@ -5,15 +5,22 @@
 
 > Semihosting for ARM Cortex-M processors
 
+This project is developed and maintained by the [Cortex-M team][team].
+
 ## [Documentation](https://docs.rs/cortex-m-semihosting)
 
+# Minimum Supported Rust Version (MSRV)
+
+This crate is guaranteed to compile on stable Rust 1.33.0 and up. It *might*
+compile with older versions but that may change in any new patch release.
+
 ## License
 
 Licensed under either of
 
-- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or
   http://www.apache.org/licenses/LICENSE-2.0)
-- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT)
 
 at your option.
 
@@ -22,3 +29,12 @@ at your option.
 Unless you explicitly state otherwise, any contribution intentionally submitted
 for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
 dual licensed as above, without any additional terms or conditions.
+
+## Code of Conduct
+
+Contribution to this crate is organized under the terms of the [Rust Code of
+Conduct][CoC], the maintainer of this crate, the [Cortex-M team][team], promises
+to intervene to uphold that code of conduct.
+
+[CoC]: ../CODE_OF_CONDUCT.md
+[team]: https://github.com/rust-embedded/wg#the-cortex-m-team

+ 2 - 4
src/debug.rs

@@ -8,10 +8,8 @@
 //!
 //! Target program:
 //!
-//! ```
-//! #[macro_use]
-//! extern crate cortex_m_semihosting;
-//! use cortex_m_semihosting::debug::{self, EXIT_SUCCESS, EXIT_FAILURE};
+//! ```no_run
+//! use riscv_semihosting::debug::{self, EXIT_SUCCESS, EXIT_FAILURE};
 //!
 //! fn main() {
 //!     if 2 == 2 {

+ 51 - 0
src/export.rs

@@ -0,0 +1,51 @@
+//! IMPLEMENTATION DETAILS USED BY MACROS
+
+use core::fmt::{self, Write};
+
+use riscv::interrupt;
+
+use crate::hio::{self, HostStream};
+
+static mut HSTDOUT: Option<HostStream> = None;
+
+pub fn hstdout_str(s: &str) {
+    let _result = interrupt::free(|_| unsafe {
+        if HSTDOUT.is_none() {
+            HSTDOUT = Some(hio::hstdout()?);
+        }
+
+        HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop)
+    });
+}
+
+pub fn hstdout_fmt(args: fmt::Arguments) {
+    let _result = interrupt::free(|_| unsafe {
+        if HSTDOUT.is_none() {
+            HSTDOUT = Some(hio::hstdout()?);
+        }
+
+        HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop)
+    });
+}
+
+static mut HSTDERR: Option<HostStream> = None;
+
+pub fn hstderr_str(s: &str) {
+    let _result = interrupt::free(|_| unsafe {
+        if HSTDERR.is_none() {
+            HSTDERR = Some(hio::hstderr()?);
+        }
+
+        HSTDERR.as_mut().unwrap().write_str(s).map_err(drop)
+    });
+}
+
+pub fn hstderr_fmt(args: fmt::Arguments) {
+    let _result = interrupt::free(|_| unsafe {
+        if HSTDERR.is_none() {
+            HSTDERR = Some(hio::hstderr()?);
+        }
+
+        HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop)
+    });
+}

+ 18 - 34
src/hio.rs

@@ -1,64 +1,46 @@
 //! Host I/O
 
+use crate::nr;
 use core::{fmt, slice};
-use nr;
 
-/// Host's standard error
-pub struct HStderr {
+/// A byte stream to the host (e.g., host's stdout or stderr).
+#[derive(Clone, Copy)]
+pub struct HostStream {
     fd: usize,
 }
 
-impl HStderr {
+impl HostStream {
     /// Attempts to write an entire `buffer` into this sink
     pub fn write_all(&mut self, buffer: &[u8]) -> Result<(), ()> {
         write_all(self.fd, buffer)
     }
 }
 
-impl fmt::Write for HStderr {
-    fn write_str(&mut self, s: &str) -> fmt::Result {
-        self.write_all(s.as_bytes()).map_err(|_| fmt::Error)
-    }
-}
-
-/// Host's standard output
-pub struct HStdout {
-    fd: usize,
-}
-
-impl HStdout {
-    /// Attempts to write an entire `buffer` into this sink
-    pub fn write_all(&mut self, buffer: &[u8]) -> Result<(), ()> {
-        write_all(self.fd, buffer)
-    }
-}
-
-impl fmt::Write for HStdout {
+impl fmt::Write for HostStream {
     fn write_str(&mut self, s: &str) -> fmt::Result {
         self.write_all(s.as_bytes()).map_err(|_| fmt::Error)
     }
 }
 
 /// Construct a new handle to the host's standard error.
-pub fn hstderr() -> Result<HStderr, ()> {
+pub fn hstderr() -> Result<HostStream, ()> {
     // There is actually no stderr access in ARM Semihosting documentation. Use
     // convention used in libgloss.
     // See: libgloss/arm/syscalls.c, line 139.
     // https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=libgloss/arm/syscalls.c#l139
-    open(":tt\0", nr::open::W_APPEND).map(|fd| HStderr { fd })
+    open(":tt\0", nr::open::W_APPEND)
 }
 
 /// Construct a new handle to the host's standard output.
-pub fn hstdout() -> Result<HStdout, ()> {
-    open(":tt\0", nr::open::W_TRUNC).map(|fd| HStdout { fd })
+pub fn hstdout() -> Result<HostStream, ()> {
+    open(":tt\0", nr::open::W_TRUNC)
 }
 
-fn open(name: &str, mode: usize) -> Result<usize, ()> {
+fn open(name: &str, mode: usize) -> Result<HostStream, ()> {
     let name = name.as_bytes();
-    match unsafe { syscall!(OPEN, name.as_ptr(), mode, name.len() - 1) } as
-        isize {
+    match unsafe { syscall!(OPEN, name.as_ptr(), mode, name.len() - 1) } as isize {
         -1 => Err(()),
-        fd => Ok(fd as usize),
+        fd => Ok(HostStream { fd: fd as usize }),
     }
 }
 
@@ -70,10 +52,12 @@ fn write_all(fd: usize, mut buffer: &[u8]) -> Result<(), ()> {
             // `n` bytes were not written
             n if n <= buffer.len() => {
                 let offset = (buffer.len() - n) as isize;
-                buffer = unsafe {
-                    slice::from_raw_parts(buffer.as_ptr().offset(offset), n)
-                }
+                buffer = unsafe { slice::from_raw_parts(buffer.as_ptr().offset(offset), n) }
             }
+            #[cfg(feature = "jlink-quirks")]
+            // Error (-1) - should be an error but JLink can return -1, -2, -3,...
+            // For good measure, we allow up to negative 15.
+            n if n > 0xfffffff0 => return Ok(()),
             // Error
             _ => return Err(()),
         }

+ 131 - 76
src/lib.rs

@@ -2,40 +2,46 @@
 //!
 //! # What is semihosting?
 //!
-//! "Semihosting is a mechanism that enables code running on a RISCV target to
-//!  communicate and use the Input/Output facilities on a host computer that is
-//!  running a debugger." - RISCV
+//! "Semihosting is a mechanism that enables code running on an ARM target to communicate and use
+//! the Input/Output facilities on a host computer that is running a debugger." - ARM
 //!
 //! # Interface
 //!
-//! Since semihosting operations are modeled as [system calls][sc], this crate
-//! exposes an untyped `syscall!` interface just like the [`sc`] crate does.
+//! This crate provides implementations of
+//! [`core::fmt::Write`](https://doc.rust-lang.org/core/fmt/trait.Write.html), so you can use it,
+//! in conjunction with
+//! [`core::format_args!`](https://doc.rust-lang.org/core/macro.format_args.html) or the [`write!` macro](https://doc.rust-lang.org/core/macro.write.html), for user-friendly construction and printing of formatted strings.
+//!
+//! Since semihosting operations are modeled as [system calls][sc], this crate exposes an untyped
+//! `syscall!` interface just like the [`sc`] crate does.
 //!
 //! [sc]: https://en.wikipedia.org/wiki/System_call
 //! [`sc`]: https://crates.io/crates/sc
 //!
 //! # Forewarning
 //!
-//! Semihosting operations are *very* slow. Like, each WRITE operation can take
-//! hundreds of milliseconds.
+//! Semihosting operations are *very* slow. Like, each WRITE operation can take hundreds of
+//! milliseconds.
 //!
 //! # Example
 //!
-//! This example will show how to print "Hello, world!" on the host.
+//! ## Using `hio::hstdout`
 //!
-//! Target program:
+//! This example will demonstrate how to print formatted strings.
 //!
-//! ```
-//! #[macro_use]
-//! extern crate riscv_semihosting;
+//! ```no_run
+//! use riscv_semihosting::hio;
+//! use core::fmt::Write;
 //!
-//! fn main() {
-//!     // File descriptor (on the host)
-//!     const STDOUT: usize = 1; // NOTE the host stdout may not always be fd 1
-//!     static MSG: &'static [u8] = b"Hello, world!\n";
+//! // This function will be called by the application
+//! fn print() -> Result<(), core::fmt::Error> {
+//!     let mut stdout = hio::hstdout().map_err(|_| core::fmt::Error)?;
+//!     let language = "Rust";
+//!     let ranking = 1;
 //!
-//!     // Signature: fn write(fd: usize, ptr: *const u8, len: usize) -> usize
-//!     let r = unsafe { syscall!(WRITE, STDOUT, MSG.as_ptr(), MSG.len()) };
+//!     write!(stdout, "{} on embedded is #{}!", language, ranking)?;
+//!
+//!     Ok(())
 //! }
 //! ```
 //!
@@ -50,8 +56,8 @@
 //! # the command will block at this point
 //! ```
 //!
-//! The OpenOCD logs will be redirected to `/tmp/openocd.log`. You can view
-//! those logs in "real time" using `tail`
+//! The OpenOCD logs will be redirected to `/tmp/openocd.log`. You can view those logs in "real
+//! time" using `tail`
 //!
 //! ``` text
 //! $ tail -f /tmp/openocd.log
@@ -63,24 +69,23 @@
 //! Info : nrf51.cpu: hardware has 4 breakpoints, 2 watchpoints
 //! ```
 //!
-//! Alternatively you could omit the `-l` flag from the `openocd` call, and the
-//! `tail -f` command but the OpenOCD output will have intermingled in it logs
-//! from its normal operation.
+//! Alternatively you could omit the `-l` flag from the `openocd` call, and the `tail -f` command
+//! but the OpenOCD output will have intermingled in it logs from its normal operation.
 //!
 //! Then, we run the program:
 //!
 //! ``` text
 //! $ arm-none-eabi-gdb hello-world
-//! # Connect to OpenOCD
+//! (gdb) # Connect to OpenOCD
 //! (gdb) target remote :3333
 //!
-//! # Enable OpenOCD's semihosting support
+//! (gdb) # Enable OpenOCD's semihosting support
 //! (gdb) monitor arm semihosting enable
 //!
-//! # Flash the program
+//! (gdb) # Flash the program
 //! (gdb) load
 //!
-//! # Run the program
+//! (gdb) # Run the program
 //! (gdb) continue
 //! ```
 //!
@@ -89,9 +94,82 @@
 //! ``` text
 //! # openocd -f $INTERFACE -f $TARGET -l /tmp/openocd.log
 //! (..)
-//! Hello, world!
+//! Rust on embedded is #1!
+//! ```
+//! ## Using the syscall interface
+//!
+//! This example will show how to print "Hello, world!" on the host.
+//!
+//! Target program:
+//!
+//! ```no_run
+//! use riscv_semihosting::syscall;
+//!
+//! // This function will be called by the application
+//! fn print() {
+//!     // File descriptor (on the host)
+//!     const STDOUT: usize = 1; // NOTE the host stdout may not always be fd 1
+//!     static MSG: &'static [u8] = b"Hello, world!\n";
+//!
+//!     // Signature: fn write(fd: usize, ptr: *const u8, len: usize) -> usize
+//!     let r = unsafe { syscall!(WRITE, STDOUT, MSG.as_ptr(), MSG.len()) };
+//! }
+//! ```
+//! Output and monitoring proceed as in the above example.
+//!
+//! ## The `dbg!` macro
+//!
+//! Analogous to [`std::dbg`](https://doc.rust-lang.org/std/macro.dbg.html) the macro
+//! `dbg!` returns a given expression and prints it using `heprintln!` including context
+//! for quick and dirty debugging.
+//!
+//! Panics if `heprintln!` returns an error.
+//!
+//! Example:
+//!
+//! ```no_run
+//! const UUID: *mut u32 = 0x0009_FC70 as *mut u32;
+//! dbg!(UUID);
+//! let mut uuid: [u32; 4] = [0; 4];
+//! for i in 0..4 {
+//!     dbg!(i);
+//!     uuid[i] = unsafe { dbg!(UUID.offset(i as isize).read_volatile()) };
+//! }
+//! ```
+//! outputs
+//! ```text
+//! [examples/semihosting.rs:37] UUID = 0x0009fc70
+//! [examples/semihosting.rs:40] i = 0
+//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 3370045464
+//! [examples/semihosting.rs:40] i = 1
+//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 1426218275
+//! [examples/semihosting.rs:40] i = 2
+//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 2422621116
+//! [examples/semihosting.rs:40] i = 3
+//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 1044138593
 //! ```
 //!
+//! # Optional features
+//!
+//! ## `inline-asm`
+//!
+//! When this feature is enabled semihosting is implemented using inline assembly (`llvm_asm!`) and
+//! compiling this crate requires nightly.
+//!
+//! When this feature is disabled semihosting is implemented using FFI calls into an external
+//! assembly file and compiling this crate works on stable and beta.
+//!
+//! ## `jlink-quirks`
+//!
+//! When this feature is enabled, return values above `0xfffffff0` from semihosting operation
+//! `SYS_WRITE` (0x05) are interpreted as if the entire buffer had been written. The current
+//! latest version 6.48b of J-Link exhibits such behaviour, causing a panic if this feature
+//! is not enabled.
+//!
+//! ## `no-semihosting`
+//!
+//! When this feature is enabled, the underlying system calls are patched out.
+//!
 //! # Reference
 //!
 //! For documentation about the semihosting operations, check:
@@ -101,69 +179,46 @@
 //!
 //! [pdf]: http://infocenter.arm.com/help/topic/com.arm.doc.dui0471e/DUI0471E_developing_for_arm_processors.pdf
 
+#![cfg_attr(feature = "inline-asm", feature(llvm_asm))]
 #![deny(missing_docs)]
-#![deny(warnings)]
-#![feature(asm)]
 #![no_std]
 
 #[macro_use]
 mod macros;
 
 pub mod debug;
+#[doc(hidden)]
+pub mod export;
 pub mod hio;
 pub mod nr;
 
-/// The hint that differentiates the semihosting call.
-// WARNING: This variable is hardcoded in the asm! Don't forget to update
-// if it changes.
-pub const RISCV_SEMIHOSTING_CALL_NUMBER: usize = 7;
-
-/// Performs a semihosting operation, takes a pointer to an argument block
-#[inline]
-#[cfg(target_arch = "riscv")]
-pub unsafe fn syscall<T>(mut nr: usize, arg: &T) -> usize {
-    // .option push
-    // .option norvc
-    asm!(r"
-      slli x0, x0, 0x1f
-      ebreak
-      srai x0, x0, 0x7
-    "
-         : "+{x10}"(nr)
-         : "{x11}"(arg)
-         : "memory"
-         : "volatile");
-    // .option pop
-    nr
+#[cfg(not(feature = "inline-asm"))]
+extern "C" {
+    fn __sh_syscall(nr: usize, arg: usize) -> usize;
 }
 
 /// Performs a semihosting operation, takes a pointer to an argument block
-#[cfg(not(target_arch = "riscv"))]
-pub unsafe fn syscall<T>(_nr: usize, _arg: &T) -> usize {
-    0
+#[inline(always)]
+pub unsafe fn syscall<T>(nr: usize, arg: &T) -> usize {
+    syscall1(nr, arg as *const T as usize)
 }
 
 /// Performs a semihosting operation, takes one integer as an argument
-#[inline]
-#[cfg(target_arch = "riscv")]
-pub unsafe fn syscall1(mut nr: usize, arg: usize) -> usize {
-    // .option push
-    // .option norvc
-    asm!(r"
-      slli x0, x0, 0x1f
-      ebreak
-      srai x0, x0, 0x7
-    "
-         : "+{x10}"(nr)
-         : "{x11}"(arg)
-         : "memory"
-         : "volatile");
-    // .option pop
-    nr
-}
-
-/// Performs a semihosting operation, takes one integer as an argument
-#[cfg(not(target_arch = "riscv"))]
+#[inline(always)]
 pub unsafe fn syscall1(_nr: usize, _arg: usize) -> usize {
-    0
+    match () {
+        #[cfg(all(feature = "inline-asm", not(feature = "no-semihosting")))]
+        () => {
+            let mut nr = _nr;
+            llvm_asm!("
+                slli x0, x0, 0x1f
+                ebreak
+                srai x0, x0, 0x7
+            " : "+{a0}"(nr) : "{a1}"(_arg) :: "volatile");
+            nr
+        }
+
+        #[cfg(feature = "no-semihosting")]
+        () => 0,
+    }
 }

+ 96 - 5
src/macros.rs

@@ -11,19 +11,110 @@ macro_rules! syscall {
         $crate::syscall($crate::nr::$nr, &[$a1 as usize, $a2 as usize])
     };
     ($nr:ident, $a1:expr, $a2:expr, $a3:expr) => {
-        $crate::syscall($crate::nr::$nr, &[$a1 as usize, $a2 as usize,
-                                           $a3 as usize])
+        $crate::syscall($crate::nr::$nr, &[$a1 as usize, $a2 as usize, $a3 as usize])
     };
     ($nr:ident, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {
-        $crate::syscall($crate::nr::$nr, &[$a1 as usize, $a2 as usize,
-                                           $a3 as usize, $a4 as usize])
+        $crate::syscall(
+            $crate::nr::$nr,
+            &[$a1 as usize, $a2 as usize, $a3 as usize, $a4 as usize],
+        )
     };
 }
 
-/// Macro version of `syscall1`
+/// Macro version of `syscall1`.
 #[macro_export]
 macro_rules! syscall1 {
     ($nr:ident, $a1:expr) => {
         $crate::syscall1($crate::nr::$nr, $a1 as usize)
     };
 }
+
+/// Macro for printing to the HOST standard output.
+///
+/// This is similar to the `print!` macro in the standard library. Both will panic on any failure to
+/// print.
+#[macro_export]
+macro_rules! hprint {
+    ($s:expr) => {
+        $crate::export::hstdout_str($s)
+    };
+    ($($tt:tt)*) => {
+        $crate::export::hstdout_fmt(format_args!($($tt)*))
+    };
+}
+
+/// Macro for printing to the HOST standard output, with a newline.
+///
+/// This is similar to the `println!` macro in the standard library. Both will panic on any failure to
+/// print.
+#[macro_export]
+macro_rules! hprintln {
+    () => {
+        $crate::export::hstdout_str("\n")
+    };
+    ($s:expr) => {
+        $crate::export::hstdout_str(concat!($s, "\n"))
+    };
+    ($s:expr, $($tt:tt)*) => {
+        $crate::export::hstdout_fmt(format_args!(concat!($s, "\n"), $($tt)*))
+    };
+}
+
+/// Macro for printing to the HOST standard error.
+///
+/// This is similar to the `eprint!` macro in the standard library. Both will panic on any failure
+/// to print.
+#[macro_export]
+macro_rules! heprint {
+    ($s:expr) => {
+        $crate::export::hstderr_str($s)
+    };
+    ($($tt:tt)*) => {
+        $crate::export::hstderr_fmt(format_args!($($tt)*))
+    };
+}
+
+/// Macro for printing to the HOST standard error, with a newline.
+///
+/// This is similar to the `eprintln!` macro in the standard library. Both will panic on any failure
+/// to print.
+#[macro_export]
+macro_rules! heprintln {
+    () => {
+        $crate::export::hstderr_str("\n")
+    };
+    ($s:expr) => {
+        $crate::export::hstderr_str(concat!($s, "\n"))
+    };
+    ($s:expr, $($tt:tt)*) => {
+        $crate::export::hstderr_fmt(format_args!(concat!($s, "\n"), $($tt)*))
+    };
+}
+
+/// Macro that prints and returns the value of a given expression for quick and
+/// dirty debugging.
+///
+/// Works exactly like `dbg!` in the standard library, replacing `eprintln!`
+/// with `heprintln!`.
+#[macro_export]
+macro_rules! dbg {
+    () => {
+        $crate::heprintln!("[{}:{}]", file!(), line!());
+    };
+    ($val:expr) => {
+        // Use of `match` here is intentional because it affects the lifetimes
+        // of temporaries - https://stackoverflow.com/a/48732525/1063961
+        match $val {
+            tmp => {
+                $crate::heprintln!("[{}:{}] {} = {:#?}",
+                    file!(), line!(), stringify!($val), &tmp);
+                tmp
+            }
+        }
+    };
+    // Trailing comma with single argument is ignored
+    ($val:expr,) => { $crate::dbg!($val) };
+    ($($val:expr),+ $(,)?) => {
+        ($($crate::dbg!($val)),+,)
+    };
+}