Semihosting
Semihosting is a mechanism that enables code running on an Embedded System (also called the target) to communicate with and use the I/O of the host computer.
Operations
Semihosting allows use of some of the host's resources, such as terminal I/O, File I/O and access to Real time. Not all operations are supported by all implementations, and some are actually a security hazard, therefore potentially not implemented.
Terminal output
In most systems, the important part is Terminal I/O, or rather Terminal out. This is the ability to send text to the host. In probably 90% of the cases, this is what is needed. This functionality is also (not accurately) referred to as printf functionality, since printf() requires an underlying implementation of terminal output. printf() requires a formatter, which at run time creates the string to be output, thus significantly increasing the size of the program (typically by between 3 -20 kb, depending on implementation and supported functionality). Since especially smaller embedded systems can often not afford this increase in Program size, a technology called Host-based formatting is available in some implementations such as the SEGGER Run Time Library.
Semihosting operations defined by ARM
ARM has defined 24 semihosting operations. Not all of these really make sense in an Embedded System. Below is the list of operations as defined by ARM, in alphabetic order, along with the function number assigned:
File operations
- SYS_CLOSE (0x2) - Closes a file on the host which has been opened by SYS_OPEN.
- SYS_ERRNO (0x13) - Returns the value of the C library errno variable that is associated with the semihosting implementation.
- SYS_FLEN (0x0C)
Target time
These functions are not typically needed and implemented.
- SYS_CLOCK (0x10) - Returns target execution time in centiseconds (10ms). Not normally used.
- SYS_ELAPSED (0x30) - Returns the number of elapsed target ticks since execution started.
Obsolete
- SYS_EXIT (0x18)
- SYS_EXIT_EXTENDED (0x20)
Common semihosting operations
A de-facto standard for semihosting operations has been defined by ARM. This, but now widely adopted, defining the "Common semihosting operations".
Semihosting has been around for a very long time. It was not invented by ARM.
Basic operation
Basically, the target CPU is halted, either by running into a breakpoint instruction or by some other operation which stops program execution and puts the target CPU under control of the debug-agent.
The debug agent can be either the debug probe (e.g. J-Link) or the debugger (such as GDB, SEGGER Ozone or the debugger integrated in Embedded Studio).
The debug agent reads one or more register(s) to determine the type of operation to be performed by the host on behalf of the target, then performs the action,
then restarts the target. The target is halted for the duration of the semihosting operation.
Ways to halt the target CPU
There are different ways to halt the target CPU, primarily special calls such as "Supervisor calls" or Breakpoints. Ideally, an implementation of semihosting is done in a way that when the system does not run under control of a debugger, it still works. This is really important for systems which use semihosting for things that are optional, not essential, such as debug output (printf) to the host. For example, ARM's Cortex-M implementation has the following problem: It uses a breakpoint instruction, which leads to a hard fault if the CPU is not running in debug mode. The way to handle this is using a special hardfault handler, which checks the source of the problem, and allows "skipping" of the BKPT instruction and resumption of program execution.
Implementation
Supervisor Call
Legacy ARM cores, such as ARM7, ARM9, ARM11, as well as Cortex-A and Cortex-R devices typically use the supervisor call (SVC instruction, previously SWI) for semihosting. The debug agent sets a breakpoint or vector catch on the SVC handler.
On entry of the SVC handler, the debugger checks whether the call should trigger semihosting or not, and if it should, it performs the semihosting operation, does an exception return, and continues execution. If the supervisor call does not trigger semihosting, execution is continued in the SVC handler.
Semihosting via supervisor call is triggered by the SVC number, which is encoded in the SVC instruction.
In ARM Mode the SVC number can be 24 bit wide. The standard SVC number for semihosting is 0x123456:
SVC 0x123456
In Thumb mode the SVC number is only 8 bit wide. The standard SVC number for semihosting is 0xAB:
SVC 0xAB
SVC Handler
To ensure that the target application keeps running even when no debugger is connected, an SVC handler should be implemented, which also checks whether the call should have triggered semihosting or not. In case semihosting should have been triggered, the application might do a simple exception return. When the SVC handler has been called for any other reason, the application might handle this accordingly.
SVC_Handler: push {R2,R3} // // Check whether we come from Thumb or ARM mode. // Application's CPSR is saved in SPSR. // mrs R2, SPSR tst R2, #0x20 beq _CheckSemiARM // SPSR.T (Bit 5) not set -> ARM mode _CheckSemiThumb: // // Load immediate of Thumb SVC instruction // #if BIG_ENDIAN ldrb R2, [LR, #-2] #else ldrb R2, [LR, #-1] #endif // // Check if it has been a semihosting SVC // ldrb R3, =0xAB cmp R2, R3 pop {R2,R3} // Restore registers (might be used by semihosting or SVC handler) bne _DoSVC // No semihosting SVC. Continue in SVC handler b _DoSemihost // Semihosting SVC. Run through debugger breakpoint and return _CheckSemiARM: // // Load immediate of ARM SVC instruction // ldr R2, [LR, #-4] bic R2, R2, #0xFF000000 // // Check if it has been a semihosting SVC // ldr R3, =0x00123456 cmp R2, R3 // // Restore registers (might be used by semihosting or SVC handler) // pop {R2,R3} bne _DoSVC // No semihosting SVC. Continue in SVC handler //b _DoSemihost // Semihosting SVC. Run through debugger breakpoint and return _DoSemihost: // // Perform exception return to instruction after SVC. // Also resets CPSR. // subs PC, LR, 0 _DoSVC: // // Stay in endless loop // User SVC handler could start here instead // b _DoSVC .end
Software Breakpoint
Cortex-M devices can use the software breakpoint instruction (BKPT) instead of the supervisor call.
When a debug probe is connected, the target halts on execution of the BKPT. The debugger checks whether the breakpint should trigger semihosting or not. If it should, it performs the semihosting operation, skips the BKPT instruction, and lets the target run.
Semihosting via software breakpoint is triggered by the parameter which can be encoded with the BKPT instruction.
BKPT is always a 16 bit instruction with 8 bit parameter. The standard parameter for semihosting is 0xAB:
BKPT 0xAB
Semihosting HardFault Handler
The disadvantage of the BKPT instruction is, that when no debugger is connected, a hard fault error occurs. The target needs to recover from the hard fault in order to continue execution.
HardFault_Handler: // // This version is for Cortex M3, Cortex M4 and Cortex M4F // Copy previously selected stack pointer to R0. // tst LR, #4 // Check EXC_RETURN in Link register bit 2. ite EQ mrseq R0, MSP // Stacking was using MSP. mrsne R0, PSP // Stacking was using PSP. // // Check if hardfault has been caused by software break. // Otherwise stay in HardFault handler. // ldr R1, =0xE000ED2C // NVIC_HFSR ldr R2, [R1] lsls R2, R2, 1 // Shift Bit 31 into carry. _Loop: bcc _Loop // Carry == 0? Hardfault has been caused by other reason. Stay in endless loop. // // Reset hardfault status // str R2, [R1] // NVIC_HFSR = NVIC_HFSR // // Modify return address to return to next instruction after BKPT // ldr R1, [R0, #24] // R0 == Current SP adds R1, #2 str R1, [R0, #24] // *(pStack + 6u) += 2u; // // Return // bx LR .end
Generic Handling
Supervisor calls and software breakpoints are target-depentent implementations for semihosting. Some architectures do not implement such functionality. For example the RISC-V instruction set includes a breakpoint instruction, but it does not take an extra parameter which could identify it as a semihosting call.
When the developer might need terminal I/O for debugging, a debugger can provide generic handling for semihosting, which does not rely on any target-specific features.
Using Semihosting with J-Link
Semihosting with Embedded Studio
The implementation of semihosting in Embedded Studio is straight forward. Standard library functions for fopen, fwrite, fread etc. are already implemented and just need to be called. The project wizard is recommended when creating a new project so that all default settings are set correctly.
The following example project opens a file on your host system and writes a string into it triggered by your target device: Semihosting Embedded Studio
Make sure the file path exists, otherwise the project will not run. You can of course change the path by simply editing variable pFilename and rebuild it with Embedded Studio.
Prerequisites for the example project are:
- Embedded Studio V4.16 or later
- Cortex-M Trace Reference Board (ST STM32F407VE)
- J-Link V10 or later
Baremetal Semihosting with Ozone
Ozone is a debugger so it does not come with standard libraries and predefined semihosting code like it does with some IDEs. Thus to make use of semihosting the semihosting calls need to be implemented manually by the user in the target application. The big benefit of this approach without predefined library functions is that you get full control about the complexity of the semihosting implementation so you can write more efficient code as the library overhead is missing.
A baremetal example implementation can be found in the following example project: Semihosting Ozone
The example project will open a file on the host system and write a string to it. Make sure the file path is existing otherwise the project will not run. You can of course change the path by simply editing variable pFilename and rebuild it with Embedded Studio.
Prerequisites for the example project are:
- Ozone V2.62 or later
- Embedded Studio V4.16 or later
- Cortex-M Trace Reference Board (ST STM32F407VE)
- J-Link V10 or later
This baremetal code can be used with any debug software which supports semihosting handling.
Semihosting with newlib and GNU Arm toolchain
This section will cover how semihosting can be used with Eclipse in combination with GDB and newlib. Generally newlib will only provide a rudimentary setup for semihosting so it will not work out-of-thebox except for printf calls. All callbacks used by e.g. fopen need to be implemented by the user to utilize semihosting.
The following project will show such an example project and create a file at a certain path, fill it with a string and close that file: Semihosting Eclipse
Make sure the file path is existing otherwise the project will not run. You can of course change the path by simply editing variable pFilename and rebuild it with Eclipse. As a base project a default J-Link debug project was created for Cortex-M. Then a debug config with the correct target device was set up. Next make sure that in the debug config semihosting is enabled.
The example project comes also with an Ozone setup that runs out-of-the-box should Eclipse not be the debugger of your choice.
Prerequisites for the example project are:
- Eclipse Cpp 2019-03
- Latest GNU ARM Eclipse plugin
- Cortex-M Trace Reference Board (ST STM32F407VE)
- J-Link V10 or later
- (optional) Ozone V2.62 or later
Semihosting with EWARM
The implementation of semihosting in EWARM is generally available but requires some extra settings in your project. Standard library functions for fopen, fwrite, fread etc. are implemented and just need to be called. Under project options semihosting must be enabled as well as the library support must be set to "full" which will drastically increase memory usage of your project.
The project options can be found at the following project settings:
Set the library level to "Full" and the library low level implementation to semihosting each.
Note: This will also increase your application size immensely so make sure your target device has enough memory resources left.
The following example project opens a file on your host system and writes a string into it triggered by your target device: Semihosting EWARM
Make sure the file path is existing otherwise the project will not run. You can of course change the path by simply editing variable pFilename and rebuild it with Embedded Studio.
Prerequisites for the example project are:
- EWARM V8.322 or later
- Cortex-M Trace Reference Board (ST STM32F407VE)
- J-Link V10 or later