OpenOCD semihosting

Is there a way to communicate with openocd and gdb on the hifive board? Useful for error messages and test runners.

This chapter describes the operation of SiFive debug hardware, which follows the RISC-V Debug
Specification v0p13. Currently only interactive debug and hardware breakpoints are supported.

This makes me a little pessimistic, but maybe there is a method through writing to Debug RAM?

Perhaps I don’t understand your question, but we use OpenOCD and GDB to load and debug programs in the Freedom E SDK and Freedom Studio. The HiFive1 board is supported (note that it uses debug spec v11, not v13, and it does use the Debug RAM).

There is an arm semihosting command in openocd that enables printing stuff to the openocd stdout which I think is also printed in gdb when debugging.

Command: arm semihosting [enable|disable]
Display status of semihosting, after optionally changing that status.
Semihosting allows for code executing on an ARM target to use the I/O facilities on the host computer i.e. the system where OpenOCD is running. The target application must be linked against a library implementing the ARM semihosting convention that forwards operation requests by using a special SVC instruction that is trapped at the Supervisor Call vector by OpenOCD.

Ah, thanks for the pointer! We have not implemented this yet in riscv-openocd.

I’m also considering adding semihosting support for RISC-V projects in GNU MCU Eclipse, but unfortunately I did not find a reasonable way to implement it.

Traditionally, semihosting calls use a reserved debugger break; on Cortex-M this is (if I remember right) BKPT 0xAB; (BKPT has a byte where various values can be passed to the debugger).

On RISC-V, with its very tight instruction encoding, there is only one BREAK instruction, which should be preserved as a way for the application to break to the debugger. So there is no easy way to implement a specific break to the debugger, that can be used to implement the semihosting call.

I was thinking on a workaround, for example to use a sequence of instructions together with placing a magic number in a register; my preference would be something like EBREAK & NOP, and a vallue like 0xca11ab1e (callable); the purpose is to minimise the risc of entering a semihosting call from a normal break.

Time permitting, I’ll try to implement this in OpenOCD and add suport for semihosting in the projects generate by the future SiFive Eclipse project template.

2 Likes

That seems like a reasonable approach. You might consider a register like mscratch for holding the “magic” value.

That would be equivalent to reserve mscratch for semihosting, which might not be what this register was designed for.

I was thinking on something less intrusive; semihosting requires two registers, and I would use a0 for the service code, and a1 for the pointer to the structure with params; to this I would add a2 with the magic value.

The only semihosting calls I’m interested in are SYS_WRITEC, SYS_WRITE0 and maybe SYS_READC. Does Eclipse for MCU have a use case for the other semihosting calls? For it to be generally useful (debug channel that works with all riscv cores) it would also need support in emulators (spike, qemu, rv8). Since support for HTIF seems to already be “around” in spike and qemu, why not fix and document that instead? Or does that require some special hardware support?

Hi @dvc, there are two use cases for semihosting in the projects generated by the GNU MCU Eclipse templates:

  • as destination for the trace functions
  • as API to access the host, for full semihosting projects

Full semihosting projects are very useful to write unit tests; they start by receiving a command line string from the host the and return an exit code; optionally they may leave some files with the individual test results.

Currently only the STM32 templates can generate full semihosted projects, the SiFive templates have the semihosting option disabled, since the RISC-V debuggers do not support semihosting yet.

By design, semihosting must access the debugger via a custom BREAK instruction; unfortunately the tightly designed RISC-V ISA has a single EBREAK instruction and we need to come up with some inovative ways of using it for semihosting too.

Semihosting shouldn’t require too much addition. Just for reference, this is how semi hosting calls is done in ARM. The main point you can see in call_host() is bkpt #0xAB and the usage of two registers. The #0xAB identifies the break as a semihost break (At least with thumb only ARM arch) (Shown in this page).

static inline int __attribute__ ((always_inline))
call_host (int reason, void* arg) {
  int value;
  __asm
  (
    "mov r0, %[com] \n"
    "mov r1, %[msg] \n"
    "bkpt #0xAB  \n"
    " mov %[val], r0"
    : [val] "=r" (value)                                 /* <-- Outputs */
    : [com] "r" (reason), [msg] "r" (arg)                /* <-- Inputs  */
    : "r0", "r1", "r2", "r3", "ip", "lr", "memory", "cc" /* <-- Clobber */
    // Clobbers r0 and r1, and lr if in supervisor mode
  );
  return value;
}

