Conclusion
Conclusion first.
1.First, not using MicroLib keeps the source behavior under our control — vendors can't arbitrarily alter the underlying lib, and the source remains portable, so different C standard libraries won't cause behavioral differences.
By extension, if you don't use MicroLib, the original libc is pulled in, and many interactive functions in the C library will generate Semihosting code. As a result, the program stutters along under debug and won't run at all outside debug.
In short, without MicroLib, you need to be very cautious with C standard library functions and avoid the system-interactive functions that generate Semihosting code.
The Ancestral Rule Must Not Change
Ever since I entered the industry, my boss flat-out banned the printf family — or rather, banned all libc functions, including malloc, memset, strcpy, and so on. The only standard C library headers allowed were stdint.h, stdlib.h, and stdbool.h. This led to a huge amount of hand-rolled code for string operations, buffer operations, trace logging, and the like. Even if banning malloc can be justified as a way to avoid thread stack overflows and to contain bugs with static memory, why exactly are all the other standard library implementations unusable?
In short, the ancestral rule must not change — even current projects still follow it.
A Clue
While reading the bootloader vulnerability write-up by this guy (which is actually another stack operation problem), I came across the article below. It may explain the origin of this ancestral rule.
Verification
Here is some of the code from the article. clock(); is a typical system-interactive function. MicroLib is disabled in this project.
#include <stdio.h>
#include <time.h>
int main(void)
{
// 省略一些初始化代码
while (1)
{
clock_t tTime = clock();
HAL_GPIO_TogglePin(GPIOH, GPIO_PIN_5);
HAL_Delay(500);
}
}
Run it in debug mode.
First, the disassembly related to clock() jumps to address 0x0800037C to execute clock().
0x0800037C 2100 MOVS r1,#0x00
0x0800037E 2010 MOVS r0,#0x10
0x08000380 BEAB BKPT 0xAB ;将会停在这句,这就是Semihosting代码的软断点
0x08000382 4905 LDR r1,[pc,#20] ; @0x08000398
0x08000384 6809 LDR r1,[r1,#0x00]
0x08000386 1A40 SUBS r0,r0,r1
Sure enough, inside clock() there is a BKPT software breakpoint.
BKPT is the Break Point (software breakpoint) instruction of Cortex-M, and the constant 0xAB is the special code for Semihosting. If your debugger happens to support Semihosting, the program won't even pause; it will just run normally. But power-cycle it and restart, and you're in trouble: “When a BKPT instruction executes in non-debug mode, it directly sends the Cortex-M processor into HardFault.”
Can Turning On Optimization Avoid It?
Actually, modern compilers are already very smart. Enabling even a little optimization will fix many bugs you didn't even know you had.
Even with O3 enabled, BKPT still inevitably appears. This libc is presumably linked into the flashed image as a precompiled binary.
Which Functions Should Be Avoided?
These are listed in the reference article; here's an excerpt.
1. Standard I/O
- printf family functions: e.g., printf, fprintf, sprintf, etc., used for formatted output to the standard output device (usually the host console).
- scanf family functions: e.g., scanf, fscanf, sscanf, etc., used for formatted input from the standard input device (usually the host keyboard).
2. File Operations
- fopen: opens a file.
- fclose: closes a file.
- fread: reads data from a file.
- fwrite: writes data to a file.
- fseek: moves the file pointer to a specified position.
- ftell: gets the current position of the file pointer.
- fflush: flushes the file output buffer.
3. Time and Date
- time: gets the current time.
- clock: gets the processor time.
- difftime: calculates the difference between two time points.
- strftime: formats time and date into a string.
4. Error Handling
- perror: outputs an error message to the standard error device.
- strerror: returns the error message string corresponding to an error code.
5. System Calls
- exit: terminates the program and returns a status code.
- system: executes a system command (rarely used in embedded systems, but may be useful when debugging on a host).
6. Other Auxiliary Functions
- getenv: gets the value of an environment variable.
- putenv: sets an environment variable (not common).
- remove: deletes a file.
- rename: renames a file.
The Original Article Is Fascinating — Highly Recommended
Obviously, we match characteristic 4 in its summary — and we're even more extreme.
How “Embedded Appendicitis” Lies Dormant and What Triggers It
Five-star General MacArthur once commented: “Ask Baidu about an illness and it starts at cancer. You fake expert, you make Semihosting sound so scary, and you say 'the compiler plants it by default' — but I'm still alive and well? Why have I never run into it?”
No offense, but you may match the following characteristics:
- You use Arm Compiler 5 in most cases;
- You use MicroLib by default in most cases;
- When using Arm Compiler 6 without selecting MicroLib, you run into the symptom “everything works in debug mode, but after flashing and running on its own, it crashes” — and you quietly note in your little notebook that only MicroLib can be used;
- You never use libc functions other than malloc, not even printf;
- Your project template was made by a big shot;
- Your application development is based on the chip vendor's example projects;
- You use a “one-stop service” software platform like RT-Thread.
Although I listed a lot, it really comes down to only two cases:
- A blind cat runs into a dead mouse — lucky.
- Someone else is carrying the load for you.