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:
- The printf() implementation provided by newlib processes the format string and substitutes the values provided as arguments.
- 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:
- Determine the exact libnosys.a file used by your application:
- 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).
- If not, check the map file as shown here
- Make a backup copy of the libnosys.a file in case the next step damages it.
- 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).