int semihosted_console_write_error(const char* buff, const unsigned int nbyte) {
  int block[3] = {2 /* STDERR */, (int) buff, nbyte};
  int res = call_host (SEMIHOSTING_SYS_WRITE, block); // Returns the number of bytes *not* written.
  if (res < 0)   return -1;
  return (nbyte - res);
}

Where the typical operations avaliable is shown in this page

The RISC-V semihosting is a bit more complicated, since the RISC-V ISA provides a single EBREAK instructions, and it is necessary to brace it with some magic values to differentiate the semihosting call from regular breaks.

The actual implementation, also supported by OpenOCD, is:

I wonder if it was intentionally chosen to have a single EBREAK to save instruction space. Seems like an oversight not to support semihosting easily. But perhaps later revisions can add another instruction to properly support it.

It was an oversight. There were long discussions on how to overcome this limitation. Proposals for later revisions are available, but the intrrest for this is not high, so I would not expect it soon.

That’s reasonable. Well considering that this EBREAK is now pretty much baked into some RISCV cpu in the field. What may be a reasonable thing to do is to add to next ISA revision… a note on how openocd place the magic number.

This would at least ensure that other people using EBREAK for other semihosting reason, won’t be stepping on openocd. And if possible, could be a good idea to document all the openocd semihosting “reasons” and “arguments” list as well for riscv (in case other tools want to do semihosting as well).

Of course this doesn’t mean we shouldn’t add another instruction to properly resolve this eventually anyway, as you mentioned. But at least this would help formalise the openocd way of doing semihosting (At least the magic number bit).

I already submitted my patches to OpenOCD master and I’m waiting for approval. Once this is done I’ll discuss with SEGGER for similar functionality. The SiFive Eclipse project templates already generate semihosted projects.

The implementation follows the ARM specs from https://static.docs.arm.com/100863/0200/semihosting.pdf

Your comments/suggestion related to documenting/formalising RISC-V semihosting are appreciated, but, to be effective, please submit them to the sw-dev list.

1 Like

Okay email is sent. Sounds like things are chuggling along in https://github.com/riscv/riscv-debug-spec/pull/185

Btw one thing that annoyed me about using the break command for semihosting in ARM, is that it halts the system when a debugger is not attached. Which means I’m forced to add a check to make sure its not called when debugger is not attached.

I can’t remember the details but does semihosted SVC Supervisor Calls have a similar behaviour or is it smart enough to ignore when a debugger is not attached by default in ARM? Also I don’t totally get SVC in arm, but does it allows for a program to monitor another program as well (Thus could have an absurd situation of nested SVC calls. )? Just trying to understand why would we have an SVC, instead of a better break instruction in ARM.

one thing that annoyed me about using the break command for semihosting in ARM, is that it halts the system when a debugger is not attached. Which means I’m forced to add a check to make sure its not called when debugger is not attached.

Well, you probably never used GNU MCU Eclipse; Cortex-M3/M4 projects generated by the GNU MCU Eclipse project templates use a HardFault handler which fakes the debuger, so the Debug version of the application also runs when the debugger is not connected:

understand why would we have an SVC, instead of a better break instruction in ARM.

These are two completely different use cases; BRK is intended to break into the debugger, while SVC is inteded to make a system call, to access some protected resources while in user mode.

Well it seems ARM in later cpus prefer that you use SWI / SVC instructions for the semihosting calls even though they are totally separate semantics. (But could be mostly just arch history)

Maybe semihosting is better thought of as a system call? Since the supervising system might be a monitoring hypervisor, rather than a JTAG debugger? Hmmm… I can see a supervisor call being used in cloud computing, as a convenient way to have a VM program, but where the console messages is using the semihosted supervisor calls.

If you can configure the system call to break to the debugger, yes, you can use them as well.

What many people usually don’t get, is that semihosting is a mechanism to use the host resources via the debugger; semihosting is not intended to work without a debugger, and semihosting depends on a special instruction that is able to break to the debugger, so the debugger, running on the host, will perform the requests.

Just noticed one thing thats really annoying me about ARM MCU when using semihosting.

It doesn’t have a reliable way to check that the debugger probe is actually disconnected. Resetting the MCU doesn’t clear the DEBUGEN bit in DHCSR. Thus when you rely on this bit to check if you can use semihosting… you will end up getting stuck in a semihost breakpoint when you disconnect the debug probe and press reset.

I would hope riscv has a way to avoid this annoyance. E.g. A reliable way to detect if a debug probe is disconnected. Or at the very least making the semihosting call nonblocking if a debugger is not attached.