Paste the .map your linker already wrote. Get the exact bytes: which region
overflowed, and which object file spent them. Runs entirely in this tab - the file never leaves your machine.
Everything above is free, for every map file you have, forever. The full version answers the question one build cannot: what changed since last time.
{"FLASH":"90%","RAM":"7K"}) that pass or faillinker-map-report.json + .md for CI and for your team$29 once - one licence key per person or team seat - 7-day full refund. A freelance embedded engineer averages $103/hr (contractrates.fyi, 2026); this is about 17 minutes of one.
Works with any GNU ld map: arm-none-eabi, riscv, avr, msp430, and the map files written by STM32CubeIDE, MCUXpresso and PlatformIO.
This page is the working piece. The full pack has everything below.
Read the .map your linker already wrote and see, in exact bytes, which object file is spending your FLASH and RAM - and by how much you overflowed.
A freelance embedded software engineer averages $103.27/hour (contractrates.fyi crowdsourced rates, 2026); one afternoon spent hand-reading a map file costs more than this tool.
Buy the full version โ $29ยท ReadyStack
Real numbers from this tool, line by line.

RAM 8,872 of 8,192 bytes, over by 680 - that is the complete answer a firmware engineer needs when arm-none-eabi-ld refuses to link, and it is the one line the linker never prints. What it prints instead is region 'RAM' overflowed by 680 bytes, and then it stops. It tells you that you lost. It does not tell you who took it.
The information is not missing. It is sitting in build/app.map, the file your build already wrote, twenty thousand lines long, in hexadecimal, with section sizes on one line and the object file that owns them on another. Everything you need is public, in your own repository, and nobody looks.
Three things, and they have to be combined:
Memory Configuration block: the real origin and length of every region.load address.Combining them is the whole job, and it has two traps.
.data is charged twice. Its variables live in RAM at run time, and its initialiser sits in FLASH so it can be copied there at boot. A script that counts it once is wrong by exactly the size of your initialised globals. In the build above that is 612 bytes on both sides.
Debug sections are charged to nothing. .debug_info, .comment and .ARM.attributes are laid down at address 0, inside no region, and never reach the device. Count them and a build that added debug symbols appears to have grown by forty kilobytes it did not grow by.
REGION USED SIZE FREE USED% STATUS
FLASH 15,088 65,536 50,448 23.0% ok
RAM 8,872 8,192 -680 108.3% OVER by 680 B
RAM - top spenders
4,096 uart.o .bss.uart_rx_buf
2,048 log.o .bss.log_pool
640 app.o .data.cfg, COMMON
448 libhal.a(stm32f4xx_hal.o) .bss.hal_state
100 main.o .data.tick
1,540 (not attributed) padding, heap and stack reservations
Now the fix is obvious rather than archaeological: one UART ring buffer is holding half of an 8 KB device. Halve it and the build fits with room to spare - 5,800 of 8,192 bytes, 70.8%.
Notice the last row. Alignment padding and the _Min_Heap_Size / _Min_Stack_Size reservations belong to no object file, so most hand-written scripts quietly drop them and their columns stop adding up. Here they get their own line, and the parts sum to the region total exactly: 7,332 attributed plus 1,540 unattributed is 8,872.
Because the task is adding twenty thousand hexadecimal numbers and grouping them by owner. A language model will give you a confident total, and you will have no way to tell that it drifted. This is arithmetic; it wants a parser, not a guess. Bloaty and puncover do it well but want the ELF and a matching toolchain; the .map file is the artefact you already have, including on a Windows machine with nothing installed.
Build with -Wl,-Map=build/app.map, then paste the file into the free web version, or open it in VS Code and run Linker Map: Analyze this linker map. Reading one map file is free, with no key and no limit. What one build cannot tell you is what changed since the build that fit - and that comparison, per-region budgets, and a CI-ready report are what the licence adds.
It reads the .map file GNU ld writes with -Wl,-Map and turns it into a byte-exact budget: every MEMORY region's capacity, used bytes, free bytes and overflow, plus every object file, archive member and input section ranked by what it spends in that region. The .data section is charged to both FLASH and RAM, because both costs are real.
Embedded firmware engineers on microcontrollers with a fixed FLASH and RAM budget: STM32, nRF, ESP32, AVR, RISC-V, built with arm-none-eabi-gcc, STM32CubeIDE, MCUXpresso, Zephyr or PlatformIO. It is for the moment the linker prints region RAM overflowed by 680 bytes and you must find which of two hundred object files is responsible.
A chat model cannot reliably add twenty thousand hexadecimal sizes; it returns a confident total you cannot check. Bloaty and puncover need the ELF plus a matching toolchain, and MemBrowse is a CI service to wire up. The .map file is already on your disk, and this reads it in the editor already open, on Windows, with nothing installed.
Analysing one map file is free forever: no key, no watermark, no file limit, the full region table and the full contributor ranking. The licence adds what a single build cannot show: saving a baseline, diffing a later build to see which object grew, per-region budgets that pass or fail, and JSON plus Markdown export for CI.
Hand-reading a linker map means summing hexadecimal sizes and attributing them across a file routinely twenty thousand lines long. A freelance embedded software engineer averages $103.27 an hour on crowdsourced 2026 rate data, so one afternoon of that hunt costs several hundred dollars, and it repeats every time the firmware grows.
One question, answered by the person who built it. Your email only if you want the answer sent.