Using Semihosting the direct Way

Most embedded developers have probably used ‘semihosting’. And yes, it is generally seen as a bad thing. Maybe you have used it, without realizing what it really is and what it does. It is simple to add a printf() to the code (again: you should not use printf), and with the right standard library, it magically it shows up in a console view:

printf a hello world

That looks great, but what is behind this, to make it happen? Actually, it is really amazing technology. And it can be used for better things than just printing text.

Outline

Print() alone can be very inefficient, because it goes through the Standard Library. So why not using it directly, without the standard library overhead? This article shows how you can use Semihosting in a direct way, and unlock its full potential: for not only writing text, but get date/time from the host, up to to read and write files on the host too.

In this article I present a comparison between using semihosting through the standard library (newlib-nano, for example), and using it directly with a McuSemihost module, which is part of the McuLib).

I’m using here the NXP MCUXpresso IDE 11.7.0 with the NXP FRDM-K22F (ARM Cortex-M4F), except for the P&E debug probe I had to use the IDE 11.6.0 instead. You can find the project used in this article on GitHub.

Semihosting

ARM defines Semihosting as:

“Semihosting is a mechanism that enables code running on an ARM target to communicate and use the Input/Output facilities on a host computer that is running a debugger.”

ARM: “What is semihosting?”

It has the Latin word ‘semi’ (“half”) in it, because half of the operation is executed on the target, and the other half on the Host. The image below shows the general concept and flow:

Semihosting flow

The application on the target calls a standard library function like printf(). Down below in the library, instead of re-targeting it to an UART connection, it prepares the data to be sent Then it calls the debugger, usually with a special exception (SVC) or special instruction (BKPT). The debugger gets notified, detects that this is a special exception to receive data. It reads the data and then forwards or shows it in a terminal.

The important thing to notice here is: the target raises a special exception or instruction to halt the target: the attached debugger recognizes this, processes the request and afterwards continues execution of the target. This means that this operation (while running in the background) needs the target to be stopped, and as such can be very intrusive. And you always need a running debug session.

The other concern is: going through the standard library is causing a lot of overhead (code size, stack usage, heap usage). So the idea is: instead using the standard library, why not using semihosting directly?

The answer or solution is a new addition to the McuLib: the McuSemihost module which you can find on GitHub. In the next sections I describe the concept and implementation. For the full details, please see the source code.

ARM Semihosting Interface

Both the ARM SVC and BKPT instructions take an 8-bit argument, for example I can pass a number with the BKPT instruction:

__asm volatile("bkpt 0xab");

ARM has defined the above instruction with the value 0xab as the way how to ‘call’ the debugger for a semihosting operation on ARMv6-M and ARMv7-M in thumb state, e.g. Cortex-M.

ARM has defined different values for different operations.

For example there is the operation SYS_WRITE (0x05) to write data to a file:

McuSemihost_Op_SYS_WRITE        = 0x05, /* write data to a file */

ARM defines the operation as below:

ARM SYS_WRITE Semihosting Operation

The McuLib implements the function McuSemihost_HostRequest() to call the debugger with the reason and the given arguments:

static inline int __attribute__ ((always_inline)) McuSemihost_HostRequest(int reason, void *arg) {
  int value;
  __asm volatile (
      "mov r0, %[rsn] \n"
      "mov r1, %[arg] \n"
      "bkpt 0xAB      \n"
      "mov %[val], r0 \n"

      : [val] "=r" (value) /* outputs */
      : [rsn] "r" (reason), [arg] "r" (arg) /* inputs */
      : "r0", "r1", "r2", "r3", "ip", "lr", "memory", "cc" /* clobber */
  );
  return value;
}

The register R0 is loaded with the operation (for example SYS_WRITE) and R1 points to an array of arguments.

Below is the code how to write data to a file, using a file handle previously obtained:

int McuSemihost_FileWrite(int fh, const unsigned char *data, size_t nofBytes) {
  int32_t param[3];

  param[0] = fh;
  param[1] = (int32_t)data;
  param[2] = nofBytes;
  return McuSemihost_HostRequest(McuSemihost_Op_SYS_WRITE, &param[0]);
}

