Semihosting and Standard Output for Embedded Projects

This page describes how embedded projects handle standard output functions like printf() or puts(), and shows the relevant VisualGDB settings that control the behavior in this scenario.

Contents

Overview

Assume that you are running the following code on an STM32 device:

for (int i = 1;;i++)
{
    printf("Iteration %d, value = %f\n", i, i * 0.1F);
}

Each time the printf() function gets invoked, the following events take place:

  1. The printf() implementation provided by newlib processes the format string and substitutes the values provided as arguments.
  2. It then calls the _write() function to physically output the formatted stringĀ (e.g. “Iteration 1, value = 0.1\n”).

This separation is needed because the actual printf() logic is the same on every device you run it on, as long as they share the same CPU – translating “Iteration %d” to “Iteration 1” does not depend on whether the output is going to UART, USB or semihosting. The _write() function, in turn, doesn’t know how the written data was produced, and just outputs it “as is” to the destination.

The printf() implementation

The printf() function is implemented in the Newlib library (printf.c) which is included in the ARM toolchain. To allow choosing between size and functionality, the ARM toolchain comes with 3 versions of printf():

Version Floating Point support Size
Regular (non-nano) Yes Largest
Newlib-nano with FP support Yes Smaller
Newlib-nano No Smallest

The exact version to use can be selected via VisualGDB Project Properties -> Embedded Project -> C Library Type:

On the low level, this setting changes the specs file (e.g. –specs=nano.specs) passed to the linker. You can see the exact flags used in each case by looking at the <toolchain directory>\toolchain.xml file.

_write() implementations

Once the printf() function has completed formatting the string, it calls the _write() function to physically output the data to its destination. _write() is typically taken from one of the following places:

Implementation Destination
Toolchain itself Regular (slow) semihosting
Fast Semihosting Framework Fast Semihosting
Application Application-defined (e.g. UART)

Toolchain-supplied _write()

The ARM toolchain itself can provide one of the 2 implementations of _write() and other similar functions that can be selected via VisualGDB Project Properties -> Embedded Project -> Implementations for _sbrk(), etc:

Setting Effect
None Calling printf() results in a link-time error
Minimal (no semihosting) Output of printf() is ignored
Support semihosting Output of printf() routed to regular semihosting

Toolchain-supplied implementations for _write() and other similar functions are declared as weak, i.e. enabling any other implementations shown below will automatically override them without causing any error.

Fast Semihosting

As the regular semihosting is very slow (requires stopping the target after outputting each line), VisualGDB provides its own fast semihosting mechanism. It comes with its own implementation of _write() that can redirect printf() output to the fast semihosting mechanism. It can be enabled by referencing the Fast Semihosting and Embedded Profiler framework and checking “Redirect printf() to fast semihosting” (enabled by default):

Application-supplied _write()

The application can provide its own implementation of _write() by using the template below (extern “C” is only needed if it’s located in a C++ file):

extern "C" int _write(int fd, char *pBuffer, int size)
{
}

It would be typically located in one of the source files and will be automatically called by printf() with the relevant data and can output it to an arbitrary location (UART, USB, network, internal circular buffer, etc). You can use Code Explorer to quickly locate the exact source file implementing it.

Troubleshooting

If the printf() function is not working as expected, you can check the map file (<Project name>.map) to find out which version of _write() was built into the application:

.text           0x08000190     0x1eec
                0x08000190                . = ALIGN (0x4)
                0x08000190                _stext = .
  *(.text*)
.text._write   0x08001fa0       0x10 c:/sysgcc/arm-eabi/<...>\libnosys.a(write.o)
                0x08001fa0                _write

In this example, it used _write() from libnosys.a, that corresponds to the “No semihosting” setting and discards all output.

Undefined reference to _write()

This error happens when the code uses _printf() and has not enabled either of the _write() implementations. You can resolve it by checking the settings shown above and enabling one of them.

Multiple definition of _write()

This error happens when the program contains multiple implementations of _write() (e.g. one defined in the application, and another one in the fast semihosting library). You can resolve it by choosing the implementation to use and disabling the extra ones.

If you need the Fast Semihosting framework, but do not want to use its implementation of _write(), you can uncheck the “redirect printf() to fast semihosting” option shown below:

<symbol> is not implemented and will always fail

This warning is new in the 12.2 ARM toolchain and comes from the libnosys.a library. Each time you link your application with a version of system call from that library (e.g. _getpid()), the linker now shows a warning, even if it ends up discarding that system call later.

These warnings do not indicate a problem (unless you are specifically trying to use one of the functions mentioned there) and can be safely ignored. They only indicate that this system call is a stub that always just returns an error code and doesn’t do anything else.

If you would like to completely remove the warnings, you would need to patch the toolchain as shown below:

  1. Determine the exact libnosys.a file used by your application:
    1. Check if the warning messages mention the path of any other .a file from the toolchain (e.g. if the warnings mention c:/sysgcc/arm-eabi/bin/../lib/gcc/arm-none-eabi/12.2.1/thumb/v7e-m+fp/hard\libc_nano.a, the libnosys.a will be located in the same directory as libc_nano.a).
    2. If not, check the map file as shown here
  2. Make a backup copy of the libnosys.a file in case the next step damages it.
  3. Run the following command on the libnosys.a file:
    arm-none-eabi-objcopy --remove-section ".gnu.warning.<symbolN>" libnosys.a

    Replace <symbolN> with the name of the symbols shown in the warnings (e.g. if the warning says “_fstat is not implemented and will always fail”, the section to remove is “.gnu.warning._fstat“). You can have multiple –remove-section “<name>” arguments in the same invocation of objcopy.
    This will remove the warning messages from the file, so the linker will no longer show them.

You can also patch all versions of libnosys.a by first dumping the list of all files into a batch file (dir /s /b libnosys.a > strip.bat) and then doing a regex-based find-and-replace with Notepad++ (e.g. (.*) => arm-none-eabi-objcopy –remove-section “.gnu.warning._close” \1).