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.
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:It is not that CMake links full newlib: it links both. Counting archive members in the image, Make pulls 86 from
libc_nano.aand none fromlibc.a; CMake pulls 22 fromlibc_nano.aand 49 fromlibc.a.Root Cause
All three linker scripts end with the same block (
core/STM32H750IB_flash.lds:260,_qspi.lds:263,_sram.lds:261):DISCARDis not the special linker-script syntax for discarding sections. The special output section is/DISCARD/, with the/characters (see GNU ld documentation ). As written,DISCARDis 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 theDISCARDsection, 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.How to fix
Changing the block to
/DISCARD/is not sufficient.libc.ais 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 and1,304 B RAM.
There is one small exception:
SpiDmaTransmitgrows 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.