Download index.html from getreadystack/linker-map-auditor: direct link, hf CLI and curl.
- Browser
- Download file 65.6 kB
-
https://huggingface.co/spaces/getreadystack/linker-map-auditor/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/linker-map-auditor/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/linker-map-auditor/resolve/main/index.html
65.6 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Linker Map Auditor - what is eating your FLASH and RAM</title> | |
| <meta name="description" content="Paste a GNU ld .map file and see, in exact bytes, which object file and which archive is spending your FLASH and RAM - and by how much you overflowed."> | |
| <style> | |
| :root{--bg:#0e1116;--panel:#161b22;--line:#272e38;--fg:#e6edf3;--dim:#8b949e; | |
| --ok:#3fb950;--bad:#f85149;--warn:#d29922;--acc:#58a6ff} | |
| *{box-sizing:border-box} | |
| body{margin:0;background:var(--bg);color:var(--fg); | |
| font:15px/1.5 ui-sans-serif,system-ui,"Segoe UI",Roboto,sans-serif} | |
| .wrap{max-width:1080px;margin:0 auto;padding:28px 20px 64px} | |
| h1{font-size:26px;margin:0 0 6px} | |
| .sub{color:var(--dim);margin:0 0 22px} | |
| .panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:18px;margin-bottom:16px} | |
| .verdict{font-size:22px;font-weight:700;margin:0 0 14px} | |
| .verdict.bad{color:var(--bad)} .verdict.ok{color:var(--ok)} | |
| table{width:100%;border-collapse:collapse;font:13px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace} | |
| th,td{padding:6px 8px;border-bottom:1px solid var(--line);text-align:right;white-space:nowrap} | |
| th:first-child,td:first-child{text-align:left} | |
| th{color:var(--dim);font-weight:600;font-size:11px;letter-spacing:.04em;text-transform:uppercase} | |
| td.name{white-space:normal;word-break:break-all} | |
| .bar{height:7px;background:#20262e;border-radius:4px;overflow:hidden;margin-top:5px;min-width:120px} | |
| .bar>i{display:block;height:100%;background:var(--ok)} | |
| .bar>i.bad{background:var(--bad)} .bar>i.warn{background:var(--warn)} | |
| .grid{display:grid;grid-template-columns:1fr 1fr;gap:16px} | |
| @media(max-width:800px){.grid{grid-template-columns:1fr}} | |
| h3{font-size:13px;color:var(--dim);text-transform:uppercase;letter-spacing:.04em;margin:0 0 8px} | |
| label{display:block;font-size:12px;color:var(--dim);margin:0 0 5px} | |
| select,textarea,input{width:100%;background:#0d1117;color:var(--fg);border:1px solid var(--line); | |
| border-radius:7px;padding:9px 10px;font:13px ui-monospace,Menlo,Consolas,monospace} | |
| textarea{min-height:132px;resize:vertical} | |
| button{background:var(--acc);color:#06121f;border:0;border-radius:7px;padding:10px 18px; | |
| font-size:14px;font-weight:700;cursor:pointer} | |
| .row{display:flex;gap:12px;align-items:flex-end;margin-top:12px;flex-wrap:wrap} | |
| .row>div{flex:1;min-width:180px} | |
| .buy{border:1px solid #2b4a6f;background:#111a24} | |
| .buy ul{margin:8px 0 12px;padding-left:18px;color:var(--fg)} | |
| .buy li{margin:3px 0;font-size:14px} | |
| .buy a.cta{display:inline-block;background:var(--acc);color:#06121f;text-decoration:none; | |
| font-weight:700;padding:10px 18px;border-radius:7px} | |
| .terms{color:var(--dim);font-size:12px;margin-top:10px} | |
| .note{color:var(--dim);font-size:12px;margin-top:10px} | |
| code{background:#0d1117;border:1px solid var(--line);border-radius:4px;padding:1px 5px;font-size:12px} | |
| footer{color:var(--dim);font-size:12px;margin-top:28px;border-top:1px solid var(--line);padding-top:14px} | |
| </style> | |
| <script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script> | |
| <!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/linker-map-auditor"></head> | |
| <body> | |
| <div class="wrap"> | |
| <h1>Linker Map Auditor</h1> | |
| <p class="sub">Paste the <code>.map</code> 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.</p> | |
| <div class="panel" id="result"></div> | |
| <div class="panel buy"> | |
| <h3>Free here, for one build</h3> | |
| <p style="margin:0 0 4px">Everything above is free, for every map file you have, forever. The full version | |
| answers the question one build cannot: <strong>what changed since last time.</strong></p> | |
| <ul> | |
| <li>Save a known-good build as a <strong>baseline</strong> and diff any later build against it</li> | |
| <li>See the exact object file that grew, and by how many bytes</li> | |
| <li>Per-region <strong>budgets</strong> (<code>{"FLASH":"90%","RAM":"7K"}</code>) that pass or fail</li> | |
| <li>Export <code>linker-map-report.json</code> + <code>.md</code> for CI and for your team</li> | |
| </ul> | |
| <a class="cta" href="https://getreadystack.com/api/buy/cl/polar_cl_2x41azB4vGEeUd5HdFkPGOiAorn6HYGk0WDro17qEb0">Get the full version - $29</a> | |
| <p class="terms">$29 once - one licence key per person or team seat. | |
| A freelance embedded engineer averages $103/hr (contractrates.fyi, 2026); this is about 17 minutes of one.</p> | |
| </div> | |
| <div class="panel"> | |
| <h3>Your map file</h3> | |
| <div class="row"> | |
| <div> | |
| <label for="example">Example build</label> | |
| <select id="example"><option value="over">Build A - 16K/8K MCU, RAM overflows</option><option value="base">Build B - the same firmware one week earlier</option><option value="fixed">Build C - after shrinking the UART and log buffers</option></select> | |
| </div> | |
| <div style="max-width:150px"> | |
| <label for="topn">Top spenders</label> | |
| <input id="topn" type="number" min="1" max="60" value="12"> | |
| </div> | |
| <div style="flex:0 0 auto"><button id="run">Analyze this map</button></div> | |
| </div> | |
| <div style="margin-top:12px"> | |
| <label for="maptext">Or paste your own map file (generated with <code>-Wl,-Map=build/app.map</code>)</label> | |
| <textarea id="maptext" spellcheck="false"></textarea> | |
| </div> | |
| <p class="note">Works with any GNU ld map: arm-none-eabi, riscv, avr, msp430, and the map files written by | |
| STM32CubeIDE, MCUXpresso and PlatformIO.</p> | |
| </div> | |
| <footer>Linker Map Auditor - the same parser that runs in the VS Code extension runs on this page.</footer> | |
| </div> | |
| <script>; | |
| /* | |
| * mapparse.js - the one brain. | |
| * Parses a GNU ld map file (arm-none-eabi-ld, riscv64-unknown-elf-ld, avr-ld, ...) | |
| * produced by `-Wl,-Map=out.map`, and answers, in exact bytes: | |
| * - how much of each MEMORY region is used, free, or overflowed | |
| * - which object files / archive members / input sections are spending it | |
| * - what changed since a saved baseline | |
| * | |
| * The same file is loaded by extension.js (CommonJS) and inlined into | |
| * index.html (browser global MapParse). One brain, two doors. | |
| */ | |
| (function (root, factory) { | |
| if (typeof module === 'object' && module.exports) module.exports = factory(); | |
| else root.MapParse = factory(); | |
| }(typeof self !== 'undefined' ? self : this, function () { | |
| var VERSION = '0.1.0'; | |
| // -- helpers --------------------------------------------------------- | |
| function hex(s) { return parseInt(s, 16); } | |
| function commas(n) { | |
| var neg = n < 0; n = Math.abs(Math.round(n)); | |
| var s = String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ','); | |
| return (neg ? '-' : '') + s; | |
| } | |
| function humanBytes(n) { | |
| var a = Math.abs(n); | |
| if (a >= 1048576) return (n / 1048576).toFixed(2) + ' MiB'; | |
| if (a >= 1024) return (n / 1024).toFixed(2) + ' KiB'; | |
| return commas(n) + ' B'; | |
| } | |
| // "512K" | "0x8000" | "90%" | 8192 -> { bytes, text } | |
| function parseBudget(v, regionLength) { | |
| if (v === null || v === undefined || v === '') return null; | |
| if (typeof v === 'number') return { bytes: Math.round(v), text: commas(v) + ' B' }; | |
| var s = String(v).trim(); | |
| var m = s.match(/^(\d+(?:\.\d+)?)\s*%$/); | |
| if (m) { | |
| if (!regionLength) return null; | |
| return { bytes: Math.floor(regionLength * parseFloat(m[1]) / 100), text: m[1] + '%' }; | |
| } | |
| m = s.match(/^0x([0-9a-fA-F]+)$/); | |
| if (m) return { bytes: hex(m[1]), text: s }; | |
| m = s.match(/^(\d+(?:\.\d+)?)\s*([KMkm])?(?:i?[Bb])?$/); | |
| if (m) { | |
| var n = parseFloat(m[1]); | |
| var unit = (m[2] || '').toUpperCase(); | |
| if (unit === 'K') n *= 1024; | |
| else if (unit === 'M') n *= 1048576; | |
| return { bytes: Math.round(n), text: s }; | |
| } | |
| return null; | |
| } | |
| // "Drivers/libhal.a(stm32f4xx_hal.o)" -> archive + member | |
| function splitObject(spec) { | |
| var m = spec.match(/^(.*?)([^\/\\]+\.a)\(([^)]+)\)\s*$/); | |
| if (m) return { path: spec, archive: m[2], member: m[3], label: m[2] + '(' + m[3] + ')' }; | |
| var base = spec.replace(/^.*[\/\\]/, ''); | |
| return { path: spec, archive: null, member: base, label: base }; | |
| } | |
| // -- the parser ------------------------------------------------------ | |
| var RE_REGION = /^(\S+)\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)(?:\s+(\S+))?\s*$/; | |
| var RE_OUT_FULL = /^([.$\w][^\s]*)\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)(?:\s+load address\s+0x([0-9a-fA-F]+))?\s*$/; | |
| var RE_OUT_NAME = /^([.$\w][^\s]*)\s*$/; | |
| var RE_OUT_TAIL = /^\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)(?:\s+load address\s+0x([0-9a-fA-F]+))?\s*$/; | |
| var RE_IN_FULL = /^\s+(\S+)\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s+(\S.*?)\s*$/; | |
| var RE_IN_FILL = /^\s+(\*fill\*)\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s*$/; | |
| var RE_IN_NAME = /^\s+(\.[^\s]+|COMMON|\*fill\*)\s*$/; | |
| var RE_IN_TAIL = /^\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s+(\S.*?)\s*$/; | |
| var FILL_LABEL = '(alignment fill)'; | |
| var NOATTR_LABEL = '(not attributed to an input section)'; | |
| function parseMap(text, name) { | |
| var lines = String(text == null ? '' : text).replace(/\r\n?/g, '\n').split('\n'); | |
| var regions = [], sections = [], inputs = [], warnings = []; | |
| // 1) Memory Configuration | |
| var i = 0, inMem = false; | |
| for (; i < lines.length; i++) { | |
| var ln = lines[i]; | |
| if (/^Memory Configuration\s*$/.test(ln)) { inMem = true; continue; } | |
| if (!inMem) continue; | |
| if (/^Linker script and memory map\s*$/.test(ln)) break; | |
| if (/^Name\s+Origin\s+Length/.test(ln)) continue; | |
| var mr = ln.match(RE_REGION); | |
| if (!mr || mr[1] === '*default*') continue; | |
| var origin = hex(mr[2]), length = hex(mr[3]); | |
| if (!isFinite(origin) || !isFinite(length) || length <= 0) continue; | |
| regions.push({ | |
| name: mr[1], origin: origin, length: length, | |
| attrs: mr[4] || '', used: 0, unattributed: 0 | |
| }); | |
| } | |
| if (!regions.length) { | |
| warnings.push('No "Memory Configuration" block found - is this a GNU ld map file? ' + | |
| 'Region totals cannot be computed without it.'); | |
| } | |
| function regionOf(addr) { | |
| for (var k = 0; k < regions.length; k++) { | |
| var r = regions[k]; | |
| if (addr >= r.origin && addr < r.origin + r.length) return r.name; | |
| } | |
| return null; | |
| } | |
| // 2) the map body | |
| var cur = null, pendingOut = null, pendingIn = null; | |
| function openSection(nm, addr, size, load) { | |
| var regs = [], rv = regionOf(addr); | |
| if (rv) regs.push(rv); | |
| if (load !== null && load !== undefined) { | |
| var rl = regionOf(load); | |
| if (rl && regs.indexOf(rl) === -1) regs.push(rl); | |
| } | |
| cur = { name: nm, addr: addr, size: size, load: (load === undefined ? null : load), | |
| regions: regs, attributed: 0 }; | |
| sections.push(cur); | |
| // A section's own size is what the region actually spends (it includes | |
| // padding no input section claims), so region totals come from here. | |
| for (var k = 0; k < regs.length; k++) { | |
| for (var j = 0; j < regions.length; j++) { | |
| if (regions[j].name === regs[k]) regions[j].used += size; | |
| } | |
| } | |
| } | |
| function addInput(nm, addr, size, spec) { | |
| if (!cur || !size) return; | |
| var obj = spec === null | |
| ? { path: FILL_LABEL, archive: null, member: FILL_LABEL, label: FILL_LABEL } | |
| : splitObject(spec); | |
| cur.attributed += size; | |
| inputs.push({ section: nm, addr: addr, size: size, object: obj.label, path: obj.path, | |
| archive: obj.archive, regions: cur.regions.slice(), parent: cur.name }); | |
| } | |
| for (; i < lines.length; i++) { | |
| var line = lines[i]; | |
| if (!line) { pendingOut = null; pendingIn = null; continue; } | |
| if (/^\s/.test(line)) { | |
| // indented: input section, or the tail of a wrapped name | |
| var mf = line.match(RE_IN_FILL); | |
| if (mf) { addInput('*fill*', hex(mf[2]), hex(mf[3]), null); pendingIn = null; pendingOut = null; continue; } | |
| if (pendingOut !== null) { | |
| var mot = line.match(RE_OUT_TAIL); | |
| if (mot) { | |
| openSection(pendingOut, hex(mot[1]), hex(mot[2]), mot[3] ? hex(mot[3]) : null); | |
| pendingOut = null; pendingIn = null; continue; | |
| } | |
| pendingOut = null; | |
| } | |
| if (pendingIn !== null) { | |
| var mit = line.match(RE_IN_TAIL); | |
| if (mit) { addInput(pendingIn, hex(mit[1]), hex(mit[2]), mit[3]); pendingIn = null; continue; } | |
| pendingIn = null; | |
| } | |
| var mi = line.match(RE_IN_FULL); | |
| if (mi && !/^0x/.test(mi[1])) { addInput(mi[1], hex(mi[2]), hex(mi[3]), mi[4]); continue; } | |
| var mn = line.match(RE_IN_NAME); | |
| if (mn) { pendingIn = mn[1]; continue; } | |
| continue; | |
| } | |
| // column 0: an output section (or linker noise we ignore) | |
| pendingIn = null; | |
| var mo = line.match(RE_OUT_FULL); | |
| if (mo) { | |
| openSection(mo[1], hex(mo[2]), hex(mo[3]), mo[4] ? hex(mo[4]) : null); | |
| pendingOut = null; | |
| continue; | |
| } | |
| var mnm = line.match(RE_OUT_NAME); | |
| if (mnm && /^[.$]/.test(mnm[1])) { pendingOut = mnm[1]; continue; } | |
| pendingOut = null; | |
| } | |
| // 3) roll up | |
| var byObject = {}, byArchive = {}, bySection = {}; | |
| function bump(store, key, region, size, extra) { | |
| var e = store[key]; | |
| if (!e) { | |
| e = store[key] = { name: key, total: 0, byRegion: {}, sections: {}, sectionsByRegion: {} }; | |
| if (extra) for (var k in extra) if (Object.prototype.hasOwnProperty.call(extra, k)) e[k] = extra[k]; | |
| } | |
| e.total += size; | |
| e.byRegion[region] = (e.byRegion[region] || 0) + size; | |
| return e; | |
| } | |
| for (var n = 0; n < inputs.length; n++) { | |
| var it = inputs[n]; | |
| var regs = it.regions.length ? it.regions : ['(outside any region)']; | |
| for (var q = 0; q < regs.length; q++) { | |
| var rg = regs[q]; | |
| var eo = bump(byObject, it.object, rg, it.size, { archive: it.archive, path: it.path }); | |
| eo.sections[it.section] = (eo.sections[it.section] || 0) + it.size; | |
| // Per region as well: listing an object's FLASH sections inside the RAM | |
| // table told the customer to go shrink a section that costs no RAM. | |
| if (!eo.sectionsByRegion[rg]) eo.sectionsByRegion[rg] = {}; | |
| eo.sectionsByRegion[rg][it.section] = (eo.sectionsByRegion[rg][it.section] || 0) + it.size; | |
| if (it.archive) bump(byArchive, it.archive, rg, it.size); | |
| bump(bySection, it.section + ' ' + it.object, rg, it.size, | |
| { section: it.section, object: it.object }); | |
| } | |
| } | |
| // what no input section claimed, per region (padding, heap/stack reservations) | |
| for (var s = 0; s < sections.length; s++) { | |
| var sec = sections[s]; | |
| var gap = sec.size - sec.attributed; | |
| if (gap <= 0) continue; | |
| for (var g = 0; g < sec.regions.length; g++) { | |
| for (var h = 0; h < regions.length; h++) { | |
| if (regions[h].name === sec.regions[g]) regions[h].unattributed += gap; | |
| } | |
| } | |
| } | |
| for (var r2 = 0; r2 < regions.length; r2++) { | |
| var R = regions[r2]; | |
| R.free = R.length - R.used; | |
| R.pct = R.length ? (R.used * 100 / R.length) : 0; | |
| R.over = R.used > R.length ? (R.used - R.length) : 0; | |
| } | |
| function sortDesc(store) { | |
| var arr = []; | |
| for (var k in store) if (Object.prototype.hasOwnProperty.call(store, k)) arr.push(store[k]); | |
| arr.sort(function (a, b) { return b.total - a.total; }); | |
| return arr; | |
| } | |
| return { | |
| version: VERSION, | |
| name: name || '', | |
| regions: regions, | |
| sections: sections, | |
| inputs: inputs, | |
| objects: sortDesc(byObject), | |
| archives: sortDesc(byArchive), | |
| inputSections: sortDesc(bySection), | |
| warnings: warnings, | |
| stats: { | |
| lines: lines.length, sections: sections.length, inputs: inputs.length, | |
| objects: Object.keys(byObject).length, archives: Object.keys(byArchive).length | |
| } | |
| }; | |
| } | |
| // top spenders inside one region | |
| function topIn(parsed, regionName, limit) { | |
| var out = []; | |
| for (var i = 0; i < parsed.objects.length; i++) { | |
| var o = parsed.objects[i], v = o.byRegion[regionName]; | |
| if (v) { | |
| var secs = (o.sectionsByRegion && o.sectionsByRegion[regionName]) || o.sections; | |
| out.push({ name: o.name, size: v, sections: secs, archive: o.archive }); | |
| } | |
| } | |
| out.sort(function (a, b) { return b.size - a.size; }); | |
| return limit ? out.slice(0, limit) : out; | |
| } | |
| function regionByName(parsed, nm) { | |
| for (var i = 0; i < parsed.regions.length; i++) if (parsed.regions[i].name === nm) return parsed.regions[i]; | |
| return null; | |
| } | |
| // -- paid: budgets --------------------------------------------------- | |
| function checkBudgets(parsed, budgets) { | |
| var rows = [], failed = 0; | |
| for (var i = 0; i < parsed.regions.length; i++) { | |
| var R = parsed.regions[i]; | |
| var has = budgets && Object.prototype.hasOwnProperty.call(budgets, R.name); | |
| var b = has ? parseBudget(budgets[R.name], R.length) : null; | |
| var limit = b ? b.bytes : R.length; | |
| var ok = R.used <= limit; | |
| if (!ok) failed++; | |
| rows.push({ region: R.name, used: R.used, limit: limit, capacity: R.length, | |
| budget: b ? b.text : '(region size)', over: ok ? 0 : R.used - limit, | |
| headroom: limit - R.used, pct: limit ? R.used * 100 / limit : 0, ok: ok }); | |
| } | |
| return { ok: failed === 0, failed: failed, rows: rows }; | |
| } | |
| // -- paid: baseline diff --------------------------------------------- | |
| function diffMaps(base, head) { | |
| var regions = [], seen = {}; | |
| function pushRegion(nm) { | |
| if (seen[nm]) return; seen[nm] = 1; | |
| var a = regionByName(base, nm), b = regionByName(head, nm); | |
| regions.push({ | |
| name: nm, before: a ? a.used : 0, after: b ? b.used : 0, | |
| delta: (b ? b.used : 0) - (a ? a.used : 0), | |
| capacity: (b || a).length, | |
| wasOver: a ? a.over > 0 : false, isOver: b ? b.over > 0 : false, | |
| over: b ? b.over : 0 | |
| }); | |
| } | |
| for (var i = 0; i < base.regions.length; i++) pushRegion(base.regions[i].name); | |
| for (var j = 0; j < head.regions.length; j++) pushRegion(head.regions[j].name); | |
| // Only real MEMORY regions are compared. Debug sections (.debug_info, | |
| // .comment, .ARM.attributes) sit at address 0, belong to no region and | |
| // never reach the device. Counting them would report a 40 KB 'growth' | |
| // that costs the customer nothing - worse than saying nothing at all. | |
| var realRegions = {}; | |
| for (var rr = 0; rr < regions.length; rr++) realRegions[regions[rr].name] = 1; | |
| // region -> object -> bytes. Nested, so no separator can collide with a | |
| // region or object name that contains spaces. | |
| function index(parsed) { | |
| var m = {}; | |
| for (var k = 0; k < parsed.objects.length; k++) { | |
| var o = parsed.objects[k]; | |
| for (var rg in o.byRegion) { | |
| if (!Object.prototype.hasOwnProperty.call(o.byRegion, rg)) continue; | |
| if (!realRegions[rg]) continue; | |
| if (!m[rg]) m[rg] = {}; | |
| m[rg][o.name] = o.byRegion[rg]; | |
| } | |
| } | |
| return m; | |
| } | |
| var A = index(base), B = index(head), objects = []; | |
| for (var rn = 0; rn < regions.length; rn++) { | |
| var rgn = regions[rn].name; | |
| var a = A[rgn] || {}, b = B[rgn] || {}, names = {}; | |
| for (var na in a) if (Object.prototype.hasOwnProperty.call(a, na)) names[na] = 1; | |
| for (var nb in b) if (Object.prototype.hasOwnProperty.call(b, nb)) names[nb] = 1; | |
| for (var nm2 in names) { | |
| if (!Object.prototype.hasOwnProperty.call(names, nm2)) continue; | |
| var before = a[nm2] || 0, after = b[nm2] || 0; | |
| if (before === after) continue; | |
| objects.push({ region: rgn, name: nm2, before: before, after: after, | |
| delta: after - before, | |
| state: before === 0 ? 'added' : (after === 0 ? 'removed' : 'changed') }); | |
| } | |
| } | |
| objects.sort(function (a, b) { return Math.abs(b.delta) - Math.abs(a.delta); }); | |
| return { regions: regions, objects: objects }; | |
| } | |
| // -- text report (the free answer) ----------------------------------- | |
| function pad(s, w, right) { | |
| s = String(s); | |
| if (s.length >= w) return s; | |
| var sp = new Array(w - s.length + 1).join(' '); | |
| return right ? sp + s : s + sp; | |
| } | |
| function formatReport(parsed, opts) { | |
| opts = opts || {}; | |
| var top = opts.top || 12; | |
| var L = []; | |
| L.push('Linker Map Auditor' + (parsed.name ? ' - ' + parsed.name : '')); | |
| L.push(parsed.stats.sections + ' output sections, ' + parsed.stats.inputs + | |
| ' input sections, ' + parsed.stats.objects + ' objects, ' + | |
| parsed.stats.archives + ' archives'); | |
| L.push(''); | |
| if (!parsed.regions.length) { | |
| L.push('No MEMORY regions found in this file.'); | |
| for (var w = 0; w < parsed.warnings.length; w++) L.push('! ' + parsed.warnings[w]); | |
| return L.join('\n'); | |
| } | |
| L.push(pad('REGION', 12) + pad('USED', 12, true) + pad('SIZE', 12, true) + | |
| pad('FREE', 12, true) + pad('USED%', 9, true) + ' STATUS'); | |
| for (var i = 0; i < parsed.regions.length; i++) { | |
| var R = parsed.regions[i]; | |
| L.push(pad(R.name, 12) + pad(commas(R.used), 12, true) + pad(commas(R.length), 12, true) + | |
| pad(commas(R.free), 12, true) + pad(R.pct.toFixed(1) + '%', 9, true) + ' ' + | |
| (R.over ? ('OVER by ' + commas(R.over) + ' B') : 'ok')); | |
| } | |
| for (var k = 0; k < parsed.regions.length; k++) { | |
| var Rg = parsed.regions[k]; | |
| if (!Rg.used) continue; | |
| L.push(''); | |
| L.push(Rg.name + ' - top spenders (' + humanBytes(Rg.used) + ' used)'); | |
| var rows = topIn(parsed, Rg.name, top); | |
| for (var m = 0; m < rows.length; m++) { | |
| var sec = rows[m].sections; | |
| var secNames = Object.keys(sec).sort(function (a, b) { return sec[b] - sec[a]; }) | |
| .slice(0, 3).join(', '); | |
| L.push(' ' + pad(commas(rows[m].size), 10, true) + ' ' + pad(rows[m].name, 34) + ' ' + secNames); | |
| } | |
| if (Rg.unattributed) { | |
| L.push(' ' + pad(commas(Rg.unattributed), 10, true) + ' ' + NOATTR_LABEL + | |
| ' (padding, heap/stack reservations)'); | |
| } | |
| } | |
| for (var w2 = 0; w2 < parsed.warnings.length; w2++) L.push('! ' + parsed.warnings[w2]); | |
| return L.join('\n'); | |
| } | |
| return { | |
| VERSION: VERSION, | |
| parseMap: parseMap, | |
| formatReport: formatReport, | |
| diffMaps: diffMaps, | |
| checkBudgets: checkBudgets, | |
| topIn: topIn, | |
| regionByName: regionByName, | |
| humanBytes: humanBytes, | |
| parseBudget: parseBudget, | |
| commas: commas | |
| }; | |
| })); | |
| </script> | |
| <script> | |
| var EXAMPLES = {"over": {"label": "Build A - 16K/8K MCU, RAM overflows", "text": "Archive member included to satisfy reference by file (symbol)\n\nDrivers/libhal.a(stm32f4xx_hal.o)\n build/main.o (HAL_Init)\n/usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n build/log.o (printf)\n\nAllocating common symbols\nCommon symbol size file\n\napp_state 0x80 build/app.o\n\nDiscarded input sections\n\n .text 0x0000000000000000 0x0 build/main.o\n .data 0x0000000000000000 0x0 build/main.o\n\nMemory Configuration\n\nName Origin Length Attributes\nFLASH 0x0000000008000000 0x0000000000010000 xr\nRAM 0x0000000020000000 0x0000000000002000 xrw\n*default* 0x0000000000000000 0xffffffffffffffff\n\nLinker script and memory map\n\nLOAD build/startup_stm32f0xx.o\nLOAD build/main.o\nLOAD build/app.o\nLOAD build/uart.o\nLOAD build/log.o\nLOAD build/system_stm32f4xx.o\nSTART GROUP\nLOAD Drivers/libhal.a\nLOAD /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a\nEND GROUP\n\n.isr_vector 0x0000000008000000 0x188\n *(.isr_vector)\n .isr_vector 0x0000000008000000 0x188 build/startup_stm32f0xx.o\n 0x0000000008000000 g_pfnVectors\n\n.text 0x0000000008000188 0x24d0\n *(.text)\n *(.text*)\n .text.main 0x0000000008000188 0xd4 build/main.o\n 0x0000000008000188 main\n .text.HAL_Init\n 0x000000000800025c 0x2c Drivers/libhal.a(stm32f4xx_hal.o)\n 0x000000000800025c HAL_Init\n .text.HAL_RCC_OscConfig\n 0x0000000008000288 0x3a8 Drivers/libhal.a(stm32f4xx_hal_rcc.o)\n 0x0000000008000288 HAL_RCC_OscConfig\n .text.SystemInit\n 0x0000000008000630 0x68 build/system_stm32f4xx.o\n 0x0000000008000630 SystemInit\n .text.printf_core\n 0x0000000008000698 0x1f40 /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n 0x0000000008000698 _printf_core\n *fill* 0x00000000080025d8 0x8\n .text.app_loop\n 0x00000000080025e0 0x54 build/app.o\n 0x00000000080025e0 app_loop\n 0x0000000008002658 . = ALIGN (0x4)\n\n.rodata 0x0000000008002658 0x1234\n *(.rodata)\n *(.rodata*)\n .rodata.str1.4\n 0x0000000008002658 0x1000 /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n .rodata.table 0x0000000008003658 0x234 build/app.o\n 0x0000000008003658 lookup_table\n\n.data 0x0000000020000000 0x264 load address 0x000000000800388c\n 0x0000000020000000 _sdata = .\n *(.data)\n *(.data*)\n .data.cfg 0x0000000020000000 0x200 build/app.o\n 0x0000000020000000 g_cfg\n .data.tick 0x0000000020000200 0x64 build/main.o\n 0x0000000020000200 g_tick\n 0x0000000020000264 _edata = .\n\n.bss 0x0000000020000268 0x1a40\n 0x0000000020000268 _sbss = .\n *(.bss)\n *(.bss*)\n .bss.uart_rx_buf\n 0x0000000020000268 0x1000 build/uart.o\n 0x0000000020000268 uart_rx_buf\n .bss.log_pool 0x0000000020001268 0x800 build/log.o\n 0x0000000020001268 log_pool\n .bss.hal_state\n 0x0000000020001a68 0x1c0 Drivers/libhal.a(stm32f4xx_hal.o)\n 0x0000000020001a68 hal_state\n COMMON 0x0000000020001c28 0x80 build/app.o\n 0x0000000020001c28 app_state\n 0x0000000020001ca8 _ebss = .\n\n._user_heap_stack\n 0x0000000020001ca8 0x604\n 0x0000000020001ca8 . = ALIGN (0x8)\n 0x00000000200020ac . = (. + _Min_Heap_Size)\n 0x00000000200022ac . = (. + _Min_Stack_Size)\n\n.comment 0x0000000000000000 0x43\n .comment 0x0000000000000000 0x43 build/main.o\n\n.ARM.attributes\n 0x0000000000000000 0x33\n .ARM.attributes\n 0x0000000000000000 0x33 build/main.o\n\n.debug_info 0x0000000000000000 0x9c40\n .debug_info 0x0000000000000000 0x9c40 build/main.o\n\nOUTPUT(build/app.elf elf32-littlearm)\n"}, "base": {"label": "Build B - the same firmware one week earlier", "text": "Archive member included to satisfy reference by file (symbol)\n\nDrivers/libhal.a(stm32f4xx_hal.o)\n build/main.o (HAL_Init)\n/usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n build/log.o (printf)\n\nAllocating common symbols\nCommon symbol size file\n\napp_state 0x80 build/app.o\n\nMemory Configuration\n\nName Origin Length Attributes\nFLASH 0x0000000008000000 0x0000000000010000 xr\nRAM 0x0000000020000000 0x0000000000002000 xrw\n*default* 0x0000000000000000 0xffffffffffffffff\n\nLinker script and memory map\n\nLOAD build/startup_stm32f0xx.o\nLOAD build/main.o\nSTART GROUP\nLOAD Drivers/libhal.a\nLOAD /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a\nEND GROUP\n\n.isr_vector 0x0000000008000000 0x188\n *(.isr_vector)\n .isr_vector 0x0000000008000000 0x188 build/startup_stm32f0xx.o\n 0x0000000008000000 g_pfnVectors\n\n.text 0x0000000008000188 0x20d0\n *(.text)\n *(.text*)\n .text.main 0x0000000008000188 0xd4 build/main.o\n 0x0000000008000188 main\n .text.HAL_Init\n 0x000000000800025c 0x2c Drivers/libhal.a(stm32f4xx_hal.o)\n 0x000000000800025c HAL_Init\n .text.HAL_RCC_OscConfig\n 0x0000000008000288 0x3a8 Drivers/libhal.a(stm32f4xx_hal_rcc.o)\n 0x0000000008000288 HAL_RCC_OscConfig\n .text.SystemInit\n 0x0000000008000630 0x68 build/system_stm32f4xx.o\n 0x0000000008000630 SystemInit\n .text.printf_core\n 0x0000000008000698 0x1b40 /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n 0x0000000008000698 _printf_core\n *fill* 0x00000000080021d8 0x8\n .text.app_loop\n 0x00000000080021e0 0x54 build/app.o\n 0x00000000080021e0 app_loop\n 0x0000000008002258 . = ALIGN (0x4)\n\n.rodata 0x0000000008002258 0x1234\n *(.rodata)\n *(.rodata*)\n .rodata.str1.4\n 0x0000000008002258 0x1000 /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n .rodata.table 0x0000000008003258 0x234 build/app.o\n 0x0000000008003258 lookup_table\n\n.data 0x0000000020000000 0x264 load address 0x000000000800348c\n 0x0000000020000000 _sdata = .\n *(.data)\n *(.data*)\n .data.cfg 0x0000000020000000 0x200 build/app.o\n 0x0000000020000000 g_cfg\n .data.tick 0x0000000020000200 0x64 build/main.o\n 0x0000000020000200 g_tick\n 0x0000000020000264 _edata = .\n\n.bss 0x0000000020000268 0x1240\n 0x0000000020000268 _sbss = .\n *(.bss)\n *(.bss*)\n .bss.uart_rx_buf\n 0x0000000020000268 0x800 build/uart.o\n 0x0000000020000268 uart_rx_buf\n .bss.log_pool 0x0000000020000a68 0x800 build/log.o\n 0x0000000020000a68 log_pool\n .bss.hal_state\n 0x0000000020001268 0x1c0 Drivers/libhal.a(stm32f4xx_hal.o)\n 0x0000000020001268 hal_state\n COMMON 0x0000000020001428 0x80 build/app.o\n 0x0000000020001428 app_state\n 0x00000000200014a8 _ebss = .\n\n._user_heap_stack\n 0x00000000200014a8 0x604\n 0x00000000200014a8 . = ALIGN (0x8)\n 0x00000000200018a8 . = (. + _Min_Heap_Size)\n 0x0000000020001aac . = (. + _Min_Stack_Size)\n\nOUTPUT(build/app.elf elf32-littlearm)\n"}, "fixed": {"label": "Build C - after shrinking the UART and log buffers", "text": "Archive member included to satisfy reference by file (symbol)\n\nDrivers/libhal.a(stm32f4xx_hal.o)\n build/main.o (HAL_Init)\n/usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n build/log.o (printf)\n\nAllocating common symbols\nCommon symbol size file\n\napp_state 0x80 build/app.o\n\nMemory Configuration\n\nName Origin Length Attributes\nFLASH 0x0000000008000000 0x0000000000010000 xr\nRAM 0x0000000020000000 0x0000000000002000 xrw\n*default* 0x0000000000000000 0xffffffffffffffff\n\nLinker script and memory map\n\nLOAD build/startup_stm32f0xx.o\nLOAD build/main.o\nSTART GROUP\nLOAD Drivers/libhal.a\nLOAD /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a\nEND GROUP\n\n.isr_vector 0x0000000008000000 0x188\n *(.isr_vector)\n .isr_vector 0x0000000008000000 0x188 build/startup_stm32f0xx.o\n 0x0000000008000000 g_pfnVectors\n\n.text 0x0000000008000188 0x24d0\n *(.text)\n *(.text*)\n .text.main 0x0000000008000188 0xd4 build/main.o\n 0x0000000008000188 main\n .text.HAL_Init\n 0x000000000800025c 0x2c Drivers/libhal.a(stm32f4xx_hal.o)\n 0x000000000800025c HAL_Init\n .text.HAL_RCC_OscConfig\n 0x0000000008000288 0x3a8 Drivers/libhal.a(stm32f4xx_hal_rcc.o)\n 0x0000000008000288 HAL_RCC_OscConfig\n .text.SystemInit\n 0x0000000008000630 0x68 build/system_stm32f4xx.o\n 0x0000000008000630 SystemInit\n .text.printf_core\n 0x0000000008000698 0x1f40 /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n 0x0000000008000698 _printf_core\n *fill* 0x00000000080025d8 0x8\n .text.app_loop\n 0x00000000080025e0 0x54 build/app.o\n 0x00000000080025e0 app_loop\n 0x0000000008002658 . = ALIGN (0x4)\n\n.rodata 0x0000000008002658 0x1234\n *(.rodata)\n *(.rodata*)\n .rodata.str1.4\n 0x0000000008002658 0x1000 /usr/lib/gcc/arm-none-eabi/newlib/libc_nano.a(lib_a-printf.o)\n .rodata.table 0x0000000008003658 0x234 build/app.o\n 0x0000000008003658 lookup_table\n\n.data 0x0000000020000000 0x264 load address 0x000000000800388c\n 0x0000000020000000 _sdata = .\n *(.data)\n *(.data*)\n .data.cfg 0x0000000020000000 0x200 build/app.o\n 0x0000000020000000 g_cfg\n .data.tick 0x0000000020000200 0x64 build/main.o\n 0x0000000020000200 g_tick\n 0x0000000020000264 _edata = .\n\n.bss 0x0000000020000268 0xe40\n 0x0000000020000268 _sbss = .\n *(.bss)\n *(.bss*)\n .bss.uart_rx_buf\n 0x0000000020000268 0x800 build/uart.o\n 0x0000000020000268 uart_rx_buf\n .bss.log_pool 0x0000000020000a68 0x400 build/log.o\n 0x0000000020000a68 log_pool\n .bss.hal_state\n 0x0000000020000e68 0x1c0 Drivers/libhal.a(stm32f4xx_hal.o)\n 0x0000000020000e68 hal_state\n COMMON 0x0000000020001028 0x80 build/app.o\n 0x0000000020001028 app_state\n 0x00000000200010a8 _ebss = .\n\n._user_heap_stack\n 0x00000000200010a8 0x604\n 0x00000000200010a8 . = ALIGN (0x8)\n 0x00000000200014a8 . = (. + _Min_Heap_Size)\n 0x00000000200016ac . = (. + _Min_Stack_Size)\n\nOUTPUT(build/app.elf elf32-littlearm)\n"}}; | |
| var $ = function (id) { return document.getElementById(id); }; | |
| function esc(s) { | |
| return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); | |
| } | |
| function render(parsed) { | |
| var M = window.MapParse, H = []; | |
| if (!parsed.regions.length) { | |
| H.push('<p class="verdict bad">No MEMORY regions found</p>'); | |
| H.push('<p>' + esc(parsed.warnings[0] || 'This does not look like a GNU ld map file.') + '</p>'); | |
| $('result').innerHTML = H.join(''); | |
| return; | |
| } | |
| var over = null; | |
| for (var i = 0; i < parsed.regions.length; i++) if (parsed.regions[i].over) over = parsed.regions[i]; | |
| if (over) { | |
| H.push('<p class="verdict bad">' + esc(over.name) + ' overflowed by ' + | |
| M.commas(over.over) + ' bytes</p>'); | |
| } else { | |
| var tight = parsed.regions[0]; | |
| for (var t = 0; t < parsed.regions.length; t++) if (parsed.regions[t].pct > tight.pct) tight = parsed.regions[t]; | |
| H.push('<p class="verdict ok">Fits - tightest region is ' + esc(tight.name) + ' at ' + | |
| tight.pct.toFixed(1) + '%, ' + M.commas(tight.free) + ' bytes free</p>'); | |
| } | |
| H.push('<table><tr><th>Region</th><th>Used</th><th>Size</th><th>Free</th><th>Used %</th><th style="text-align:left">Fill</th></tr>'); | |
| for (var r = 0; r < parsed.regions.length; r++) { | |
| var R = parsed.regions[r]; | |
| var cls = R.over ? 'bad' : (R.pct >= 90 ? 'warn' : ''); | |
| H.push('<tr><td>' + esc(R.name) + '</td><td>' + M.commas(R.used) + '</td><td>' + M.commas(R.length) + | |
| '</td><td>' + M.commas(R.free) + '</td><td>' + R.pct.toFixed(1) + '%</td>' + | |
| '<td style="text-align:left"><div class="bar"><i class="' + cls + '" style="width:' + | |
| Math.min(100, R.pct).toFixed(1) + '%"></i></div></td></tr>'); | |
| } | |
| H.push('</table>'); | |
| var top = parseInt($('topn').value, 10) || 12; | |
| H.push('<div class="grid" style="margin-top:18px">'); | |
| for (var k = 0; k < parsed.regions.length; k++) { | |
| var Rg = parsed.regions[k]; | |
| if (!Rg.used) continue; | |
| H.push('<div><h3>' + esc(Rg.name) + ' - who is spending it</h3><table>'); | |
| H.push('<tr><th>Object</th><th>Bytes</th></tr>'); | |
| var rows = M.topIn(parsed, Rg.name, top); | |
| for (var m = 0; m < rows.length; m++) { | |
| H.push('<tr><td class="name">' + esc(rows[m].name) + '</td><td>' + M.commas(rows[m].size) + '</td></tr>'); | |
| } | |
| if (Rg.unattributed) { | |
| H.push('<tr><td class="name" style="color:#8b949e">not attributed to an input section</td><td style="color:#8b949e">' + | |
| M.commas(Rg.unattributed) + '</td></tr>'); | |
| } | |
| H.push('</table></div>'); | |
| } | |
| H.push('</div>'); | |
| H.push('<p class="note">' + parsed.stats.sections + ' output sections, ' + parsed.stats.inputs + | |
| ' input sections, ' + parsed.stats.objects + ' objects, ' + parsed.stats.archives + | |
| ' archives. <code>.data</code> is charged twice on purpose: it occupies RAM at run time and its initialiser occupies FLASH.</p>'); | |
| $('result').innerHTML = H.join(''); | |
| } | |
| function run() { | |
| var txt = $('maptext').value; | |
| render(window.MapParse.parseMap(txt, 'pasted map')); | |
| } | |
| function loadExample() { | |
| var e = EXAMPLES[$('example').value]; | |
| if (e) $('maptext').value = e.text; | |
| run(); | |
| } | |
| $('run').addEventListener('click', run); | |
| $('example').addEventListener('change', loadExample); | |
| $('topn').addEventListener('change', run); | |
| loadExample(); | |
| </script> | |
| <section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.linker-map-auditor" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'linker-map-auditor',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/linker-map-auditor" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'linker-map-auditor',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.linker-map-auditor</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section> | |
| <section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind Linker Map Auditor - FLASH & RAM budget for GNU ld map files every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"linker-map-auditor",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section> | |
| <section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">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.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">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.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_2x41azB4vGEeUd5HdFkPGOiAorn6HYGk0WDro17qEb0" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section> | |
| <!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button></div><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div><script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script> | |
| <script data-yj="yj-use-beacon v2">(function(){try{var r=document.querySelector('main')||document.body;var p=location.pathname,k='yju:'+p,sent=false,armed=false;function ours(el){return !!(el&&el.closest&&el.closest('[data-yj],[data-yj-cta],.yj-email-gate,.yj-find,.yj-related,#yj-ask,#yjgo,#yjr'));}function fire(){if(sent||!armed||sessionStorage.getItem(k))return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}r.addEventListener('click',function(e){var b=e.target&&e.target.closest?e.target.closest('button,input[type=submit],[role=button]'):null;if(!b||ours(b))return;armed=true;setTimeout(fire,400);},true);r.addEventListener('input',function(e){if(!ours(e.target))armed=true;},true);var mo=new MutationObserver(function(){if(armed){fire();if(sent)mo.disconnect();}});mo.observe(r,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script> | |
| <section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-09">Updated 2026-09-09</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-09","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku20171_result_card.jpg" alt="RAM 8,872 / 8,192 bytes: reading the one line your linker refuses to print" loading="lazy" style="max-width:100%;border-radius:6px"></p> | |
| <p>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 <code>region 'RAM' overflowed by 680 bytes</code>, and then it stops. It tells you that you lost. It does not tell you who took it.</p> | |
| <p>The information is not missing. It is sitting in <code>build/app.map</code>, 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.</p> | |
| <h3>What is actually in a map file</h3> | |
| <p>Three things, and they have to be combined:</p> | |
| <ol><li>The <code>Memory Configuration</code> block: the real origin and length of every region.</li><li>Output sections, each with an address and a size, some with a separate <code>load address</code>.</li><li>Input sections, each naming the object file or archive member that contributed it.</li></ol> | |
| <p>Combining them is the whole job, and it has two traps.</p> | |
| <p><strong><code>.data</code> is charged twice.</strong> 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.</p> | |
| <p><strong>Debug sections are charged to nothing.</strong> <code>.debug_info</code>, <code>.comment</code> and <code>.ARM.attributes</code> 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.</p> | |
| <h3>The answer, for a real build</h3> | |
| <pre><code>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</code></pre> | |
| <p>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%.</p> | |
| <p>Notice the last row. Alignment padding and the <code>_Min_Heap_Size</code> / <code>_Min_Stack_Size</code> 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.</p> | |
| <h3>Why not ask a model</h3> | |
| <p>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 <code>.map</code> file is the artefact you already have, including on a Windows machine with nothing installed.</p> | |
| <h3>Try it on your own build</h3> | |
| <p>Build with <code>-Wl,-Map=build/app.map</code>, then paste the file into the free web version, or open it in VS Code and run <strong>Linker Map: Analyze this linker map</strong>. 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.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku20171_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku20171_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Linker Map Auditor actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">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.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is Linker Map Auditor for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">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.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask an AI chatbot or use a free tool?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">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.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free, and what needs a licence?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">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.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would it cost to do this by hand?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">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.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Linker Map Auditor actually do?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "Who is Linker Map Auditor for?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "Why not just ask an AI chatbot or use a free tool?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "What is free, and what needs a licence?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "What would it cost to do this by hand?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:20171,slug:"linker-map-auditor",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/personal-data-map-dsar-audit"><span class="yj-rel-t">Personal Data Map: find every column a GDPR subject access request has to reac</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/bem-class-extractor-html"><span class="yj-rel-t">BEM Class Auditor for HTML and SCSS</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/firmware-release-gate"><span class="yj-rel-t">Firmware Release Gate for sdkconfig and prj.conf</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/gnu-global-cpp-vscode-config-pack"><span class="yj-rel-t">GNU Global C/C++ .vscode Config Pack</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/en/p/build-6-procreate-lettering-brushes-that-feel-like-ink-the-e"><span class="yj-rel-t">Build 6 Procreate Lettering Brushes That Feel Like Ink — The Exact-Settings Wo</span><span class="yj-rel-p">$39</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ca-climate-disclosure-clock-2026"><span class="yj-rel-t">CA Climate Disclosure Clock 2026 — which SB 253 / SB 261 report your group owe</span><span class="yj-rel-p">$60</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body> | |
| </html> | |