Semihosting

From SEGGER Wiki
Jump to: navigation, search

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. This is done by halting the target program, in most cases using some sort of a breakpoint instruction at a certain point in the code, or a mode switch (supervisor mode for legacy ARM devices or Cortex A/R). SEGGER has made a generic, portable implementation publicly available, see SEGGER Semihosting.

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.

History

Semihosting is nothing new. It has been used in Embedded Systems for many decades. Most companies in the space of development tools have developed their own semihosting, basically all following the same idea of halting the target processor, typically by letting it run into a breakpoint. However, there was not interoperability since there was no standard, until ARM defined somewhat of a standard for ARM processors. This standard, is available at http://infocenter.arm.com/help/topic/com.arm.doc.dui0471i/CHDJHHDI.html . Unfortunately it does not come with an implementation.

SEGGER extensions

SEGGER has extended this ARM version of Semihosting.

SEGGER did the following

  • include a generic version of a function to set a breakpoint on so that it can run on any system
  • add a C implementation with header file, under a permissive license
  • add assembly implementations of the code required for ARM 7/9/11 and Cortex A/R (Supervisor handler)
  • add assembly implementations of the code required on Cortex-M systems, especially the Hardfault handler that allows treating BPs as NOPs when running in release mode (not under control of a debugger)
  • Provide a complete reference project for Embedded Studio
  • Add additional functionality for exit and for host-side formatting

Operations

Semihosting allows use of some of the host's resources, such as terminal I/O, File I/O and access to system information. Not all operations are supported by all target implementations and debuggers, and some are actually a security hazard, therefore potentially not implemented.

Common semihosting operations

Semihosting has been around a very long time. Some operations which can be performed with semihosting have proven to be more useful than others. The first publicly available standard for (mostly) useful semihosting operations and their implementation has been defined by ARM. They are now widely adopted, also on non-ARM-based devices. The SEGGER Semihosting implementation is largely compatible to the ARM implementation with some enhancements such as host-based printf (keeping the target program free from the formatting code).

Terminal I/O

In most systems, the important part is Terminal I/O, or at least terminal output. This is the ability to send text to the host, to display it in a debugger console or terminal. In probably 90% of the cases, this is what is needed, for example for debugging with log output only.

This functionality is also (not accurately) referred to as printf functionality, since printf() requires an underlying implementation of terminal output. But printf() is more, as it also includes a formatter, which at run time creates the string to be output, thus significantly increasing the size of the program (typically by between 3 to 20 kB, depending on implementation and supported functionality). Since especially smaller embedded systems can often not afford this increase in program size, another semihosting operation, typically called Host-based formatting, is available in some implementations such as the SEGGER Run Time Library with Embedded Studio.

Semihosting operations defined by ARM

ARM defined semihosting operations, as well as groups for additional standard and user operations. Some of these operations are already obsolete, as they are not really useful in current embedded systems. Below is the list of operations as defined by ARM, along with the function number assigned:

File operations

  • SYS_OPEN (0x01) - Open a file or stream on the host system.
  • SYS_ISTTY (0x09) - Check whether a file handle is associated with a file or a stream/terminal such as stdout.
  • SYS_WRITE (0x05) - Write to a file or stream.
  • SYS_READ (0x06) - Read from a file at the current cursor position.
  • SYS_CLOSE (0x2) - Closes a file on the host which has been opened by SYS_OPEN.
  • SYS_FLEN (0x0C) - Get the length of a file.
  • SYS_SEEK (0x0A) - Set the file cursor to a given position in a file.
  • SYS_TMPNAM (0x0D) - Get a temporary absolute file path to create a temporary file.
  • SYS_REMOVE (0x0E) - Remove a file on the host system. Possibly insecure!
  • SYS_RENAME (0x0F) - Rename a file on the host system. Possibly insecure!

Terminal I/O operations

  • SYS_WRITEC (0x03) - Write one character to the debug terminal.
  • SYS_WRITE0 (0x04) - Write a 0-terminated string to the debug terminal.
  • SYS_READC (0x07) - Read one character from the debug terminal.

Time operations

  • SYS_CLOCK (0x10)
  • SYS_ELAPSED (0x30)
  • SYS_TICKFREQ (0x31)
  • SYS_TIME (0x11)

System/Misc. operations

  • SYS_ERRNO (0x13) - Returns the value of the C library errno variable that is associated with the semihosting implementation.
  • SYS_GET_CMDLINE (0x15) - Get commandline parameters for the application to run with (argc and argv for main())
  • SYS_HEAPINFO (0x16)
  • SYS_ISERROR (0x08)
  • SYS_SYSTEM (0x12)

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.

Supervisor Call (Legacy ARM devices and Cortex-A/R)

As a reference for semihosting via supervisor call, refer to SEGGER_SEMIHOST_CortexA.

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. This typically halts the CPU at address 0x8, in SVC mode.

The debugger checks whether the instruction triggering the SVC call is the semihosting instruction, by looking at the parameter, the SVC number (0x123456 when entering from ARM mode, 0xAB when entering from Thumb mode). If it is a different number, program execution continues. If it is, the debugger performs the semihosting operation, then resumes program operation by copying the values saved in SPSR_SVC and LR_SVC to CPSR and PC, identical to an exception return. If the supervisor call does not trigger semihosting, execution is continued in the SVC handler.

In ARM Mode the SVC number is 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

ARM SVC Handler

To ensure that the target application keeps running even when no debugger is connected, an SVC handler should be implemented. When SVC is used for semihosting only, the SVC handler can simply do an exception return to continue execution in the application.

