Resource optimization for low-end systems
Contract
| Field | Bound contract |
|---|---|
| Trigger | Firmware exceeds its flash or RAM budget, a stack overflows in production, a linker map needs reading, or -Os versus -O2 needs deciding per file. |
| Authority | Read-only. Emits analysis and commands for the operator to run; no file writes, no rollback needed. No remote mutation. |
| Side effect | Measurement commands and a size verdict in chat. Nothing is written. |
| Done | The largest flash or RAM consumers are named from the map file, and each has a stated reduction tactic. |
Inputs
- Firmware ELF and map file (required): the linked image plus the
-Wl,-Map=output. - Budget (required): flash and RAM limits from the part's datasheet.
- Toolchain (required): the cross
binutilsmatching the target, for examplearm-none-eabi-*.
Procedure
Measure the image.
arm-none-eabi-size -A firmware.elf arm-none-eabi-objdump -h firmware.elf nm --size-sort -S firmware.elf | tail -20Done when: section totals and the largest symbols are known.
Read the linker map. Generate it at link time and grep the sections.
gcc ... -Wl,-Map=firmware.map grep -E '\.text|\.rodata|\.data|\.bss' firmware.map | headLook for the largest symbols and for library code pulled in unexpectedly, such as full
printf. Done when: the top consumers are named with their sizes.Apply the size flags.
Flag Effect -OsOptimize for size -ffunction-sections -fdata-sectionsOne section per symbol -Wl,--gc-sectionsDrop unreferenced sections -fltoCross-TU dead code elimination -specs=nano.specsSmaller newlib on ARM GCC -Wl,--print-memory-usagePrint region usage at link time CFLAGS += -Os -ffunction-sections -fdata-sections LDFLAGS += -Wl,--gc-sections -Wl,--print-memory-usage--gc-sectionscan collect interrupt vectors and other unreferenced entry points; pin them withKEEP()in the linker script. Done when: the flags are on and the image still boots.Measure the stack.
# Static per-function usage, from -fstack-usage builds find . -name '*.su' -exec cat {} \; # Stack bound symbol from the linker script grep _estack firmware.mapAt runtime, fill the stack with a pattern such as
0xDEADBEEF, run the worst-case path, and scan for the high-water mark. Under FreeRTOS useuxTaskGetStackHighWaterMark. Done when: worst-case stack depth is measured, not guessed.Cut RAM by section.
Section Tactic .bssShrink buffers; use pool allocators .dataMove constants to flash with const, landing them in.rodataHeap Avoid malloc; use fixed poolsStack Reduce call depth; size ISR stacks separately Done when: each RAM section has a named reduction.
Decide size versus speed per file.
Hot path in an ISR or a fast control loop? |-- yes -> -O2 for that file (#pragma GCC optimize or per-file flags) +-- no -> -Os globallyDone when: hot files carry
-O2and the rest stay at-Os.
Failure and recovery
| Symptom | Cause | Fix |
|---|---|---|
printf is the largest pull-in |
Full newlib implementation | Retarget _write; use a tiny printf |
--gc-sections broke an IRQ |
Vector or handler collected | KEEP() it in the linker script |
| Stack overflow appears late | Deep call plus IRQ nesting | Measure the high-water mark; raise the stack bound |
| RAM is free but the ELF is large | .data not loaded from flash |
Check VMA versus LMA in the linker script |
| LTO link fails | Mixed compiler versions | Build every object with the same GCC |
Output
A size report naming the top flash and RAM consumers from the map file, the flags applied, the measured stack bound, and the per-file optimization split.