Below is how this look on the target, where it writes the data “hello world!” to the file handle 1 (which is actually ‘stdout’):

Semihosting Flow for a printf() call

Semihosting HardFault Handler

So the BKPT instruction requires the debugger to catch it. But what happens if there is no debugger attached or active? In that case, a Hardfault exception will happen, because the BKPT instruction was not handled by the debugger. That’s why if someone tells me that the application works in debug mode, but crashes without debugger, I will ask “do you use printf(), because this might use semihosting?”.

The solution to that problem is to implement a special HardFault handler as you can see in McuHardFault.c.

__asm volatile (
...
    " ldrh r2,[r1]                \n"  /* load opcode causing the fault */
    " ldr r3,=0xBEAB              \n"  /* load constant 0xBEAB (BKPT 0xAB) into R3" */
    " cmp r2,r3                   \n"  /* is it the BKPT 0xAB? */
    " beq _SemihostReturn         \n"  /* if yes, return from semihosting */
    " b McuHardFault_HandlerC     \n"  /* if no, dump the register values and halt the system */
...
  );

That Hardfault handler checks if the fault is originated by a semihosting operation. If yes, it ignores it and continues normal execution. It still will take time for all this, but it will allow continuation of the application.

Semihosting Operations

With the BKPT 0xAB instruction, the MCU is able to call the debugger. The kind of operation is passed with the R0 register. ARM defines the following semihosting operations, passed as immediate values in R0:

typedef enum McuSemihost_Op_e {
  McuSemihost_Op_SYS_OPEN         = 0x01, /* open a file */
  McuSemihost_Op_SYS_CLOSE        = 0x02, /* close a file */
  McuSemihost_Op_SYS_WRITEC       = 0x03, /* write a character byte, pointed to by R1: not implemented by PEMICRO */
  McuSemihost_Op_SYS_WRITE0       = 0x04, /* writes a null terminated string. R1 points to the first byte, not supported by PEMICRO */
  McuSemihost_Op_SYS_WRITE        = 0x05, /* write data to a file */
  McuSemihost_Op_SYS_READ         = 0x06, /* read data from a file: not implemented reading from stdin by PEMICRO */
  McuSemihost_Op_SYS_READC        = 0x07, /* read a character from stdin: not implemented by PEMICRO */
  McuSemihost_Op_SYS_ISERROR      = 0x08, /* Determines whether the return code from another semihosting call is an error status or not.  */
  McuSemihost_Op_SYS_ISTTY        = 0x09, /* check if it is a TTY */
  McuSemihost_Op_SYS_SEEK         = 0x0A, /* move current file position */

  McuSemihost_Op_SYS_FLEN         = 0x0C, /* tell the file length */
  McuSemihost_Op_SYS_TMPNAME      = 0x0D, /* get a temporary file handle */
#if McuSemihost_CONFIG_HAS_SYS_REMOVE
  McuSemihost_Op_SYS_REMOVE       = 0x0E, /* remove a file */
#endif
#if McuSemihost_CONFIG_HAS_SYS_RENAME
  McuSemihost_Op_SYS_RENAME       = 0x0F, /* rename a file */
#endif
  McuSemihost_Op_SYS_CLOCK        = 0x10, /* returns the number of centi-seconds since execution started */
  McuSemihost_Op_SYS_TIME         = 0x11, /* time in seconds since Jan 1st 1970 */
  McuSemihost_Op_SYS_SYSTEM       = 0x12, /* Passes a command to the host command-line interpreter. */
  McuSemihost_Op_SYS_ERRNO        = 0x13, /* get the current error number*/

  McuSemihost_Op_SYS_GET_CMDLINE  = 0x15, /* Returns the command line used for the call to the executable */
  McuSemihost_Op_SYS_HEAPINFO     = 0x16, /* Returns the system stack and heap parameters. */
  McuSemihost_Op_SYS_ENTER_SVC    = 0x17, /* Sets the processor to Supervisor mode and disables all interrupts */
  McuSemihost_Op_SYS_EXCEPTION    = 0x18, /* Exit application */

  McuSemihost_Op_SYS_ELLAPSED     = 0x30, /* Returns the number of elapsed target ticks since execution started. */
  McuSemihost_Op_SYS_TICKFREQ     = 0x31, /* Returns the tick frequency. */

#if 0 && McuSemihost_CONFIG_DEBUG_CONNECTION==McuSemihost_DEBUG_CONNECTION_SEGGER /* documented by SEGGER, but not implemented? */
  McuSemihost_Op_SYS_IS_CONNECTED = 0x00, /* check if debugger is connected: note that this is not implemented with GDB server */
  McuSemihost_Op_SYS_WRITEF       = 0x40, /* write a printf-style string, but formatting is on the host: seems not be implemented with GDB server */
#endif
} McuSemihost_Op_e;