SVC_Handler:
        subs PC, LR, 0  // Perform exception return

When the application uses supervisor calls for semihosting as well as for any other operation, the SVC handler should check whether a 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.

As the debugger usually sets a breakpoint or vector catch on the SVC vector, the target application halts on every SVC, for semihosting operations and for other operations. To avoid this, some debuggers, such as Ozone, can be configured to break on an instruction or function called by the SVC handler. In this case the SVC handler always runs without break on entry and checks whether to do semihosting or not. Refer to the example implementation below.

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:
       //
       // Call semihosting handler for the debugger to set a breakpoint on
       //
       blx SEGGER_SEMIHOST_DebugHalt
       //
       // 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

As a reference for semihosting via software breakpoint, refer to SEGGER_SEMIHOST_CortexM.

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

Cortex-M HardFault Handler with Semihosting

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

As a reference for generic semihosting, refer to SEGGER_SEMIHOST_Generic and SEGGER_SEMIHOST_Generic_RISCV.

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. This can also be used instead of the target-dependent implementation, for example to avoid the SVC vector catch on Cortex-A devices.

The target application prepares the operation as usual, but instead of causing the break itself, it calls a function, such as SEGGER_SEMIHOST_DebugHalt(). To the target application this handling is generic. It has to only pass the semihosting parameters and return the return value in the function.

When a debugger, such as Ozone, is connected, it automagically sets a breakpoint in the entry of the function, and handles semihosting when it breaks there. While the implementation is generic for the target, the debugger has to know about the calling convention and ABI of the target appliction, to know how the parameters are passed.

When no debugger is connected, the target application simply returns from the function. If necessary it can check whether a debugger is connected or not (for example by calling SEGGER_SEMIHOST_IsConnected).

Generic Handler (for Ozone)

The following function is recoginzed by Ozone for semihosting.

/*********************************************************************
*
*       SEGGER_SEMIHOST_DebugHalt()
*
*  Function description
*    Dummy call for generic implementation.
*    The debugger may set a breakpoint on this function, 
*    handle the semihosting request,
*    and return to the caller.
*
*  Return value
*    Return parameter r0 if debugger is not connected.
*/
int __attribute__((noinline)) SEGGER_SEMIHOST_DebugHalt(int r0, int r1) {
  (void)r1;   // Avoid unused parameter warning
  return r0;
}


Using Semihosting with SEGGER tools

SEGGER has adopted the standard in all relevant tools. This includes Ozone, the J-Link debugger and Embedded Studio. Ozone: Semihosting in Ozone works with any program which complies with the standard, meaning that other tool chains can be used to generate ELF file which can be debugged with semihosting in Ozone. J-Run: Automating performance and functionality tests. https://blog.segger.com/j-run-automating-performance-tests/

Semihosting with Embedded Studio

The Embedded Studio debugger fully support the SEGGER Semihosting standard. It also easily creates programs that use semihosting. Using Embedded Studio is one of the easiest ways to create and run programs using semihosting.

Semihosting with Ozone

Ozone fully supports semihosting operations that comply with the ARM or SEGGER standard. The applications can be built by any tool chain, such as ARM/Keil, IAR, GCC / Eclipse. Ozone supports handling semihosting via software breakpoint, supervisor call, as well as with a generic breakpoint on SEGGER_SEMIHOST_DebugHalt. By default semihosting is automatically enabled. No configuration is required. However, the semihosting handling can be configured to match the target implementation. Refer to Semihosting with Ozone for more information.

Generating programs using semihosting

Generic Semihosting code - any tool chain

In case the toolchain does not implement ARM semihosting operations, the sources can also be added and called directly instead of through standard library functions. In this case, the SEGGER_SEMIHOST fucntions need to be called directly, so for example SEGGER_SEMIHOST_Writef() instead of printf()

Semihosting with Embedded Studio

Embedded Studio uses the SEGGER semihosting standard. The standard library in Embedded Studio projects automatically make use of these operations in standard functions, such as printf, scanf, fopen, fwrite, fread, ...

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

The semihosting implementation of Embedded Studio also includes host-formatted output. For formatted output, such as via printf(), the target does not have to construct the formatted string in RAM. Instead the format string is passed via semihosting to Embedded Studio, which parses it and reads the parameters from the target.

Host-formatted output saves a lot of resources, as it does not require the target application to use an extra buffer in RAM to format the string and can perform fast compared to output of the formatted string character by character. This way debug output can be made possible even on most constrained devices with little RAM and computing power.


Semihosting with GCC and newlib

Most GCC toolchains include newlib (or newlib.nano) as the standard library. By default the standard I/O functions, such as printf, scanf, fopen, fread, ..., of newlib, do not do anything. Instead they require some low-level functions to be implemented and call the right handling operations.

The low-level functions can in general be implemented to do anything. They could use the target system internal file system for file operations and a display for printf output. For debugging and output to the host, the functions can be implemented to call semihosting operations.

If only printf is required for output to the debug console, it is usually sufficient to implement _write(). For file I/O or other functions, some more low-level functions need to be implemented.

In addition to the low-level target implementation, the debugger also needs to handle semihosting. Most GDB-based debuggers, such as Eclipse can do this. The J-Link GDB Server also includes some semihosting handling, to enable semihosting where the GDB Debugger cannot do this. As a more powerful alternative, Ozone also supports all semihosting operations and can be used with GCC/newlib code.

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.

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