knurling-rs / probe-run

Run embedded programs just like native ones
Apache License 2.0
643 stars 75 forks source link

Semihosting support #120

Open jonas-schievink opened 3 years ago

jonas-schievink commented 3 years ago

Semihosting provides a canonical way to exit the program with a firmware-controlled exit status. This could replace our current scheme, where we terminate the program after any breakpoint was hit, and look at the stack backtrace to determine whether to use an exit status that indicates failure (being in the HardFault exception handler is treated as a failure).

Using semihosting seems more robust and flexible to me.

japaric commented 3 years ago

semihosting support

semihosting as in "ARM-style" semihosting as implemented by OpenOCD and QEMU?

how much semihosting support do we want to expose? just the exit function or also the open, close, read, write API?

I'd be wary of providing 2 ways to do IO with the host: semihosting and RTT. RTT is non-blocking so I consider it the better solution for device-host communication.

My other concern is that having the probe checking for semihosting invocations (check if device halted; then check register 0) will eat some of the USB bandwidth used by the RTT polling mechanism so it will effectively reduce the rate at which the host can clear the device's RTT buffer. This is less of an issue if we only support semihosting exit because we can defer the semihosting check when the device's RTT buffer is empty (which would indicate that the device has halted), but if we have semihosting IO and RTT running concurrently then RTT's bandwidth may be more significantly reduced.

jonas-schievink commented 3 years ago

semihosting as in "ARM-style" semihosting as implemented by OpenOCD and QEMU?

Yeah, the "official" semihosting for ARM, so that we can reuse the existing device-side implementation.

how much semihosting support do we want to expose? just the exit function or also the open, close, read, write API?

Just exit would be enough I think. We have no way to handle multiple data streams at the moment anyways. We could however detect attempts to use semihosting I/O and tell the user that it isn't supported.

Re: Bandwidth – since semihosting halts the device anyways, couldn't we still just check for it if the RTT buffer is empty (while the device is waiting on a semihosting call it wouldn't write anything to RTT)? It would make semihosting even slower, but probe-run needs to download the RTT data at some point anyways, so we could just schedule the bandwidth so that RTT always takes precedence, right?

japaric commented 3 years ago

This could replace our current scheme, where we terminate the program after any breakpoint was hit, and look at the stack backtrace to determine whether to use an exit status that indicates failure (being in the HardFault exception handler is treated as a failure).

note that probe-run also puts a break point on HardFault. Without the breakpoint, the program would get stuck in an infinite loop: that's the default behavior of HardFault. I'm not sure if I would remove this breakpoint mechanism. If we do remove it then we need to override HardFault to call exit(1) which can result in linker errors if the user set a custom HardFault to do something else. If we don't override HardFault then UDF would result in an infinite loop.

jonas-schievink commented 3 years ago

Hmm, yeah, we probably want to keep that mechanism.

I'm mostly looking for a more ergonomic way to control the exit status than to cause a hard fault or put loop { bkpt(); } into every app.

japaric commented 3 years ago

(Note that cortex-m-semihosting exit function is not divergent so you would still end up with loop { exit(0) } instead of loop { bkpt() }, which I'm not sure it's much of an improvement.)

jonas-schievink commented 3 years ago

Hmm, yeah. Maybe that should use a loop internally. Seems like the intent is always to actually exit the app.