Some operations require additional arguments. The arguments are passed using the R1 register, pointing to an array of values.

Most implementations of semihosting support the SYS_OPEN, SYS_CLOSE, SYS_WRITE. As noted in the comments above, not every debug probe/debugger vendor implements all codes. In general the SEGGER implementation seems the most complete one to me, while the other debug probe vendor at least support writing characters (aka printf()) to a semihosting console.

One concern is that some operations (file rename or delete, executing a shell command on the host, …) can be potentially dangerous. This is why for example SEGGER has not implemented everything. So be careful using potential harmful operations on the host!

Semihosting File Operation

Below some examples to perform file operations with semihosting:

int McuSemihost_FileOpen(const unsigned char *filename, int mode) {
  int32_t param[3];

  param[0] = (int32_t)filename;
  param[1] = mode;
  param[2] = McuUtility_strlen((char*)filename);
  return McuSemihost_HostRequest(McuSemihost_Op_SYS_OPEN, &param[0]);
}

int McuSemihost_FileClose(int fh) {
  int32_t param;

  param = fh;
  return McuSemihost_HostRequest(McuSemihost_Op_SYS_CLOSE, &param);
}

int McuSemihost_FileRead(int fh, unsigned char *data, size_t nofBytes) {
  int32_t param[3];

  param[0] = fh;
  param[1] = (int32_t)data;
  param[2] = nofBytes;
  return McuSemihost_HostRequest(McuSemihost_Op_SYS_READ, &param[0]);
}

int McuSemihost_FileWrite(int fh, const unsigned char *data, size_t nofBytes) {
  int32_t param[3];

  param[0] = fh;
  param[1] = (int32_t)data;
  param[2] = nofBytes;
  return McuSemihost_HostRequest(McuSemihost_Op_SYS_WRITE, &param[0]);
}

The above way uses the file operations directly, not through the standard library (fopen(), fread(), fclose()). Keep in mind that the standard library does some buffering in the library too, while with the above code the buffering is on the host (debugger) only.

Semihosting Time API

A very cool thing is to get the time and date information from the host, using the SYS_TIME operation. The code below shows how to initialize the MCU RTC with the time and date information from the host:

int McuSemihost_HostTime(void) {
  return McuSemihost_HostRequest(McuSemihost_Op_SYS_TIME, NULL);
}

uint8_t McuSemihost_GetTimeDateFromHost(TIMEREC *time, DATEREC *date, int offsetHour) {
  int secs = McuSemihost_HostTime(); /* using SYS_TIME */
  if (secs<=0) {
    return ERR_FAILED;
  }
  McuTimeDate_UnixSecondsToTimeDateCustom(secs, offsetHour, time, date, 1970);
  return ERR_OK;
}

With this, I can use the information to update the hardware RTC with the time/date from the host:

if (McuSemihost_GetTimeDateFromHost(&time, &date, -1)==ERR_OK) {
  McuLog_info("host date/time %02d.%02d.%04d %02d:%02d,%02d", date.Day, date.Month, date.Year, time.Hour, time.Min, time.Sec);
  McuTimeDate_SetTimeDate(&time, &date);
}

Example usage with Unit Tests

Semihosting is very powerful: for example I’m using the McuSemihost module to get the host time/date to initialize the board RTC, followed by some unit tests which do store the test results on the host. Semihosting is a really easy way to get data to the target or dump it off to the host:unit

