ARM Semihosting - native but slow Debugging
ARM Semihosting is a distinctive feature of the ARM platform, that allows to use input and output functions on a host computer that get forwarded to the microcontrollers over a hardware debugger. It is helpful when there is no input/output interface dedicated for logging on the target MCU.
Last update: 2022-06-29
Table of Content
STM32-Tutorials F411RE_Semihosting.zip
Semihosting setup
- Connect a debugger via SWD interface
- Include Semihosting library in GCC Linker:
-l rdimon --specs=rdimon.specs
. Exclude the defaultsyscall.c
implementation if needed - Call
initialise_monitor_handles()
at the begining of the main function - Run OpenOCD with command
monitor arm semihosting enable
Limitation
-
Semihosting implementation in OpenOCD is designed so that every string must be terminated with the newline character
\n
before the string appears on the OpenOCD console -
Semihosting only works during a debug session, and it’s slow and affects the system performance
-
CPU is halt when Semihosting is executing in host machine, therefore Semihosting is not suitable for realtime application
Debugging#
There are some debug techniques used to inspect the firmware running on ARM-based MCUs:
-
Semihosting: built-in to every ARM chips, need adding additional library and running in debug mode.
-
Console log: forward to a native UART port, a Virtual COM port through a USB port.
-
Serial Wire View (SWV): fast output over dedicated Single Wire Output (SWO) pin, but it’s only available on Cortex-M3+, and this is uni-direction communication.
-
Real Time Transfer (RTT): extremely fast but only work with SEGGER Debugger, can have a real-time bi-direction communication.
Semihosting#
ARM Semihosting is a distinctive feature of the ARM platform, that allows to use input and output functions on a host computer that get forwarded to the microcontrollers over a hardware debugger, by hooking into I/O functions, such as printf()
and scanf()
, or even fopen()
.
Semihosting is implemented by a set of defined software instructions, for example, SVC
, that generate exceptions from program control. The application invokes the appropriate Semihosting call and the debugger then handles the exception by communicating with the debugging application on the host computer.
ARM processors prior to ARMv7 use the
SVC
instructions, formerly known asSWI
instructions, to make Semihosting calls. However, for an ARMv6-M or ARMv7-M, in a Cortex-M1 or Cortex-M3 processor, Semihosting is implemented using theBKPT
instruction.
Hardware setup#
Semihosting need to be run under a debug session to communicate with Semihosting-enabled debugger, such as OpenOCD. In STM32, debugging channel maybe ST-LINK debugger (onboard, or external) which connects to the MCU via SWCLK and SWDIO in the SWD interface.
Linker options#
To use Semihosting, it has to be set in the linker options, and initialized in the main program.
Standard C libraries
GNU ARM libraries use newlib
to provide standard implementation of C libraries. To reduce the code size and make it independent to hardware, there is a lightweight version newlib-nano
used in MCUs.
However, newlib-nano
does not provide an implementation of low-level system calls which are used by C standard libraries, such as print()
or scan()
. To make the application compilable, a new library named nosys
should be added. This library just provide a simple implementation of low-level system calls which mostly return a by-pass value.
The lib newlib-nano
is enabled via linker options --specs=nano.specs
, and nosys
is enabled via linker option --specs=nosys.specs
. These two libraries are included by default in GCC linker options in generated project, check it here.
Rdimon library
There is a rdimon
library that implements interrupt for some special system calls, which pauses the processor and interacts with the host debugger to exchange data, such as SYS_WRITE (0x05)
or SYS_READ (0x06)
. This library provides low-level system calls to handling the newlib-nano
specs.
The lib rdimon
is enabled via linker option -l rdimon --specs=rdimon.specs
STM32CubeIDE automatically generates syscalls.c
with a simple implementation for nosys.specs
. However, it conflicts with the implementation in rdimon.specs
.
Exclude syscalls.c
from the build to avoid compilation error of multiple definitions.
Initialize Semihosting#
The rdimon
library has to be initialized before it can run properly. It exposes a function to do that, then use it:
extern void initialise_monitor_handles(void);
int main(void) {
initialise_monitor_handles();
...
}
After that, the application can use printf()
, scanf()
, or gets()
.
Debugger option#
The final thing is to enable Semihosting on debugger that will handle the interruptions fired from MCUs. We can select ST-LINK (OpenOCD) to use OpenOCD through ST-Link debugger. To start Semihosting in the debugger process, add below command in the Startup commands option:
monitor arm semihosting enable
Debug with Semihosting#
Run the project in debug mode and then interact with MCUs. Here are some lines of code to print a message, get a string, and write to a file on the host machine:
#include <stdio.h>
#include <string.h>
extern void initialise_monitor_handles(void);
char buffer[255];
int main(void) {
initialise_monitor_handles();
printf("Please enter your name: \n");
gets(buffer);
printf("\nAh, I know you, %s!\n", buffer);
// test.out will be created in the host machine
FILE *fd= fopen("D:\\test.out", "w+");
if(fd) {
fwrite(buffer, sizeof(char), strlen(buffer), fd);
fclose(fd);
}
uint8_t counter = 0;
while (1) {
printf("counter = %d\n", counter++);
}
}
Using files
When using fopen()
, the file path is specified either as relative to the current directory of the host process (e.g. openocd.exe
), or absolute, using the path conventions of the host operating system. Read more in here.