Compiling bare-metal C code for an ARM Cortex-M microcontroller using arm-none-eabi-gcc often results in a frustrating linker failure: undefined reference to _exit or undefined reference to _sbrk. This error happens because standard C library functions (like exit(), printf(), and malloc()) expect an underlying operating system to provide low-level POSIX system calls.

The Cause: Bare-Metal Firmware Has No OS System Calls

When your main() function returns or calls exit(), Newlib (the C library shipped with ARM GCC) attempts to invoke _exit(status) to yield control back to the OS kernel. On a bare-metal microcontroller with no OS, _exit() does not exist:

ARM GCC Compilation Call Chaintext
main() return -> exit() -> _exit() -> POSIX System Call (MISSING on Bare-Metal!)
                                        |
                                        +--> Linker Error: undefined reference to _exit

The cleanest fix is telling the ARM GCC linker to use nosys.specs, which automatically supplies stub implementation functions for all missing OS system calls (_exit, _sbrk, _write, _read):

Makefile Linker Commandbash
# Build bare-metal firmware using nano C library and nosys stubs
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb main.c -o firmware.elf \
    --specs=nano.specs \
    --specs=nosys.specs

Solution 2: Implement a Custom _exit() Stub in C

If you prefer not to pull in nosys.specs, implement your own _exit() stub function that enters an infinite hardware loop to prevent system runaway:

syscalls_stub.cc
#include <unistd.h>
 
// Custom _exit stub for bare-metal ARM microcontrollers
void _exit(int status) {
    (void)status; // Suppress unused parameter warning
    
    // Disable interrupts and trap CPU in an infinite loop
    __asm__ volatile ("cpsid i"); // Disable IRQ interrupts
    while (1) {
        __asm__ volatile ("wfi"); // Wait for Interrupt (Low power idle)
    }
}

Why Infinite Loop _exit() is Necessary on Microcontrollers:

  • Prevents CPU Instruction Drift: If main() exits without an infinite loop, the CPU program counter will attempt to execute uninitialized flash memory.