04.01.2000 00:00:00,22 INFO  UnitTest.c:56: ************** START: Tests **************
04.01.2000 00:00:00,23 INFO  UnitTest.c:25: open host test file: "c:\tests\unittestlog.txt"
04.01.2000 00:00:00,30 INFO  UnitTest.c:56: ************** START: RTC **************
04.01.2000 00:00:00,32 INFO  UnitTest.c:200: host date/time: 06.03.2023 18:44:40
06.03.2023 18:44:40,02 INFO  UnitTest.c:206: successfully set date/time
06.03.2023 18:44:41,00 INFO  UnitTest.c:60: **************  END:  RTC **************
....
06.03.2023 18:55:33,20 INFO  UnitTest.c:30: closed host test file: "c:\tests\unittestlog.txt"
06.03.2023 18:55:33,21 INFO  UnitTest.c:60: ************** END: Tests **************

Debug Probes

I tested Semihosting operation with PEMICRO (Mulitlink FX), SEGGER (J-Link Plus) and NXP LinkServer (McuLink Pro) debug probes with the FRDM-K22F board.

P&E Multilink FX, FRDM-K22F, SEGGER J-Link Plus, NXP MCU-Link Pro

There is a problem in MCUXpresso IDE 11.7.0: the P&E semihosting does not work, so I used the MCUXpresso IDE 11.6.0 instead.

Issue with P&E Semihosting in MCUXpresso 11.7.0

The J-Link bundled with the MCUXpresso IDE 11.7.0 is the V7.84e, but this has broken semihosting support (only 7.68 works, or you have to upgrade to V7.86b). So I upgraded to the J-Link V7.86b, so make sure you have at least this version.

Because not every debug probe supports everything, there is a setting in the McuSemihost to configure the probe used:

#define McuSemihost_DEBUG_CONNECTION_GENERIC     (0) /*!< generic debug probe */
#define McuSemihost_DEBUG_CONNECTION_LINKSERVER  (1) /*!< NXP Linkserver debug probe */
#define McuSemihost_DEBUG_CONNECTION_SEGGER      (2) /*!< SEGGER J-Link debug probe */
#define McuSemihost_DEBUG_CONNECTION_PEMICRO     (3) /*!< P&E Multilink debug probe */
#define McuSemihost_DEBUG_CONNECTION_PYOCD       (4) /*!< PyOCD debug probe */

#ifndef McuSemihost_CONFIG_DEBUG_CONNECTION
  #define McuSemihost_CONFIG_DEBUG_CONNECTION    McuSemihost_DEBUG_CONNECTION_GENERIC
#endif

The McuSemihost module is taking the settings into consideration, and uses the supported operations if possible.

Debug Launch Configurations

The debugger (or launch configuration in Eclipse) needs to be configured for semihosting. Below are the settings for P&E, Segger, NXP and PyOCD:

P&E Debug Setting for Semihosting
Segger J-Link Settings for Semihosting (NXP Plugin)
Eclipse CDT J-Link Settings for Semihosting
PyOCD Semihosting Settings
NXP LinkServer Semihosting Setting

Benchmark

All debuggers support console I/O (printf(), …), to various degree. The J-Link semihosting implementation is the most complete one. I have measured the performance of text output and basic file operations, newlib-nano Standard Library, compared with the direct way using the McuLib McuSemihost implementation. The benchmark does the following, in that sequence (you can find the code on GitHub):

  1. Writing formatted output: ‘printf
  2. Writing text: ‘puts
  3. Uses character by character operation: ‘putchar
  4. Create a file on the host and writes data to it: ‘file write data‘ and closes the file
  5. Reopens the file and reads the data: ‘file read data

FLASH and RAM

First a comparison is for FLASH and RAM:

Flash and RAM

So there is an advantage in code size (FLASH). The RAM usage is basically the same. Most RAM usage is because of heap and stack memory. Both reserve a heap size of 4 KByte and a stack size of 1 KByte:

The big difference is how much of heap and stack is used:

Stack and Heap

Here we can see that the McuSemihost implementation needs less stack space, and zero heap space. The newlib-nano implementation uses the dynamic heap memory for its buffering and file handling, but as well for the printf() stdin, stdout and stderr handles.

Cycles

