Skip to content

CMake build uses full newlib instead of newlib-nano #714

Description

@zeroisnan

While comparing the Make and CMake build flows (same as issue #712), I found that the CMake builds are pulling in newlib on top of newlib-nano. Same sources, both flows, examples/SerialPrint:

arm-none-eabi-nm -S <elf> | grep _impure_data
# Make:  0000004c   (76 B  — newlib-nano's reduced struct _reent)
# CMake: 00000120   (288 B — full newlib)

It is not that CMake links full newlib: it links both. Counting archive members in the image, Make pulls 86 from libc_nano.a and none from libc.a; CMake pulls 22 from libc_nano.a and 49 from libc.a.

Root Cause

All three linker scripts end with the same block (core/STM32H750IB_flash.lds:260, _qspi.lds:263, _sram.lds:261):

    DISCARD :
    {
        libc.a ( * )
        libm.a ( * )
        libgcc.a ( * )
    }

DISCARD is not the special linker-script syntax for discarding sections. The special output section is /DISCARD/, with the / characters (see GNU ld documentation ). As written, DISCARD is treated as an ordinary output section.

The Make flow does not show this problem because of the way the linker script is passed to the compiler. The Makefile uses -T$(LDSCRIPT), which GCC places at the end of the linker command line. This means the -lc_nano / newlib-nano libraries are searched before the libc.a reference from the linker script. By the time the linker reaches the DISCARD section, all symbols have been resolved.

The CMake flow passes the script using -Wl,-T,..., which leaves it earlier in the linker command line. In that case the libc.a reference from the script can satisfy symbols before newlib-nano is selected.

-T$LDS       ->  _impure_data = 76 B
-Wl,-T,$LDS  ->  _impure_data = 288 B

How to fix

Changing the block to /DISCARD/ is not sufficient. libc.a is still referenced by the linker script and still gets loaded, so the CMake result is unchanged.

Removing the whole block from all three linker scripts fixes the issue.

Across all 35 CMake examples this leaves zero members from libc.a; a typical stdio-using example drops 2,832 B flash and
1,304 B RAM.

There is one small exception: SpiDmaTransmit grows by 704 bytes, apparently due to a difference in how newlib-nano implements __register_exitproc, anyway this is also what the Make flow produces. The Make flow is unaffected, output is byte-identical before and after.

By deleting the block completely, we remove the forced dependency on the full libc.a. This allows the compiler's intended linker flags (like --specs=nano.specs) to do their job properly and pull in the lightweight newlib-nano without interference.

I can open a PR to remove the block from all three linker scripts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions