From 69d6c2944e740a519777e3e3532ff3544f48890f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Sun, 19 Jul 2026 23:42:18 +0300 Subject: [PATCH] scripts: add fw analyze script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onur Özkan --- scripts/analyze_fw.py | 122 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100755 scripts/analyze_fw.py diff --git a/scripts/analyze_fw.py b/scripts/analyze_fw.py new file mode 100755 index 0000000..c93787e --- /dev/null +++ b/scripts/analyze_fw.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Show flash, static RAM and ELF-section use for embedded firmware. + +Usage: + python3 scripts/analyze_kaptan_firmware.py +""" + +import re +from pathlib import Path +import subprocess +import sys + + +REGION = re.compile( + r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*" + r"ORIGIN\s*=\s*([^,]+),\s*LENGTH\s*=\s*([^\s/}]+)", + re.MULTILINE, +) + + +def number(text): + """Parse linker numbers such as 0x2001_0000, 176K, and 1M.""" + text = text.strip().replace("_", "") + scale = {"K": 1024, "M": 1024 * 1024}.get(text[-1:].upper(), 1) + return int(text[:-1] if scale != 1 else text, 0) * scale + + +def run(command): + """Run a tool and return stdout.""" + return subprocess.run(command, check=True, text=True, capture_output=True).stdout + + +def llvm_size(): + """Find the llvm-size installed by the active Rust toolchain.""" + sysroot = run(["rustc", "--print", "sysroot"]).strip() + host = next( + line.split(": ", 1)[1] + for line in run(["rustc", "-vV"]).splitlines() + if line.startswith("host: ") + ) + tool = Path(sysroot) / "lib" / "rustlib" / host / "bin" / "llvm-size" + if not tool.is_file(): + raise RuntimeError( + "llvm-size is unavailable; install it with: rustup component add llvm-tools-preview" + ) + return tool + + +def regions(memory_x): + """Return every named linker region as (name, origin, length).""" + return [ + (name, number(origin), number(length)) + for name, origin, length in REGION.findall(memory_x.read_text()) + ] + + +def sections(output): + """Return llvm-size sections as (name, bytes, address).""" + result = [] + for line in output.splitlines(): + fields = line.split() + if len(fields) == 3 and fields[0].startswith("."): + result.append((fields[0], int(fields[1], 0), int(fields[2], 0))) + return result + + +def region_for(address, layout): + """Return the linker region containing an address, if any.""" + for name, origin, length in layout: + if origin <= address < origin + length: + return name + return "outside" + + +def main(): + if len(sys.argv) != 3: + raise ValueError(f"usage: {sys.argv[0]} ") + + elf = Path(sys.argv[1]) + memory_x = Path(sys.argv[2]) + if not elf.is_file(): + raise ValueError(f"ELF does not exist: {elf}") + if not memory_x.is_file(): + raise ValueError(f"linker layout does not exist: {memory_x}") + + layout = regions(memory_x) + if not layout: + raise ValueError(f"no MEMORY regions found in: {memory_x}") + section_list = sections(run([str(llvm_size()), "-A", str(elf)])) + if not section_list: + raise ValueError(f"no sections found in: {elf}") + + print(f"\nFirmware: {elf}") + print(f"ELF file size: {elf.stat().st_size:,} bytes") + print("\nLinker regions:") + print(" Region Origin Used / Capacity Remaining") + for name, origin, length in layout: + used = sum( + size + for _, size, address in section_list + if size and origin <= address < origin + length + ) + remaining = length - used + print( + f" {name:<12} 0x{origin:08X} {used / 1024:>7.2f} KiB /" + f" {length / 1024:>7.2f} KiB {remaining / 1024:>7.2f} KiB" + f" ({100 * used / length:5.2f}%)" + ) + + print("\nELF sections:") + print(" Region Address Size Section") + for name, size, address in sorted(section_list, key=lambda section: section[2]): + if size: + print(f" {region_for(address, layout):<12} 0x{address:08X} {size:>10,} {name}") + + +if __name__ == "__main__": + try: + main() + except (IndexError, OSError, RuntimeError, ValueError, subprocess.CalledProcessError) as error: + print(f"error: {error}", file=sys.stderr) + raise SystemExit(1) from error