The benchmark measures the cycles spent on the target for each step, using the ARM cycle counter:

  CCOUNTER_START();
  printf("hello world!\n");
  CCOUNTER_STOP();
  Cycles_LogTime("Stdlib printf hello world");

Below are the cycles for the first two steps: in blue using the newlib-nano, and in orange using the McuSemihost module:

Semihosting output

The McuLib with the McuSemihost module does not go through the standard library and needs less cycles.

The next part is writing many characters which char operations (putchar) followed by writing and reading a file:

Here again the McuSemihost implementation of the McuLib needs less CPU cycles compared to the Standard Library.

The number of cycles are more ore less the same for each debug probe used. There are only differences where some operations like SYS_WRITE0 are not supported by every probe.

Comparing how much time is needed for each debug probe is interesting too:

Time on the Host in Seconds

The data shows that the McuLib implementation has a small advantage, because less overhead then the Standard Library way. But the overhead in the debugger connection is really the bottleneck, so the target CPU performance (less cycles for the McuLib) are not paying off here.

But what we can see is: using a debug probe can make a difference how much time the operation takes. With no debug probe attached and with the above HardFault handler, the number of cycles needed have an impact.

Summary

Semihosting is a very interesting concept and allows the running target to communicate with the host through the debugger. Most developers might use semihosting through a standard library implementation, for example with newlib or newlib-nano. With the proposed McuSemihost.c, it is possible and more efficient to use semihosting directly. While it does not give much advantage over the time spent with the host, it uses less cycles, code (FLASH) and data (RAM, heap, stack) on the target. Not every debug probe implements the full set of semihosting operation, and the performance of the debug probe has an impact too.

I hope, you find this article useful, and if you are using semihosting, you might find the ‘direct’ way and control of it beneficial for your application too.

Happy semihosting 🙂

Links

28 thoughts on “Using Semihosting the direct Way

  1. We have a saying in the Rust embedded community: “Friends don’t let friends use semihosting!”. Semihosting is a horribly outdated concept and superseded by (wait for it!) Segger RTT. I do find it somewhat ironic that the company you mention so often in your article actually has a real solution to peoples debugging needs without requiring the horrible hack that is semihosting: It is slow, it is wasteful in resources and it happy eats your lunch, err, crashes your program when there’s no debugging probe attached and debugger running so you always have to use a dedicated build which — more often than not — interferes with the program you’re actually trying to debug…

    Like

    • Hi Daniel,
      Thanks for your thoughts! Yes, semihosting has severe drawbacks which I have mentioned in the article too, and I do use RTT in many of my applications too (in reference to all my other articles about RTT). If you want to use text input/output, RTT is the way to go, especially if you own a (original) J-Link probe. For a very long time, this was limited to J-Link probes only, but now other probe interfaces (OpenOCD, pyOCD, …) start adding RTT support too (not tried hat yet). But what I need often is a way to create/open/read/write files on the host. This is what semihosting offers, and this is what I’m missing with RTT. Yes, there is the RTT Logger, but this is only for uplink and one channel (you can send data, and this is stored to a file). Or do you use RTT for file I/O too?
      As for ‘crashing if there is no debug probe attached’: see my reference to a hardfault handler which fixes that.
      So my view is: semihosting as other technologies has pros and cons, and should be used with care, and knowing what you are doing. Same thing with printf() and other technologies.

      Like

  2. SEGGER just has released a new version V7.86c, with following fixes/updates which is great:
    – [GDB Server]: Semihosting: Implemented functionality for SEGGER command extension SYS_IS_CONNECTED (0x00).
    – [GDB Server]: Semihosting: SYS_ERRNO often did not return an error code in case an error occurred. Fixed.
    – [GDB Server]: Semihosting: Write operations now also accept handle 2 (stderr), which is routed to the Semihosting TELNET-channel.

    Like

  3. Hello Erich, many thanks for this article. Semihosting has been on my list for a while. My thoughts: openocd also supports semihosting. One has to send “monitor arm semihosting enable” to the gdbserver on startup. There are several other commands which imply that openocd is capable of doing file IO.
    Concerning the section about debug probes I’m wondering what semihosting has to do with the probe. The actual work has to be done in something like gdbserver or gdb itself which have access to the host. The probe itself can do only some simple tricks like redirecting RTT debug output to a CDC. That’s what my probe does 😉 https://github.com/rgrr/yapicoprobe
    For me semihosting could be possibly of good use to write back profile/coverage data collected in the target. clang-rt provides modules for writing coverage data via file IO which works pretty on the host system. I think I have to check if this can be expanded to the target system via semihosting!

    Like

    • Hi Hardy,
      yes, I’m using semihosting in the context of gprof/gcov as well (see articles about this on my blog).
      Semihosting depends in several aspects of the probe: first, the operations defined by ARM has to be supported in the probe firmware. During my research I have found that the probe vendors (or GDB server implementations) vary very much what they support. So far I have not seen a debug probe implementing all operations, only some of it.
      Second, the probe performance has an impact on the performance of the semihosting implementation. For example I see that pyOCD in general is slower than a J-Link, and I can see this with the semihosting operation on the probe as well: the probe needs to read memory and registers during the semihosting operation, and if the probe is slow, the semihosting operation will take more time too.

      And thanks for the link to your probe! I’m doing something simpler for my embedded system course, so I’ll have a look at your work too.

      Like

      • Haha, your blog is like a treasure chest. Almost no work to be done now for me. Except that I have to check it with clang.

        And… have fun with the probe. Please let me know, if you experience limited fun.

        Like

        • I need to build/produce a custom board for the board, because we need a target power option with USB-C and a standard ARM 10-pin debug header/connector. I have not decided yet on the firmware. What puzzles me is that the ‘official’ Pico-Probe uses different pins according to the schematics? Have you seen this?

          Like

        • Pleeease use my firmware 😉 Several advantages: e.g. faster & nicer LED blinking.

          Honestly there are others: e.g. support for level shifter & support by the developer. And I’m not talking about its sigrok capabilities.

          I’m really waiting for someone doing a hardware project with it. One of your students perhaps?

          Concerning your point with the “official” probe HW: I was a little bit surprised as well, that they have changed the pinout, see https://datasheets.raspberrypi.com/debug/raspberry-pi-debug-probe-schematics.pdf. But that should be really easy to adopt accordingly.
          Also there are four LEDs to signal device state which could give an even nicer light show.
          I’m currently waiting for my probe hardware to arrive.

          Like

        • Hi Hardy,
          yes, I was surprised when looking at the schematics of the ‘official’ probe, and I have not been able to order one myself yet. It seems to me that it must be pre-loaded with a custom firmware, otherwise I cannot imagine how it is supposed to work.
          As for the probe hardware: I started with that myself using KiCAD, but have been diverted from it by lab work. I see if I can continue this week with it. We have used SEGGER J-Link EDU mini in many of our courses, but because they are not available any more, we are looking for using the RP2040 instead.

          Like

        • I just have finished the PCB/Gerbers of a ‘PicoDAP’ probe. It is of the size of the Pico board, but includes a standard SWD 10pin debug header, a 3-pin ‘Pico’ header (normal 2.54 pin header) plus a UART connection (2.54 mm pins, GND, Rx and Tx). I needed to keep it small and simple, so only one LED for 3.3V power and the ‘usual’ Pico green LED.

          Like

        • I’ve made the “port” to the Pico Debug Probe hardware to YAPicoprobe. If you share your schematic of your PicoDAP probe, I could check if any changes are required.

          Like

        • I have named it ‘picoLink’ and it is available here:
          https://github.com/ErichStyger/mcuoneclipse/tree/master/KiCAD/Projects/PicoLink
          Schematics of the first version inside the V0.1 subfolder.
          I used the same pins as the ‘normal’ PicoProbe (not the new one), as shown in https://mcuoneclipse.com/2022/09/17/picoprobe-using-the-raspberry-pi-pico-as-debug-probe/

          I have assembled one board so far, and it works great with the ‘original’ PicoProbe binary and build I have used so far.
          I wanted to use your firmware and build it, but the build failed, so I gave up (for now). Maybe you could put up releases on gitHub to make it easier?

          Like

      • I don’t think I’ve ever seen someone use semihosting to do profiling, usually people use advanced mechanisms like ITM for profiling (of course only on ARM Cortex-M3 or better), https://github.com/orbcode/orbuculum is a fantastic toolchain to do that. There’s certainly tooling allowing to use RTT for efficient tracing, ballpark profiling or unittesting. In the Rust world, the knurling project provides a ton of useful tooling so people don’t have to use archaic tools and mechanisms like gdb and semihosting 😉: https://knurling.ferrous-systems.com/tools/ .

        Like

        • The semihosting technology out there for a very long time and used widely, especially in the context of ‘hardware-less’ communication. I see gcov and gprof widely used, from Embedded Linux down to smaller embedded systems. RTT nicely extends and augments the technology, but is currently limited to serial communication, while semihosting goes well beyond that (file I/O, serial communication, time and date information, up to the ability to execute scripts on the host). RTT has lots of advantages, especially for using it in the context of SystemView or TraceAlyzer and RTOS performance analysis. One tool does not fit all, it is all about the right tool for the right task.

          Like

        • Just wanted to add: I’m very well aware of Rust, and using Rust in one of my embedded courses too. And there is nothing wrong with using gdb or semihosting: there are reasons why tools are in existence, and there are always choices.

          Like

    • Hi Yu,
      Indeed, and I did not expect that comments would be that ’emotional’ ;-).
      To me, both RTT and semihosting has its place and usage: they are different technologies with different use cases, and with different level of probe support.

      Erich

      Like

  4. Hello Erich,
    concerning inline assembly in McuSemihost_HostRequest(): I’m currently playing with semihosting on low level and thus on the actual semihost trigger.

    If one uses your above implementation of __asm(), clang generates

    00000000 :
    0: b5f0 push {r4, r5, r6, r7, lr}
    2: af03 add r7, sp, #0xc
    4: b083 sub sp, #0xc
    6: 4614 mov r4, r2
    8: ab00 add r3, sp, #0x0
    a: c307 stm r3!, {r0, r1, r2}
    c: 2505 movs r5, #0x5
    e: 466e mov r6, sp
    10: 4628 mov r0, r5
    12: 4631 mov r1, r6
    14: beab bkpt #0xab
    16: 4605 mov r5, r0
    18: 1b60 subs r0, r4, r5
    1a: b003 add sp, #0xc
    1c: bdf0 pop {r4, r5, r6, r7, pc}

    if using instead

    static inline uintptr_t __attribute__ ((always_inline)) prf_sys_semihost(uintptr_t op, uintptr_t arg)
    {
    register uintptr_t r0 __asm(“r0”) = op;
    register uintptr_t r1 __asm(“r1”) = arg;
    __asm volatile (
    “bkpt 0xab \n”
    : “=r” (r0) /* output */
    : “r” (r0), “r” (r1) /* inputs */
    );
    return r0;
    } // prf_sys_semihost

    output will be

    00000000 :
    0: b083 sub sp, #0xc
    2: ab00 add r3, sp, #0x0
    4: c307 stm r3!, {r0, r1, r2}
    6: 2005 movs r0, #0x5
    8: 4669 mov r1, sp
    a: beab bkpt #0xab
    c: 1a10 subs r0, r2, r0
    e: b003 add sp, #0xc
    10: 4770 bx lr

    I have to admit that I’m not sure concerning register clobbering cause this is work in progress. At least it works 😉

    PS: I know that semihosting call are actually not time critical, but just to mention…

    Like

    • ok… added clobber list to the __asm() statement and not surprisingly it now looks:

      00000000 :
      0: b5d0 push {r4, r6, r7, lr}
      2: af02 add r7, sp, #0x8
      4: b083 sub sp, #0xc
      6: 4614 mov r4, r2
      8: ab00 add r3, sp, #0x0
      a: c307 stm r3!, {r0, r1, r2}
      c: 2005 movs r0, #0x5
      e: 4669 mov r1, sp
      10: beab bkpt #0xab
      12: 1a20 subs r0, r4, r0
      14: b003 add sp, #0xc
      16: bdd0 pop {r4, r6, r7, pc}

      Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.